Timeout exceptions sometimes don't work, even in pure (non FFI) code

I'm trying to test some properties with quickcheck. If these tests fail, they will almost certainly fail by nontermination. I've been using the 'within' function to catch these nontermination cases. However, I was surprised to find that this doesn't always work. 'within' uses the 'timeout' function under the hood. Here is an example that demonstrates the problem: import System.Timeout import Control.Exception works :: Int -> Int works x = sum $ cycle [x] doesntWork :: Int -> Int doesntWork x = last $ cycle [x] test1 = timeout 1 $ evaluate $ works 5 == 5 -- terminates test2 = timeout 1 $ evaluate $ doesntWork 5 == 5 -- never terminates test1 returns Nothing as expected, but test2 never terminates. Why? I thought timeout exceptions are supposed to always work with pure (non FFI) Haskell code. Is there any way I can work around this? I'm using ghc 6.12.2 Thanks, - Job

On Tuesday 01 February 2011 16:40:43, Job Vranish wrote:
I'm trying to test some properties with quickcheck. If these tests fail, they will almost certainly fail by nontermination. I've been using the 'within' function to catch these nontermination cases. However, I was surprised to find that this doesn't always work. 'within' uses the 'timeout' function under the hood. Here is an example that demonstrates the problem:
import System.Timeout import Control.Exception
works :: Int -> Int works x = sum $ cycle [x]
doesntWork :: Int -> Int doesntWork x = last $ cycle [x]
test1 = timeout 1 $ evaluate $ works 5 == 5 -- terminates test2 = timeout 1 $ evaluate $ doesntWork 5 == 5 -- never terminates
test1 returns Nothing as expected, but test2 never terminates. Why?
When compiled with optimisations, works doesn't terminate either. I believe it's because works actually does some work and allocations (without optimisations), while doesntWork is a non-allocating loop. GHC only makes context switches on allocations, so with a non-allocating loop, the timeout thread never gets to run to see whether the time limit is exceeded. Without optimisations, sum allocates thunks, with optimisations it becomes a tight loop not hitting the heap. `last $ cycle [x]' becomes a tight loop even without optimisations.
I thought timeout exceptions are supposed to always work with pure (non FFI) Haskell code. Is there any way I can work around this?
Make sure your tests always allocate, or write them as IO stuff and call yield (or threadDelay) within the loops to let GHC make some context switches.
I'm using ghc 6.12.2
Thanks,
- Job

Hmm, that's good to know. It looks like it also works for most data dependency cases. works = let a = b b = a in a main = timeout 1 $ evaluate $ works This terminates even with optimizations. So I think I'll be able to work around it. I wish timeout worked a little more consistently. This behavior is quite annoying. Thanks for your help, - Job On Tue, Feb 1, 2011 at 11:18 AM, Daniel Fischer < daniel.is.fischer@googlemail.com> wrote:
On Tuesday 01 February 2011 16:40:43, Job Vranish wrote:
I'm trying to test some properties with quickcheck. If these tests fail, they will almost certainly fail by nontermination. I've been using the 'within' function to catch these nontermination cases. However, I was surprised to find that this doesn't always work. 'within' uses the 'timeout' function under the hood. Here is an example that demonstrates the problem:
import System.Timeout import Control.Exception
works :: Int -> Int works x = sum $ cycle [x]
doesntWork :: Int -> Int doesntWork x = last $ cycle [x]
test1 = timeout 1 $ evaluate $ works 5 == 5 -- terminates test2 = timeout 1 $ evaluate $ doesntWork 5 == 5 -- never terminates
test1 returns Nothing as expected, but test2 never terminates. Why?
When compiled with optimisations, works doesn't terminate either.
I believe it's because works actually does some work and allocations (without optimisations), while doesntWork is a non-allocating loop. GHC only makes context switches on allocations, so with a non-allocating loop, the timeout thread never gets to run to see whether the time limit is exceeded. Without optimisations, sum allocates thunks, with optimisations it becomes a tight loop not hitting the heap. `last $ cycle [x]' becomes a tight loop even without optimisations.
I thought timeout exceptions are supposed to always work with pure (non FFI) Haskell code. Is there any way I can work around this?
Make sure your tests always allocate, or write them as IO stuff and call yield (or threadDelay) within the loops to let GHC make some context switches.
I'm using ghc 6.12.2
Thanks,
- Job
participants (2)
-
Daniel Fischer
-
Job Vranish