Safe Haskell | Safe-Inferred |
---|---|
Language | Haskell2010 |
“Unfork” is the opposite of “fork”; whereas forking allows things to run concurrently, unforking prevents things from running concurrently. Use one of the functions in this module when you have an action that will be used by concurrent threads but needs to run serially.
Result available | Result discarded | |
Async I/O | unforkAsyncIO | unforkAsyncIO_ |
Async STM | unforkAsyncSTM | unforkAsyncSTM_ |
Sync I/O | unforkSyncIO | unforkSyncIO_ |
Example
A typical use case is a multi-threaded program that writes log messages. If threads use putStrLn
directly, the strings may be interleaved in the combined output.
concurrently_
(putStrLn
"one") (putStrLn
"two")
Instead, create an unforked version of putStrLn
.
unforkAsyncIO_
putStrLn
$ \log ->concurrently_
(log "one") (log "two")
Asynchrony
The four async functions are unforkAsyncIO
, unforkAsyncIO_
, unforkAsyncSTM
, unforkAsyncSTM_
.
unforkAsyncIO :: (a -> IO b) -> ( ( a -> IO (Future b) ) -> IO c ) -> IO c unforkAsyncIO_ :: (a -> IO b) -> ( ( a -> IO () ) -> IO c ) -> IO c unforkAsyncSTM :: (a -> IO b) -> ( ( a -> STM (STM (Maybe b)) ) -> IO c ) -> IO c unforkAsyncSTM_ :: (a -> IO b) -> ( ( a -> STM () ) -> IO c ) -> IO c | | | | | | |---------| | |--------------------------| | Original | Unforked action | action | | |--------------------------------------| Continuation
These functions all internally use a queue. The unforked action does not perform the underlying action at all, but instead merely writes to the queue. A separate thread reads from the queue and performs the actions, thus ensuring that the actions are all performed in one linear sequence.
There are, therefore, three threads of concern to this library:
- the one running the user-provided continuation
- the one performing the enqueued actions
- the parent thread that owns the other two
Non-exceptional termination works as follows:
- Thread 1 reaches its normal end and halts
- Thread 2 finishes processing any remaining queued jobs, then halts
- Thread 3 halts
Threads 1 and 2 are “linked”, in the parlance of Control.Concurrent.Async; if either thread throws an exception, then the other action is cancelled, and the exception is re-thrown by thread 3. Likewise, any exception that is thrown to the parent thread will result in the cancellation of it children. In other words, if anything fails, then the entire system fails immediately. This is desirable for two reasons:
- It avoids the risk of leaving any dangling threads
- No exceptions are “swallowed”; if something fails, you will see the exception.
If this is undesirable, you can change the behavior by catching and handling exceptions. If you want a system that is resilient to failures of the action, then unfork an action that catches exceptions. If you want a system that finishes processing the queue even after the continuation fails, then use a continuation that catches and handles exceptions.
Results
The functions in this module come in pairs: one that provides some means of obtaining the result, and one (ending in an underscore) that discards the action's result.
In the asynchronous case, the result-discarding functions provide no means of even determining whether the action has completed yet; we describe these as "fire-and-forget" functions, because there is no further interaction the initiator of an action can have with it after the action has begun.
The async functions that do provide results are unforkAsyncSTM
and unforkAsyncIO
. Internally, each result is stored in a TVar
or MVar
, respectively. These variables are exposed to the user in a read-only way:
unforkAsyncSTM
gives access to itsTVar
via(
, whose value isSTM
(Maybe
result))Nothing
while the action is in flight, andJust
thereafter.unforkAsyncIO
gives access to itsMVar
via(
. TheFuture
result)Future
type offers two functions:poll
to see the current status (Nothing
while the action is in flight, andJust
thereafter), andawait
to block until the action completes.
In both cases, an action is either pending or successful. There is no representation of a “threw an exception” action result. This is because of the “if anything fails, then the entire system fails immediately” property discussed in the previous section. If an action throws an exception, your continuation won't live long enough to witness it anyway because it will be immediately killed.
Synchrony
The two sync functions are unforkSyncIO
and unforkSyncIO_
.
unforkSyncIO :: (a -> IO b) -> IO (a -> IO b ) unforkSyncIO_ :: (a -> IO b) -> IO (a -> IO ()) | | | | |---------| |----------| Original action Unforked action
These are much simpler than their asynchronous counterparts; there is no queue, no new threads are spawned, and therefore no continuation-passing is needed. These simply produce a variant of the action that is bracket
ed by acquisition and release of an MVar
to assure mutual exclusion.
The hazard of the synchronous approach is that the locking has a greater potential to bottleneck performance.
Synopsis
- unforkAsyncIO_ :: (task -> IO result) -> ((task -> IO ()) -> IO conclusion) -> IO conclusion
- unforkAsyncIO :: (task -> IO result) -> ((task -> IO (Future result)) -> IO conclusion) -> IO conclusion
- data Future result
- await :: Future result -> IO result
- poll :: Future result -> IO (Maybe result)
- unforkAsyncSTM_ :: (task -> IO result) -> ((task -> STM ()) -> IO conclusion) -> IO conclusion
- unforkAsyncSTM :: (task -> IO result) -> ((task -> STM (STM (Maybe result))) -> IO conclusion) -> IO conclusion
- unforkSyncIO_ :: (task -> IO result) -> IO (task -> IO ())
- unforkSyncIO :: (task -> IO result) -> IO (task -> IO result)
Asynchronous I/O
:: (task -> IO result) | Action that needs to be run serially |
-> ((task -> IO ()) -> IO conclusion) | Continuation with the unforked action |
-> IO conclusion |
Turns an IO action into a fire-and-forget async action
For example, use (
to log to unforkAsyncIO_
putStrLn
)stdout
in a multi-threaded application.
Related functions:
unforkAsyncIO
does not discard the action result, and it allows polling or waiting for completionunforkAsyncSTM_
gives the unforked action result asSTM
instead ofIO
:: (task -> IO result) | Action that needs to be run serially |
-> ((task -> IO (Future result)) -> IO conclusion) | Continuation with the unforked action |
-> IO conclusion |
Unforks an action, with the new action's asynchronous result available as (
IO
(Future
result))
Related functions:
- Use
unforkAsyncIO_
if you do not need to know when the action has completed or obtain its result value - Use
unforkAsyncSTM
if you need the composability ofSTM
Asynchronous STM
:: (task -> IO result) | Action that needs to be run serially |
-> ((task -> STM ()) -> IO conclusion) | Continuation with the unforked action |
-> IO conclusion |
Turns an IO action into a fire-and-forget STM action
Related functions:
unforkAsyncSTM
does not discard the action result, and it allows polling or waiting for completionunforkAsyncIO_
gives the unforked action result asIO
instead ofSTM
:: (task -> IO result) | Action that needs to be run serially |
-> ((task -> STM (STM (Maybe result))) -> IO conclusion) | Continuation with the unforked action |
-> IO conclusion |
Unforks an action, with the new action's asynchronous result available as (
STM
(Maybe
result))
Related functions:
- Use
unforkAsyncSTM_
if you do not need to know when the action has completed or obtain its result value - Use
unforkAsyncIO
if you do not need the composability ofSTM
Synchronous I/O
Unforks an action by blocking on a mutex lock, discarding the action's result
Related functions:
- Use
unforkSyncIO
if you need the action's result - Consider instead using
unforkAsyncIO_
, which uses a queue and a separate thread, to avoid blocking
:: (task -> IO result) | Action that needs to be run serially |
-> IO (task -> IO result) | The unforked action |
Unforks an action by blocking on a mutex lock
Related functions:
- Use
unforkSyncIO_
if you don't need the action's result - Consider instead using
unforkAsyncIO
, which uses a queue and a separate thread, to avoid blocking