48f0ddcf29c74e6878fb7c6c113a35638f53d173
[ghc-base.git] / System / Timeout.hs
1 {-# OPTIONS -fglasgow-exts #-}
2 -------------------------------------------------------------------------------
3 -- |
4 -- Module      :  System.Timeout
5 -- Copyright   :  (c) The University of Glasgow 2007
6 -- License     :  BSD-style (see the file libraries/base/LICENSE)
7 --
8 -- Maintainer  :  libraries@haskell.org
9 -- Stability   :  experimental
10 -- Portability :  non-portable
11 --
12 -- Attach a timeout event to arbitrary 'IO' computations.
13 --
14 -------------------------------------------------------------------------------
15
16 module System.Timeout ( timeout ) where
17
18 import Prelude             (IO, Ord((<)), Eq((==)), Int, (.), otherwise, fmap)
19 import Data.Maybe          (Maybe(..))
20 import Control.Monad       (Monad(..), guard)
21 import Control.Concurrent  (forkIO, threadDelay, myThreadId, killThread)
22 import Control.Exception   (handleJust, throwDynTo, dynExceptions, bracket)
23 import Data.Dynamic        (Typeable, fromDynamic)
24 import Data.Unique         (Unique, newUnique)
25
26 -- An internal type that is thrown as a dynamic exception to
27 -- interrupt the running IO computation when the timeout has
28 -- expired.
29
30 data Timeout = Timeout Unique deriving (Eq, Typeable)
31
32 -- |Wrap an 'IO' computation to time out and return @Nothing@ in case no result
33 -- is available within @n@ microseconds (@1\/10^6@ seconds). In case a result
34 -- is available before the timeout expires, @Just a@ is returned. A negative
35 -- timeout interval means \"wait indefinitely\". When specifying long timeouts,
36 -- be careful not to exceed @maxBound :: Int@.
37 --
38 -- The design of this combinator was guided by the objective that @timeout n f@
39 -- should behave exactly the same as @f@ as long as @f@ doesn't time out. This
40 -- means that @f@ has the same 'myThreadId' it would have without the timeout
41 -- wrapper. Any exceptions @f@ might throw cancel the timeout and propagate
42 -- further up. It also possible for @f@ to receive exceptions thrown to it by
43 -- another thread.
44 --
45 -- A tricky implementation detail is the question of how to abort an @IO@
46 -- computation. This combinator relies on asynchronous exceptions internally.
47 -- The technique works very well for computations executing inside of the
48 -- Haskell runtime system, but it doesn't work at all for non-Haskell code.
49 -- Foreign function calls, for example, cannot be timed out with this
50 -- combinator simply because an arbitrary C function cannot receive
51 -- asynchronous exceptions. When @timeout@ is used to wrap an FFI call that
52 -- blocks, no timeout event can be delivered until the FFI call returns, which
53 -- pretty much negates the purpose of the combinator. In practice, however,
54 -- this limitation is less severe than it may sound. Standard I\/O functions
55 -- like 'System.IO.hGetBuf', 'System.IO.hPutBuf', 'Network.Socket.accept', or
56 -- 'System.IO.hWaitForInput' appear to be blocking, but they really don't
57 -- because the runtime system uses scheduling mechanisms like @select(2)@ to
58 -- perform asynchronous I\/O, so it is possible to interrupt standard socket
59 -- I\/O or file I\/O using this combinator.
60
61 timeout :: Int -> IO a -> IO (Maybe a)
62 timeout n f
63     | n <  0    = fmap Just f
64     | n == 0    = return Nothing
65     | otherwise = do
66         pid <- myThreadId
67         ex  <- fmap Timeout newUnique
68         handleJust (\e -> dynExceptions e >>= fromDynamic >>= guard . (ex ==))
69                    (\_ -> return Nothing)
70                    (bracket (forkIO (threadDelay n >> throwDynTo pid ex))
71                             (killThread)
72                             (\_ -> fmap Just f))