Copyright | (c) Naoto Shimazaki 2018-2020 |
---|---|
License | MIT (see the file LICENSE) |
Maintainer | https://github.com/nshimaza |
Stability | experimental |
Safe Haskell | None |
Language | Haskell2010 |
A simplified implementation of Erlang/OTP like supervisor over thread and underlying behaviors.
Synopsis
- data Inbox a
- newtype ActorQ a = ActorQ (Inbox a)
- send :: ActorQ a -> a -> IO ()
- trySend :: ActorQ a -> a -> IO (Maybe ())
- length :: ActorQ a -> IO Word
- receive :: Inbox a -> IO a
- tryReceive :: Inbox a -> IO (Maybe a)
- receiveSelect :: (a -> Bool) -> Inbox a -> IO a
- tryReceiveSelect :: (a -> Bool) -> Inbox a -> IO (Maybe a)
- type ActorHandler message result = Inbox message -> IO result
- data Actor message result = Actor {
- actorQueue :: ActorQ message
- actorAction :: IO result
- newActor :: ActorHandler message result -> IO (Actor message result)
- newBoundedActor :: InboxLength -> ActorHandler message result -> IO (Actor message result)
- type MonitoredAction = (IO () -> IO ()) -> IO ()
- data ExitReason
- type Monitor = ExitReason -> ThreadId -> IO ()
- watch :: Monitor -> IO () -> MonitoredAction
- nestWatch :: Monitor -> MonitoredAction -> MonitoredAction
- noWatch :: IO () -> MonitoredAction
- data Restart
- data ChildSpec
- newChildSpec :: Restart -> IO () -> ChildSpec
- newMonitoredChildSpec :: Restart -> MonitoredAction -> ChildSpec
- addMonitor :: Monitor -> ChildSpec -> ChildSpec
- data RestartSensitivity = RestartSensitivity {}
- data IntenseRestartDetector
- newIntenseRestartDetector :: RestartSensitivity -> IntenseRestartDetector
- detectIntenseRestart :: IntenseRestartDetector -> TimeSpec -> (Bool, IntenseRestartDetector)
- detectIntenseRestartNow :: IntenseRestartDetector -> IO (Bool, IntenseRestartDetector)
- data Strategy
- type SupervisorQueue = ActorQ SupervisorMessage
- newSupervisor :: Strategy -> RestartSensitivity -> [ChildSpec] -> ActorHandler SupervisorMessage ()
- newSimpleOneForOneSupervisor :: ActorHandler SupervisorMessage ()
- newChild :: CallTimeout -> SupervisorQueue -> ChildSpec -> IO (Maybe ThreadId)
- newStateMachine :: state -> (state -> message -> IO (Either result state)) -> ActorHandler message result
- newStateMachineActor :: state -> (state -> message -> IO (Either result state)) -> IO (Actor message result)
- newtype CallTimeout = CallTimeout Int
- type ServerCallback a = a -> IO ()
- cast :: ActorQ cmd -> cmd -> IO ()
- call :: CallTimeout -> ActorQ cmd -> (ServerCallback a -> cmd) -> IO (Maybe a)
- callAsync :: CallTimeout -> ActorQ cmd -> (ServerCallback a -> cmd) -> (Maybe a -> IO b) -> IO (Async b)
- callIgnore :: ActorQ cmd -> (ServerCallback a -> cmd) -> IO ()
Actor and Message queue
Actor is restartable IO action with inbound message queue. Actor is
designed to allow other threads sending messages to an actor keep using
the same write-end of the queue before and after restart of the actor.
Actor consists of message queue and its handler. Inbox
is a message
queue designed for actor's message inbox. It is thread-safe, bounded or
unbounded, and selectively readable queue.
To protect read-end of the queue, it has different type for read-end and
write-end. Message handler of actor can access to both end but only
write-end is accessible from outside of message handler. To realize
this, constructor of Inbox
is not exposed. The only way to create a
new Inbox
object is creating a new actor using newActor
function.
newActor :: (Inbox message -> IO result) -> IO (Actor message result)
This package provides type synonym for message handler as below.
type ActorHandler message result = (Inbox message -> IO result)
newActor
receives an user supplied message handler, creates a new
Inbox
value, then returns write-end of actor's message queue and IO
action of the actor's body wrapped by Actor
. Actor
is defined as
following.
data Actor message result = Actor { actorQueue :: ActorQ message -- ^ Write end of message queue of 'Actor' , actorAction :: IO result -- ^ IO action to execute 'Actor' }
The 'ActorQ message' in the Actor
is the write-end of created Inbox
.
While user supplied message handler receives Inbox
, which is read-end
of created queue, caller of newActor
gets write-end only.
Message queue
Inbox
is specifically designed queue for implementing actor. All
behaviors available in this package depend on it. It provides following
capabilities.
- Thread-safe read/pull/receive and write/push/send.
- Blocking and non-blocking read operation.
- Selective read operation.
- Current queue length.
- Bounded queue.
The type Inbox
is intended to be used only for pulling side as inbox
of actor. Single Inbox object is only readable from single actor. In
order to avoid from other actors, no Inbox constructor is exposed but
instead you can get it only via newActor
or newBoundedActor
.
Read an oldest message from Inbox
To read a message at the head of message queue, apply receive
to
Inbox
. If one or more message is available, receive
returns oldest
one. If no message is available, receive
blocks until at least one
message arrives. A skeleton of actor message handler will look like
this.
myActorHandler :: Inbox YourMessageType -> IO () myActorHandler inbox = do newMessage <- receive inbox doSomethingWith newMessage myActorHandler inbox
Send a message to an actor
To send a message to an actor, call send
with write-end of the actor's
inbox and the message.
send :: ActorQ message -> message -> IO ()
ActorQ
is write-end of actor's message queue. ActorQ
is actually
just a wrapper of Inbox
. Its role is hiding read-end API of Inbox.
From outside of actor, only write-end is exposed via ActorQ. From
inside of actor, both read-end and write-end are available. You can
read from given inbox directly. You need to wrap the inbox by Actor
when you write to the inbox.
Send a message from an actor to itself
When you need to send a message to your inbox, do this.
send (ActorQ yourInbox) message
You can convert Inbox
(read-end) to ActorQ
(write-end) by wrapping
Inbox
by ActorQ
so that you can send a message from an actor to
itself.
myActorHandler :: Inbox YourMessageType -> IO () myActorHandler inbox = do newMessage <- receive inbox doSomethingWith newMessage send (ActorQ inbox) messageToMyself -- Send a message to itself. myActorHandler inbox
Send a message to given ActorQ
. Block while the queue is full.
Try to send a message to given ActorQ
. Return Nothing if the queue is
already full.
tryReceive :: Inbox a -> IO (Maybe a) Source #
Try to receive first message in Inbox
. It returns Nothing if there is no
message available.
:: (a -> Bool) | Predicate to pick a interesting message. |
-> Inbox a | Message queue where interesting message searched for. |
-> IO a |
Perform selective receive from given Inbox
.
receiveSelect
searches given queue for first interesting message
predicated by user supplied function. It applies the predicate to the
queue, returns the first element that satisfy the predicate, and remove the
element from the Inbox.
It blocks until interesting message arrived if no interesting message was found in the queue.
Caution
Use this function with care. It does NOT discard any message unsatisfying predicate. It keeps them in the queue for future receive and the function itself blocks until interesting message arrived. That causes your queue filled up by non-interesting messages. There is no escape hatch.
Consider using tryReceiveSelect
instead.
Caveat
Current implementation has performance caveat. It has O(n) performance characteristic where n is number of messages existing before your interested message appears. It is because this function performs liner scan from the top of the queue every time it is called. It doesn't cache pair of predicates and results you have given before.
Use this function in limited situation only.
:: (a -> Bool) | Predicate to pick a interesting message. |
-> Inbox a | Message queue where interesting message searched for. |
-> IO (Maybe a) |
Try to perform selective receive from given Inbox
.
tryReceiveSelect
searches given queue for first interesting message
predicated by user supplied function. It applies the predicate to the
queue, returns the first element that satisfy the predicate, and remove the
element from the Inbox.
It return Nothing if there is no interesting message found in the queue.
Caveat
Current implementation has performance caveat. It has O(n) performance characteristic where n is number of messages existing before your interested message appears. It is because this function performs liner scan from the top of the queue every time it is called. It doesn't cache pair of predicates and results you have given before.
Use this function in limited situation only.
Actor
Actor is IO action emulating Erlang's actor.
It has a dedicated Inbox
and processes incoming messages until
reaching end state.
Actor is restartable without replacing message queue. When actor's IO action crashed and restarted, the new execution of the IO action continue referring the same message queue. Thus, threads sending messages to the actor can continue using the same write-end of the queue.
newActor
and newBoundedActor
create an actor with new Inbox. It is
the only exposed way to create a new Inbox. This limitation is
intended. It prevents any code other than message handler of the actor
from reading the inbox.
From perspective of outside of actor, user supplies an IO action with
type ActorHandler
to newActor
or newBoundedActor
then user gets IO
action of created actor and write-end of message queue of the actor,
which is ActorQ
type value.
From perspective of inside of actor, in other word, from perspective of user supplied message handler, it has a message queue both read and write side available.
Shared Inbox
You can run created actor multiple time simultaneously with different
thread each. In such case, each actor instances share single Inbox
.
This would be useful to distribute task stream to multiple worker actor
instances, however, keep in mind there is no way to control which
message is routed to what actor.
type ActorHandler message result = Inbox message -> IO result Source #
Type synonym of user supplied message handler working inside actor.
data Actor message result Source #
Actor representation.
Actor | |
|
:: ActorHandler message result | IO action handling received messages. |
-> IO (Actor message result) |
Create a new actor.
Users have to supply a message handler function with ActorHandler
type.
ActorHandler accepts a Inbox
and returns anything.
newActor
creates a new Inbox
, apply user supplied message handler to the
queue, returns reference to write-end of the queue and IO action of the
actor. Because newActor
only returns ActorQ
, caller of newActor
can
only send messages to created actor. Caller cannot receive message from the
queue.
Inbox
, or read-end of the queue, is passed to user supplied message
handler so the handler can receive message to the actor. If the handler
need to send a message to itself, wrap the message queue by ActorQ
constructor then use send
over created ActorQ
. Here is an example.
send (ActorQ yourInbox) message
:: InboxLength | Maximum length of inbox message queue. |
-> ActorHandler message result | IO action handling received messages. |
-> IO (Actor message result) |
Create a new actor with bounded inbox queue.
Supervisable IO action
Monitored action
This package provides facility for supervising IO actions. With types and functions described in this section, you can run IO action with its own thread and receive notification on its termination at another thread with reason of termination. Functions in this section provides guaranteed supervision of your thread.
It looks something similar to bracket
. What distinguishes
from bracket is guaranteed work through entire lifetime of thread.
Use bracket
when you need guaranteed cleanup of resources
acquired within the same thread. It works as you expect. However,
installing callback for thread supervision using bracket (or
finally
or even low level catch
) within a thread
has NO guarantee. There is a little window where asynchronous
exception is thrown after the thread is started but callback is not yet
installed. We will discuss this later in this section.
Notification is delivered via user supplied callback. Helper functions described in this section install your callback to your IO action. Then the callback will be called on termination of the IO action.
Important: Callback is called in terminated thread
Callback is called in terminated thread. You have to use inter-thread communication in order to notify to another thread.
User supplied callback receives ExitReason
and
ThreadId
so that user can determine witch thread
was terminated and why it was terminated. In order to receive those
parameters, user supplied callback must have type signature Monitor
,
which is following.
ExitReason -> ThreadId -> IO ()
Function watch
installs your callback to your plain IO action then
returns monitored action.
Callback can be nested. Use nestWatch
to install another callback to
already monitored action.
Helper functions return IO action with signature MonitoredAction
instead of plain IO ()
. From here to the end of this section it will
be a little technical deep dive for describing why it has such
signature.
The signature of MonitoredAction
is this.
(IO () -> IO ()) -> IO ()
It requires an extra function argument. It is because MonitoredAction
will be invoked with forkIOWithUnmask
.
In order to ensure callback on termination works in any timing, the
callback must be installed under asynchronous exception masked. At the
same time, in order to allow killing the tread from another thread, body
of IO action must be executed under asynchronous exception unmasked.
In order to satisfy both conditions, the IO action and callback must be
called using forkIOWithUnmask
. Typically it looks
like following.
mask_ $ forkIOWithUnmask $ \unmask -> unmask action `finally` callback
The extra function parameter in the signature of MonitoredAction
is
used for accepting the unmask
function which is passed by
forkIOWithUnmask
. Functions defined in this
section help installing callback and converting type to fit to
forkIOWithUnmask
.
type MonitoredAction = (IO () -> IO ()) -> IO () Source #
MonitoredAction
is type synonym of function with callback on termination
installed. Its type signature fits to argument for forkIOWithUnmask
.
data ExitReason Source #
ExitReason
indicates reason of thread termination.
Normal | Thread was normally finished. |
UncaughtException SomeException | A synchronous exception was thrown and it was not caught. This indicates some unhandled error happened inside of the thread handler. |
Killed | An asynchronous exception was thrown. This also happen when the thread was killed by supervisor. |
Instances
Show ExitReason Source # | |
Defined in Control.Concurrent.SupervisorInternal showsPrec :: Int -> ExitReason -> ShowS # show :: ExitReason -> String # showList :: [ExitReason] -> ShowS # |
= ExitReason | Reason of thread termination. |
-> ThreadId | ID of terminated thread. |
-> IO () |
Monitor
is user supplied callback function which is called when monitored thread is terminated.
watch :: Monitor -> IO () -> MonitoredAction Source #
Install Monitor
callback function to simple IO action.
nestWatch :: Monitor -> MonitoredAction -> MonitoredAction Source #
Install another Monitor
callback function to MonitoredAction
.
noWatch :: IO () -> MonitoredAction Source #
Convert simple IO action to MonitoredAction
without installing Monitor
.
Child Specification
ChildSpec
is casting mold of child thread IO action which supervisor
spawns and manages. It is passed to supervisor, then supervisor let it
run with its own thread, monitor it, and restart it if needed.
ChildSpec provides additional attributes to MonitoredAction
for
controlling restart on thread termination. That is Restart
.
Restart
represents restart type concept came from Erlang/OTP. The
value of Restart
defines how restart operation by supervisor is
triggered on termination of the thread. ChildSpec
with Permanent
restart type triggers restart operation regardless its reason of
termination. It triggers restarting even by normal exit. Transient
triggers restarting only when the thread is terminated by exception.
Temporary
never triggers restarting.
Refer to Erlang/OTP for more detail of restart type concept.
newMonitoredChildSpec
creates a new ChildSpec
from a
MonitoredAction
and a restart type value. newChildSpec
is short cut
function creating a ChildSpec
from a plain IO action and a restart
type value. addMonitor
adds another monitor to existing ChildSpec
.
Restart
defines when terminated child thread triggers restart operation by
its supervisor. Restart
only defines when it triggers restart operation.
It does not directly means if the thread will be or will not be restarted.
It is determined by restart strategy of supervisor. For example, a static
Temporary
child never triggers restart on its termination but static
Temporary
child will be restarted if another Permanent
or Transient
thread with common supervisor triggered restart operation and the supervisor
has OneForAll
strategy.
Permanent |
|
Transient |
|
Temporary |
|
ChildSpec
is representation of IO action which can be supervised by
supervisor. Supervisor can run the IO action with separate thread, watch
its termination and restart it based on restart type.
:: Restart | Restart type of resulting |
-> IO () | User supplied IO action which the |
-> ChildSpec |
Create a ChildSpec
from plain IO action.
newMonitoredChildSpec Source #
:: Restart | Restart type of resulting |
-> MonitoredAction | User supplied monitored IO action which the |
-> ChildSpec |
Create a ChildSpec
from MonitoredAction
.
Supervisor
data RestartSensitivity Source #
RestartSensitivity
defines condition how supervisor determines intensive restart is happening. If more than
restartSensitivityIntensity
time of restart is triggered within restartSensitivityPeriod
, supervisor decides
intensive restart is happening and it terminates itself. Default intensity (maximum number of acceptable restart)
is 1. Default period is 5 seconds.
RestartSensitivity | |
|
Instances
Default RestartSensitivity Source # | |
Defined in Control.Concurrent.SupervisorInternal |
data IntenseRestartDetector Source #
IntenseRestartDetector
keeps data used for detecting intense restart. It keeps maxR (maximum restart intensity),
maxT (period of majoring restart intensity) and history of restart with system timestamp in Monotonic
form.
newIntenseRestartDetector :: RestartSensitivity -> IntenseRestartDetector Source #
Create new IntenseRestartDetector with given RestartSensitivity
parameters.
:: IntenseRestartDetector | Intense restart detector containing history of past restart with maxT and maxR |
-> TimeSpec | System timestamp in |
-> (Bool, IntenseRestartDetector) | Returns |
Determine if the last restart results intensive restart. It pushes the last restart timestamp to the DelayedQueue
of restart history held inside of the IntenseRestartDetector then check if the oldest restart record is pushed out
from the queue. If no record was pushed out, there are less number of restarts than limit, so it is not intensive.
If a record was pushed out, it means we had one more restarts than allowed. If the oldest restart and newest
restart happened within allowed time interval, it is intensive.
This function implements pure part of detectIntenseRestartNow
.
detectIntenseRestartNow Source #
:: IntenseRestartDetector | Intense restart detector containing history of past restart with maxT and maxR. |
-> IO (Bool, IntenseRestartDetector) |
Determine if intensive restart is happening now. It is called when restart is triggered by some thread termination.
Restart strategy of supervisor
type SupervisorQueue = ActorQ SupervisorMessage Source #
Type synonym for write-end of supervisor's message queue.
:: Strategy | Restarting strategy of monitored threads. |
-> RestartSensitivity | Restart intensity sensitivity in restart count and period. |
-> [ChildSpec] | List of supervised child specifications. |
-> ActorHandler SupervisorMessage () |
Create a supervisor.
When created supervisor IO action started, it automatically creates child
threads based on given ChildSpec
list and supervise them. After it
created such static children, it listens given SupervisorQueue
. User can
let the supervisor creates dynamic child thread by calling newChild
.
Dynamic child threads created by newChild
are also supervised.
When the supervisor thread is killed or terminated in some reason, all children including static children and dynamic children are all killed.
With OneForOne
restart strategy, when a child thread terminated, it is
restarted based on its restart type given in ChildSpec
. If the terminated
thread has Permanent
restart type, supervisor restarts it regardless its
exit reason. If the terminated thread has Transient
restart type, and
termination reason is other than Normal
(meaning UncaughtException
or
Killed
), it is restarted. If the terminated thread has Temporary
restart type, supervisor does not restart it regardless its exit reason.
Created IO action is designed to run in separate thread from main thread. If you try to run the IO action at main thread without having producer of the supervisor queue you gave, the supervisor will dead lock.
:: CallTimeout | Request timeout in microsecond. |
-> SupervisorQueue | Inbox message queue of the supervisor to ask new thread. |
-> ChildSpec | Child specification of the thread to spawn. |
-> IO (Maybe ThreadId) |
Ask the supervisor to spawn new temporary child thread. Returns ThreadId
of the new child.
State machine
State machine behavior is most essential behavior in this package. It
provides framework for creating IO action of finite state machine
running on its own thread. State machine has single Inbox
, its local
state, and a user supplied message handler. State machine is created
with initial state value, waits for incoming message, passes received
message and current state to user supplied handler, updates state to
returned value from user supplied handler, stops or continue to listen
message queue based on what the handler returned.
To create a new state machine, prepare initial state of your state
machine and define your message handler driving your state machine,
apply newStateMachine
to the initial state and handler. You will get
a ActorHandler
so you can get an actor of the state machine by
applying newActor
to it.
Actor queue action <- newActor $ newStateMachine initialState handler
Or you can use short-cut helper.
Actor queue action <- newStateMachineActor initialState handler
The newStateMachine
returns write-end of message queue for the state
machine and IO action to run. You can run the IO action by
forkIO
or async
, or you can
let supervisor run it.
User supplied message handler must have following type signature.
handler :: (state -> message -> IO (Either result state))
When a message is sent to state machine's queue, it is automatically
received by state machine framework, then the handler is called with
current state and the message. The handler must return either result or
next state. When 'Left result' is returned, the state machine stops and
returned value of the IO action is IO result
. When 'Right state' is
returned, the state machine updates current state with the returned
state and wait for next incoming message.
:: state | Initial state of the state machine. |
-> (state -> message -> IO (Either result state)) | Message handler which processes event and returns result or next state. |
-> ActorHandler message result |
Create a new finite state machine.
The state machine waits for new message at Inbox
then callback user
supplied message handler. The message handler must return Right
with new
state or Left
with final result. When Right
is returned, the state
machine waits for next message. When Left
is returned, the state machine
terminates and returns the result.
newStateMachine
returns an IO action wrapping the state machine described
above. The returned IO action can be executed within an Async
or bare
thread.
Created IO action is designed to run in separate thread from main thread. If you try to run the IO action at main thread without having producer of the message queue you gave, the state machine will dead lock.
newStateMachineActor :: state -> (state -> message -> IO (Either result state)) -> IO (Actor message result) Source #
create an unbound actor of newStateMachine. Short-cut of following.
newActor $ newStateMachine initialState messageHandler
Simple server behavior
Server behavior provides synchronous request-response style communication, a.k.a. ask pattern, with actor. Server behavior allows user to send a request to an actor then wait for response form the actor. This package provides a framework for implementing such actor.
Server behavior in this package is actually a set of helper functions and type synonym to help implementing ask pattern over actor. User need to follow some of rules described below to utilize those helpers.
Define ADT type for messages
First, user need to define an algebraic data type for message to the server in following form.
data myServerCommand = ReqWithoutResp1 | ReqWithoutResp2 Arg1 | ReqWithoutResp3 Arg2 Arg3 | ReqWithResp1 (ServerCallback Result1) | ReqWithResp1 ArgX (ServerCallback Result2) | ReqWithResp2 ArgY ArgZ (ServerCallback Result3)
The rule is this:
- Define an ADT containing all requests.
- If a request doesn't return response, define a value type for the request as usual element of sum type.
- If a request returns a response, put
(ServerCallback ResultType)
at the last argument of the constructor for the request whereResultType
is type of returned value.
ServerCallback
is type synonym of a function type as following.
type ServerCallback a = (a -> IO ())
So real definition of your myServerCommand
is:
data MyServerCommand = ReqWithoutResp1 | ReqWithoutResp2 Arg1 | ReqWithoutResp3 Arg2 Arg3 | ReqWithResp1 (Result1 -> IO ()) | ReqWithResp2 ArgX (Result2 -> IO ()) | ReqWithResp3 ArgY ArgZ (Result3 -> IO ())
Define message handler
Next, user need to define an actor handling the message. In this example, we will use state machine behavior so that we can focus on core message handling part. For simplicity, this example doesn't have internal state and it never finishes.
Define a state machine message handler handling myServerCommand
.
myHandler :: () -> MyServerCommand -> IO (Either () ()) myHandler _ ReqWithoutResp1 = doJob1 $> Right () myHandler _ (ReqWithoutResp2 arg1) = doJob2 arg1 $> Right () myHandler _ (ReqWithoutResp3 arg2 arg3) = doJob3 arg2 arg3 $> Right () myHandler _ (ReqWithResp1 cont1) = (doJob4 >>= cont1) $> Right () myHandler _ (ReqWithResp2 argX cont2) = (doJob5 argX >>= cont2) $> Right () myHandler _ (ReqWithResp3 argY argZ cont3) = (doJob6 argY argZ >>= cont3) $> Right ()
The core idea here is implementing request handler in CPS style. If a request returns a response, the request message comes with callback function (a.k.a. continuation). You can send back response for the request by calling the callback.
Requesting to server
Function call
, callAsync
, and callIgnore
are helper functions to
implement request-response communication with server. They install
callback to message, send the message, returns response to caller. They
receive partially applied server message constructor, apply it to
callback function, then send it to server. The installed callback
handles response from the server. You can use call
like following.
maybeResult1 <- call def myServerActor ReqWithResp1 maybeResult2 <- call def myServerActor $ ReqWithResp2 argX maybeResult3 <- call def myServerActor $ ReqWithResp3 argY argZ
When you send a request without response, use cast
.
cast myServerActor ReqWithoutResp1 cast myServerActor $ ReqWithoutResp2 arg1 cast myServerActor $ ReqWithoutResp3 arg2 arg3
When you send a request with response but ignore it, use callIgnore
.
callIgnore myServerActor ReqWithResp1 callIgnore myServerActor $ ReqWithResp2 argX callIgnore myServerActor $ ReqWithResp3 argY argZ
Generally, ask pattern, or synchronous request-response communication is
not recommended in actor model. It is because synchronous request
blocks entire actor until it receives response or timeout. You can
mitigate the situation by wrapping the synchronous call with async
.
Use callAsync
for such purpose.
newtype CallTimeout Source #
Timeout of call method for server behavior in microseconds. Default is 5 second.
Instances
Default CallTimeout Source # | |
Defined in Control.Concurrent.SupervisorInternal def :: CallTimeout # |
type ServerCallback a = a -> IO () Source #
Type synonym of callback function to obtain return value.
Send an asynchronous request to a server.
:: CallTimeout | Timeout. |
-> ActorQ cmd | Message queue of the target server. |
-> (ServerCallback a -> cmd) | Request to the server without callback supplied. |
-> IO (Maybe a) |
Send an synchronous request to a server and waits for a return value until timeout.
:: CallTimeout | Timeout. |
-> ActorQ cmd | Message queue. |
-> (ServerCallback a -> cmd) | Request to the server without callback supplied. |
-> (Maybe a -> IO b) | callback to process return value of the call. Nothing is given on timeout. |
-> IO (Async b) |
Make an asynchronous call to a server and give result in CPS style. The
return value is delivered to given callback function. It also can fail by
timeout. Calling thread can wait
for a return value from the
callback.
Use this function with care because there is no guaranteed cancellation of
background worker thread other than timeout. Giving infinite timeout (zero)
to the CallTimeout
argument may cause the background thread left to run,
possibly indefinitely.
:: ActorQ cmd | Message queue of the target server. |
-> (ServerCallback a -> cmd) | Request to the server without callback supplied. |
-> IO () |
Send an request to a server but ignore return value.