-- |An experimental library for lazy promises that can be evaluated in -- pure code. -- -- Evaluation of a promise before its thread completes results in an -- indefinite block. This is accomplished by the use of 'unsafeInterleaveIO'. -- Thus, care should be taken in using this library, since it couples the -- execution time of pure code with an arbitrary IO computation. -- Using System.Timeout from the timeout package can help to ensure that -- forcing a promise is always well-defined. -- -- For safer implementations of promises, see Control.Concurrent.Spawn from -- the spawn package, and Control.Concurrent.Future from the future package. module Control.Concurrent.Promise.Unsafe ( -- *Creating promises promise, tryPromise -- *Creating lists of promises , promises, tryPromises ) where import Control.Concurrent.Thread (forkIO, Result, result) import Control.Concurrent.Chan (Chan, newChan, readChan, writeChan) import Control.Exception (catch) import GHC.Conc (pseq) import System.IO.Unsafe (unsafeInterleaveIO) import Control.Monad import Control.Applicative import Prelude hiding (catch) safePromises :: [IO a] -> IO (Chan (Result a)) safePromises ios = do c <- newChan forM_ ios $ \io -> forkIO $ (writeChan c . Right =<< io) `catch` (writeChan c . Left) return c -- |Forks an IO computation as a thread and immediately returns -- a lazy future. Evaluating the future before the thread completes -- causes it to wait for a result. If the thread halts with a thrown exception, -- then evaluating the future will re-throw the exception. promise :: IO a -> IO a promise io = head <$> promises [io] -- |Like 'promise', but does not rethrow exceptions. Instead the exception is -- wrapped as part of the 'Result'. tryPromise :: IO a -> IO (Result a) tryPromise io = head <$> tryPromises [io] -- |Forks a sequence of IO computations in multiple threads, and immediately -- returns a list of futures. The order of the futures is determined by -- the order in which the threads terminate. If an exception is thrown by the -- list of threads, then the exception is re-thrown when its corresponding -- future is evaluated. promises :: [IO a] -> IO [a] promises ios = do c <- safePromises ios fmap (scanl1 pseq) . forM ios $ \_ -> unsafeInterleaveIO (result =<< readChan c) {-# NOINLINE promises #-} -- |Like 'promises', but doesn't re-throw exceptions. tryPromises :: [IO a] -> IO [Result a] tryPromises ios = do c <- safePromises ios fmap (scanl1 pseq) . forM ios $ \_ -> unsafeInterleaveIO (readChan c) {-# NOINLINE tryPromises #-}