Timeout and unsafePerformIO

237 Views Asked by At

I'm getting some practice in Haskell, exploring some areas I'm not familiar with, but I've ben unable to understand the behavior I get while mixing System.Timeout and System.IO.Unsafe.

I'm lazyly read an stream, with getContents, filtering it with a pure function, and outputting the results. A typical filter would be like:

import Data.List(break)
import System.Timeout(timeout)
import System.IO.Unsafe(unsafePerformIO)

main = do
    text <- getContents 
    putStr $ aFilter text
aFilter text = h ++ t where
    (h, t) = getUntil "\n" text
getUntil separator text = break (\x -> (separator!!0) == x) text

And with a filter like that, the program reads all stdin, as expected, and outputs to stdout. But If I do something like:

aFilter text = result where
    l = consumeWithTimeout (getUntil "\n" text)
    result = case l of
        Nothing -> "Timeout!\n"
        Just (h, t) -> h ++ t

consumeWithTimeout (x, y) = unsafePerformIO $! timeout 6 $! deepseq x (return (x, y))

I'd expect my program to instantly timeout, print the "Timeout!" message, and close. Instead, it hangs there, waiting for input.

Am I wrong in thinking that the timeout function is evaluated at program launch? I expect it to be, because I immediately write part of its return value to stdout, and the software does react every time I input a line. Is unsafePerformIO inserting some kind of lazyness into my function? Or is it inserting lazyness into the internals of System.Timeout?

2

There are 2 best solutions below

0
chi On

I would expect

timeout 6 $! return $! (x, y)

to never trigger the timeout under normal workload. The code above does not force the evaluation of x and y. Maybe using evaluate will help here.

Further, using unsafePerformIO for this task looks rather overkill. Using unsafePerformIO should be done only as a last resort.

1
Gabriella Gonzalez On

The reason why is that return $! (x, y) does not strictly evaluate x or y. It only evaluates the tuple constructor, which does not necessarily evaluate its fields x and y.

So what happens in your program is that return $! (x, y) succeeds immediately without actually trying to evaluate x and y. The h ++ t part then begins to evaluate h which is when it finally begins to block for input.

This is, by the way, the reason you should not use unsafePerformIO: you cannot easily reason about when effects actually happen.