This Future module was written by Chris Kuklewicz to see if he understood the design and utility of the new C++ standard's future. In particular the ability to cleanly access either a resulting value or exception.
There a methods to poll (with check
), to block (with wait
or timedWait
), and to block and
retrieve the actual value or rethrow the exception in the accessing thread (with get
or
timedGet
). Timeouts are in micro seconds, values less than or equal to zero use non-blocking
check
. The timeout should be detected reagarless of the blocking or FFI state of the worker
thread.
One can also manage the threadBy calling abort
, which may cause the promise to store the
exception from the abort as well as killing the worker thread. The worker thread Id is a
secret, this is needed to ensure the running of the continuations. The abort
operation has the
same synchronous behavior as killThread
.
Note: There is no way for an outside thread to directly set the value of the promise to a
non-exception value. Using abort
(or throwTo
with getPromiseThreadId
) creates a race
condition in setting the result of the promise. There is no way to change the result of promise
once it has been set.
The extension to the C++ standard is in the continuation attachment. The addTodo
command
will, while the worker is running, add the todo
continuation to an internal list. Immediately
upon finishing the action the worker thread will always run through the queued continuations.
Each todo
will be run in its own forkIO thread (unblocked). If the addTodo
command is
issued after the promise value has been set then it simplify runs the todo
in a new thread.
Thus there is no way multiple continuations can interfere with each other, and there are no
ordering guarantees between them. The todo
action will not be able to distinguish whether it
is being run from the stored queue or immediately.
The use of block
and finally
should ensure that no matter how the worker ends the stored
continations are run. For instnace: if abort
is used then the continations might be run with
that thread killing exception or with the custom Promise.abort exception if no other result is
already present.
One use case for addTodo
is to allow multiplexing. Several promises could be given a
continuation to write the results to an MChan or MVar, allowing another process to block waiting
for the first one to finish.
- data Promise a
- type PromiseResult a = Either SomeException a
- forkPromise :: IO a -> IO (Promise a)
- forkPromises :: [IO a] -> IO ([Promise a], Chan (PromiseResult a))
- racePromises :: [IO a] -> IO (PromiseResult a)
- check :: Promise a -> IO (Maybe (PromiseResult a))
- wait :: Promise a -> IO (PromiseResult a)
- get :: Promise a -> IO a
- timedWait :: Int -> Promise a -> IO (Maybe (PromiseResult a))
- timedGet :: Int -> Promise a -> IO (Maybe a)
- abort :: Promise t -> IO ()
- addTodo :: Promise a -> (PromiseResult a -> IO ()) -> IO ()
Documentation
type PromiseResult a = Either SomeException aSource
forkPromise :: IO a -> IO (Promise a)Source
forkPromise take an action to run, and runs it in a new thread. This is run in an unblock context. If the action succeeds it will store its result as (Right {}). If the action throws an exception, or the
forkPromises :: [IO a] -> IO ([Promise a], Chan (PromiseResult a))Source
racePromises :: [IO a] -> IO (PromiseResult a)Source
timedWait :: Int -> Promise a -> IO (Maybe (PromiseResult a))Source
timedWait
with a positive value in micro seconds is a blocking read with timeout.