Copyright | (c) Tim Watson 2012 - 2017 |
---|---|
License | BSD3 (see the file LICENSE) |
Maintainer | Tim Watson <watson.timothy@gmail.com> |
Stability | experimental |
Portability | non-portable (requires concurrency) |
Safe Haskell | None |
Language | Haskell98 |
The Client Portion of the Managed Process API.
- sendControlMessage :: Serializable m => ControlPort m -> m -> Process ()
- shutdown :: ProcessId -> Process ()
- call :: forall s a b. (Addressable s, Serializable a, Serializable b) => s -> a -> Process b
- safeCall :: forall s a b. (Addressable s, Serializable a, Serializable b) => s -> a -> Process (Either ExitReason b)
- tryCall :: forall s a b. (Addressable s, Serializable a, Serializable b) => s -> a -> Process (Maybe b)
- callTimeout :: forall s a b. (Addressable s, Serializable a, Serializable b) => s -> a -> TimeInterval -> Process (Maybe b)
- flushPendingCalls :: forall b. Serializable b => TimeInterval -> (b -> Process b) -> Process (Maybe b)
- callAsync :: forall s a b. (Addressable s, Serializable a, Serializable b) => s -> a -> Process (Async b)
- cast :: forall a m. (Addressable a, Serializable m) => a -> m -> Process ()
- callChan :: forall s a b. (Addressable s, Serializable a, Serializable b) => s -> a -> Process (ReceivePort b)
- syncCallChan :: forall s a b. (Addressable s, Serializable a, Serializable b) => s -> a -> Process b
- syncSafeCallChan :: forall s a b. (Addressable s, Serializable a, Serializable b) => s -> a -> Process (Either ExitReason b)
- callSTM :: forall s a b. Addressable s => s -> (a -> STM ()) -> STM b -> a -> Process (Either ExitReason b)
API for client interactions with the process
sendControlMessage :: Serializable m => ControlPort m -> m -> Process () Source #
Send a control message over a ControlPort
.
shutdown :: ProcessId -> Process () Source #
Send a signal instructing the process to terminate. The receive loop which
manages the process mailbox will prioritise Shutdown
signals higher than
any other incoming messages, but the server might be busy (i.e., still in the
process of excuting a handler) at the time of sending however, so the caller
should not make any assumptions about the timeliness with which the shutdown
signal will be handled. If responsiveness is important, a better approach
might be to send an exit signal with Shutdown
as the reason. An exit
signal will interrupt any operation currently underway and force the running
process to clean up and terminate.
call :: forall s a b. (Addressable s, Serializable a, Serializable b) => s -> a -> Process b Source #
Make a synchronous call - will block until a reply is received.
The calling process will exit with ExitReason
if the calls fails.
NOTE: this function does not catch exceptions!
safeCall :: forall s a b. (Addressable s, Serializable a, Serializable b) => s -> a -> Process (Either ExitReason b) Source #
Safe version of call
that returns information about the error
if the operation fails. If the calling process dies (that is, forces itself
to exit such that an exit signal arises with ExitOther String
) then
evaluation will return Left exitReason
and the explanation will be
stashed away as (ExitOther String)
.
NOTE: this function does not catch exceptions!
The safety of the name, comes from carefully handling situations in which the server dies while we're waiting for a reply. Notably, exit signals from other processes, kill signals, and both synchronous and asynchronous exceptions can still terminate the caller abruptly. To avoid this consider masking or evaluating within your own exception handling code.
tryCall :: forall s a b. (Addressable s, Serializable a, Serializable b) => s -> a -> Process (Maybe b) Source #
Version of safeCall
that returns Nothing
if the operation fails. If
you need information about *why* a call has failed then you should use
safeCall
or combine catchExit
and call
instead.
NOTE: this function does not catch exceptions!
In fact, this API handles fewer exceptions than it's relative, "safeCall". Notably, exit signals, kill signals, and both synchronous and asynchronous exceptions can still terminate the caller abruptly. To avoid this consider masking or evaluating within your own exception handling code (as mentioned above).
callTimeout :: forall s a b. (Addressable s, Serializable a, Serializable b) => s -> a -> TimeInterval -> Process (Maybe b) Source #
Make a synchronous call, but timeout and return Nothing
if a reply
is not received within the specified time interval.
If the result of the call is a failure (or the call was cancelled) then
the calling process will exit, with the ExitReason
given as the reason.
If the call times out however, the semantics on the server side are
undefined, i.e., the server may or may not successfully process the
request and may (or may not) send a response at a later time. From the
callers perspective, this is somewhat troublesome, since the call result
cannot be decoded directly. In this case, the "flushPendingCalls" API may
be used to attempt to receive the message later on, however this makes
no attempt whatsoever to guarantee which call response will in fact
be returned to the caller. In those semantics are unsuited to your
application, you might choose to exit
or die
in case of a timeout,
or alternatively, use the callAsync
API and associated waitTimeout
function (in the Async API), which takes a re-usable handle on which
to wait (with timeouts) multiple times.
flushPendingCalls :: forall b. Serializable b => TimeInterval -> (b -> Process b) -> Process (Maybe b) Source #
Attempt to flush out any pending call responses.
callAsync :: forall s a b. (Addressable s, Serializable a, Serializable b) => s -> a -> Process (Async b) Source #
Invokes call
out of band, and returns an async handle.
cast :: forall a m. (Addressable a, Serializable m) => a -> m -> Process () Source #
callChan :: forall s a b. (Addressable s, Serializable a, Serializable b) => s -> a -> Process (ReceivePort b) Source #
Sends a channel message to the server and returns a ReceivePort
on
which the reponse can be delivered, if the server so chooses (i.e., the
might ignore the request or crash).
syncCallChan :: forall s a b. (Addressable s, Serializable a, Serializable b) => s -> a -> Process b Source #
A synchronous version of callChan
.
syncSafeCallChan :: forall s a b. (Addressable s, Serializable a, Serializable b) => s -> a -> Process (Either ExitReason b) Source #
A safe version of syncCallChan
, which returns Left ExitReason
if the
call fails.
callSTM :: forall s a b. Addressable s => s -> (a -> STM ()) -> STM b -> a -> Process (Either ExitReason b) Source #
Manages an rpc-style interaction with a server process, using STM
actions
to read/write data. The server process is monitored for the duration of the
call. The stm write expression is passed the input, and the read expression
is evaluated and the result given as Right b
or Left ExitReason
if a
monitor signal is detected whilst waiting.
Note that the caller will exit (with ExitOther String
) if the server
address is un-resolvable.
A note about scheduling and timing guarantees (or lack thereof): It is not
possibly to guarantee the contents of ExitReason
in cases where this API
fails due to server exits/crashes. We establish a monitor prior to evaluating
the stm writer action, however monitor
is asychronous and we've no way to
know whether or not the scheduler will allow monitor establishment to proceed
first, or the stm transaction. As a result, assuming that your server process
can diefailexit on evaluating the read end of the STM write we perform here
(and we assume this is very likely, since we apply no safety rules and do not
even worry about serializing thunks passed from the client's thread), it is
just as likely that in the case of failure you will see a reason such as
ExitOther DiedUnknownId
due to the server process crashing before the node
controller can establish a monitor.
As unpleasant as this is, there's little we can do about it without making false assumptions about the runtime. Cloud Haskell's semantics guarantee us only that we will see some monitor signal in the event of a failure here. To provide a more robust error handling, you can catch/trap failures in the server process and return a wrapper reponse datum here instead. This will still be subject to the failure modes described above in cases where the server process exits abnormally, but that will at least allow the caller to differentiate between expected and exceptional failure conditions.