-- Hoogle documentation, generated by Haddock
-- See Hoogle, http://www.haskell.org/hoogle/
-- | The Cloud Haskell Application Platform
--
-- Modelled after Erlang OTP's gen_server, this framework provides
-- similar facilities for Cloud Haskell, grouping essential practices for
-- client/server development into a set of modules and standards designed
-- to help you build concurrent, distributed applications with relative
-- ease.
@package distributed-process-client-server
@version 0.2.0
-- | This module provides a wrap around a simple Timer that can be
-- started, stopped, reset, cleared, and read. A convenient function is
-- provided for creating a Match expression for the timer.
--
--
--
-- The timers defined in this module are based on a TVar Bool.
-- When the client program is -threaded (i.e.
-- rtsSupportsBoundThreads == True), then the timers are set
-- using registerDelay, which is very efficient and relies only
-- no the RTS IO Manager. When we're not -threaded, we fall back
-- to using Control.Distributed.Process.Extras.Timer to set the
-- TVar, which has much the same effect, but requires us to
-- spawn a process to handle setting the TVar - a process which
-- could theoretically die before setting the variable.
module Control.Distributed.Process.ManagedProcess.Timer
-- | We hold timers in 2 states, each described by a Delay. isActive =
-- isJust . mtSignal the TimerRef is optional since we only use the Timer
-- module from extras when we're unable to registerDelay (i.e. not
-- running under -threaded)
data Timer
-- | A key for storing timers in prioritised process backing state.
type TimerKey = Int
-- | Creates a default Timer which is inactive.
delayTimer :: Delay -> Timer
-- | Starts a Timer Will use the GHC registerDelay API if
-- rtsSupportsBoundThreads == True
startTimer :: Delay -> Process Timer
-- | Stops a previously started Timer. Has no effect if the
-- Timer is inactive.
stopTimer :: Timer -> Process Timer
-- | Clears and restarts a Timer.
resetTimer :: Timer -> Delay -> Process Timer
-- | Clears/cancels a running timer. Has no effect if the Timer is
-- inactive.
clearTimer :: Maybe TimerRef -> Process ()
-- | Creates a Match for a given timer, for use with Cloud
-- Haskell's messaging primitives for selective receives.
matchTimeout :: Timer -> [Match (Either TimedOut Message)]
-- | Create a match expression for a given Timer. When the timer
-- expires (i.e. the "TVar Bool" is set to True), the
-- Match will return Yield i, where i is the
-- given TimerKey.
matchKey :: TimerKey -> Timer -> [Match (Either TimedOut Message)]
-- | As "matchKey", but instead of a returning Yield i, the
-- generated Match handler evaluates the first argument - and
-- expression from TimerKey to Process Message - to
-- determine its result.
matchRun :: (TimerKey -> Process Message) -> TimerKey -> Timer -> [Match Message]
-- | True if a Timer is currently active.
isActive :: Timer -> Bool
-- | Reads a given TVar Bool for a timer, and returns STM
-- TimedOut once the variable is set to true. Will retry in
-- the meanwhile.
readTimer :: TVar Bool -> STM TimedOut
-- | Used during STM reads on Timers and to implement blocking. Since
-- timers can be associated with a TimerKey, the second
-- constructor for this type yields a key indicating whic Timer it
-- refers to. Note that the user is responsible for establishing and
-- maintaining the mapping between Timers and their keys.
data TimedOut
TimedOut :: TimedOut
Yield :: TimerKey -> TimedOut
instance GHC.Generics.Generic Control.Distributed.Process.ManagedProcess.Timer.TimedOut
instance GHC.Show.Show Control.Distributed.Process.ManagedProcess.Timer.TimedOut
instance GHC.Classes.Eq Control.Distributed.Process.ManagedProcess.Timer.TimedOut
instance Data.Binary.Class.Binary Control.Distributed.Process.ManagedProcess.Timer.TimedOut
-- | The Server Portion of the Managed Process API.
module Control.Distributed.Process.ManagedProcess.Server
-- | Creates a Condition from a function that takes a process state
-- a and an input message b and returns a Bool
-- indicating whether the associated handler should run.
condition :: forall a b. (Serializable a, Serializable b) => (a -> b -> Bool) -> Condition a b
-- | Create a Condition from a function that takes a process state
-- a and returns a Bool indicating whether the associated
-- handler should run.
state :: forall s m. (Serializable m) => (s -> Bool) -> Condition s m
-- | Creates a Condition from a function that takes an input message
-- m and returns a Bool indicating whether the associated
-- handler should run.
input :: forall s m. (Serializable m) => (m -> Bool) -> Condition s m
-- | Instructs the process to send a reply and continue running.
reply :: (Serializable r) => r -> s -> Reply r s
-- | Instructs the process to send a reply and evaluate the
-- ProcessAction.
replyWith :: (Serializable r) => r -> ProcessAction s -> Reply r s
-- | Instructs the process to skip sending a reply and evaluate a
-- ProcessAction
noReply :: (Serializable r) => ProcessAction s -> Reply r s
-- | Instructs the process to continue running and receiving messages.
continue :: s -> Action s
-- | Instructs the process loop to wait for incoming messages until
-- Delay is exceeded. If no messages are handled during this
-- period, the timeout handler will be called. Note that this
-- alters the process timeout permanently such that the given
-- Delay will remain in use until changed.
--
-- Note that timeoutAfter NoDelay will cause the timeout handler
-- to execute immediately if no messages are present in the process'
-- mailbox.
timeoutAfter :: Delay -> s -> Action s
-- | Instructs the process to hibernate for the given
-- TimeInterval. Note that no messages will be removed from the
-- mailbox until after hibernation has ceased. This is equivalent to
-- calling threadDelay.
hibernate :: TimeInterval -> s -> Process (ProcessAction s)
-- | Instructs the process to terminate, giving the supplied reason. If a
-- valid shutdownHandler is installed, it will be called with the
-- ExitReason returned from this call, along with the process
-- state.
stop :: ExitReason -> Action s
-- | As stop, but provides an updated state for the shutdown
-- handler.
stopWith :: s -> ExitReason -> Action s
-- | Sends a reply explicitly to a caller.
--
--
-- replyTo = sendTo
--
replyTo :: (Serializable m) => CallRef m -> m -> Process ()
-- | Sends a reply to a SendPort (for use in handleRpcChan et
-- al).
--
--
-- replyChan = sendChan
--
replyChan :: (Serializable m) => SendPort m -> m -> Process ()
-- | Reject the message we're currently handling.
reject :: forall r s. s -> String -> Reply r s
-- | Reject the message we're currently handling, giving an explicit
-- reason.
rejectWith :: forall r m s. (Show r) => s -> r -> Reply m s
-- | Continue without giving a reply to the caller - equivalent to
-- continue, but usable in a callback passed to the
-- handleCall family of functions.
noReply_ :: forall s r. (Serializable r) => s -> Reply r s
-- | Halt process execution during a call handler, without paying any
-- attention to the expected return type.
haltNoReply_ :: Serializable r => ExitReason -> Reply r s
-- | Version of continue that can be used in handlers that ignore
-- process state.
continue_ :: s -> Action s
-- | Version of timeoutAfter that can be used in handlers that
-- ignore process state.
--
--
-- action (\(TimeoutPlease duration) -> timeoutAfter_ duration)
--
timeoutAfter_ :: StatelessHandler s Delay
-- | Version of hibernate that can be used in handlers that ignore
-- process state.
--
--
-- action (\(HibernatePlease delay) -> hibernate_ delay)
--
hibernate_ :: StatelessHandler s TimeInterval
-- | Version of stop that can be used in handlers that ignore
-- process state.
--
--
-- action (\ClientError -> stop_ ExitNormal)
--
stop_ :: StatelessHandler s ExitReason
-- | Constructs a call handler from a function in the
-- Process monad. > handleCall = handleCallIf (const True)
handleCall :: (Serializable a, Serializable b) => CallHandler s a b -> Dispatcher s
-- | Constructs a call handler from an ordinary function in the
-- Process monad. Given a function f :: (s -> a ->
-- Process (ProcessReply b s)), the expression handleCall f
-- will yield a Dispatcher for inclusion in a Behaviour
-- specification for the GenProcess. Messages are only dispatched
-- to the handler if the supplied condition evaluates to True.
handleCallIf :: forall s a b. (Serializable a, Serializable b) => Condition s a -> CallHandler s a b -> Dispatcher s
-- | As handleCall but passes the CallRef to the handler
-- function. This can be useful if you wish to reply later to the
-- caller by, e.g., spawning a process to do some work and have it
-- replyTo caller response out of band. In this case the
-- callback can pass the CallRef to the worker (or stash it away
-- itself) and return noReply.
handleCallFrom :: forall s a b. (Serializable a, Serializable b) => DeferredCallHandler s a b -> Dispatcher s
-- | As handleCallFrom but only runs the handler if the supplied
-- Condition evaluates to True.
handleCallFromIf :: forall s a b. (Serializable a, Serializable b) => Condition s a -> DeferredCallHandler s a b -> Dispatcher s
-- | Creates a handler for a typed channel RPC style interaction.
-- The handler takes a SendPort b to reply to, the initial input
-- and evaluates to a ProcessAction. It is the handler code's
-- responsibility to send the reply to the SendPort.
handleRpcChan :: forall s a b. (Serializable a, Serializable b) => ChannelHandler s a b -> Dispatcher s
-- | As handleRpcChan, but only evaluates the handler if the
-- supplied condition is met.
handleRpcChanIf :: forall s a b. (Serializable a, Serializable b) => Condition s a -> ChannelHandler s a b -> Dispatcher s
-- | Constructs a cast handler from an ordinary function in the
-- Process monad. > handleCast = handleCastIf (const True)
handleCast :: (Serializable a) => CastHandler s a -> Dispatcher s
-- | Constructs a cast handler from an ordinary function in the
-- Process monad. Given a function f :: (s -> a ->
-- Process (ProcessAction s)), the expression handleCall f
-- will yield a Dispatcher for inclusion in a Behaviour
-- specification for the GenProcess.
handleCastIf :: forall s a. (Serializable a) => Condition s a -> CastHandler s a -> Dispatcher s
-- | Creates a generic input handler (i.e., for received messages that are
-- not sent using the cast or call APIs) from an
-- ordinary function in the Process monad.
handleInfo :: forall s a. (Serializable a) => ActionHandler s a -> DeferredDispatcher s
-- | Handle completely raw input messages.
handleRaw :: forall s. ActionHandler s Message -> DeferredDispatcher s
-- | Constructs a handler for both call and cast messages.
-- handleDispatch = handleDispatchIf (const True)
handleDispatch :: forall s a. (Serializable a) => ActionHandler s a -> Dispatcher s
-- | Constructs a handler for both call and cast messages.
-- Messages are only dispatched to the handler if the supplied condition
-- evaluates to True. Handlers defined in this way have no
-- access to the call context (if one exists) and cannot therefore reply
-- to calls.
handleDispatchIf :: forall s a. (Serializable a) => Condition s a -> ActionHandler s a -> Dispatcher s
-- | Creates an exit handler scoped to the execution of any and all
-- the registered call, cast and info handlers for the process.
handleExit :: forall s a. (Serializable a) => (ProcessId -> ActionHandler s a) -> ExitSignalDispatcher s
-- | Conditional version of handleExit
handleExitIf :: forall s a. (Serializable a) => (s -> a -> Bool) -> (ProcessId -> ActionHandler s a) -> ExitSignalDispatcher s
-- | Constructs an action handler. Like handleDispatch this
-- can handle both cast and call messages, but you
-- won't know which you're dealing with. This can be useful where certain
-- inputs require a definite action, such as stopping the server, without
-- concern for the state (e.g., when stopping we need only decide to
-- stop, as the terminate handler can deal with state cleanup etc). For
-- example:
--
--
-- action (MyCriticalSignal -> stop_ ExitNormal)
--
action :: forall s a. (Serializable a) => StatelessHandler s a -> Dispatcher s
-- | Constructs a call handler from a function in the
-- Process monad. The handler expression returns the reply, and
-- the action will be set to continue.
--
--
-- handleCall_ = handleCallIf_ $ input (const True)
--
handleCall_ :: (Serializable a, Serializable b) => (a -> Process b) -> Dispatcher s
-- | Constructs a call handler from an ordinary function in the
-- Process monad. This variant ignores the state argument present
-- in handleCall and handleCallIf and is therefore useful
-- in a stateless server. Messges are only dispatched to the handler if
-- the supplied condition evaluates to True
--
-- See handleCall
handleCallIf_ :: forall s a b. (Serializable a, Serializable b) => Condition s a -> (a -> Process b) -> Dispatcher s
-- | A variant of handleCallFrom_ that ignores the state argument.
handleCallFrom_ :: forall s a b. (Serializable a, Serializable b) => StatelessCallHandler s a b -> Dispatcher s
-- | A variant of handleCallFromIf that ignores the state argument.
handleCallFromIf_ :: forall s a b. (Serializable a, Serializable b) => Condition s a -> StatelessCallHandler s a b -> Dispatcher s
-- | A variant of handleRpcChan that ignores the state argument.
handleRpcChan_ :: forall s a b. (Serializable a, Serializable b) => StatelessChannelHandler s a b -> Dispatcher s
-- | A variant of handleRpcChanIf that ignores the state argument.
handleRpcChanIf_ :: forall s a b. (Serializable a, Serializable b) => Condition s a -> StatelessChannelHandler s a b -> Dispatcher s
-- | Version of handleCast that ignores the server state.
handleCast_ :: (Serializable a) => StatelessHandler s a -> Dispatcher s
-- | Version of handleCastIf that ignores the server state.
handleCastIf_ :: forall s a. (Serializable a) => Condition s a -> StatelessHandler s a -> Dispatcher s
-- | Constructs a control channel handler from a function in the
-- Process monad. The handler expression returns no reply, and the
-- control message is treated in the same fashion as a
-- cast.
--
--
-- handleControlChan = handleControlChanIf $ input (const True)
--
handleControlChan :: forall s a. (Serializable a) => ControlChannel a -> ActionHandler s a -> ExternDispatcher s
-- | Version of handleControlChan that ignores the server state.
handleControlChan_ :: forall s a. (Serializable a) => ControlChannel a -> StatelessHandler s a -> ExternDispatcher s
-- | Creates a generic input handler for STM actions, from an
-- ordinary function in the Process monad. The STM a
-- action tells the server how to read inputs, which when presented are
-- passed to the handler in the same manner as handleInfo
-- messages would be.
--
-- Note that messages sent to the server's mailbox will never match this
-- handler, only data arriving via the STM a action will.
--
-- Notably, this kind of handler can be used to pass non-serialisable
-- data to a server process. In such situations, the programmer is
-- responsible for managing the underlying STM infrastructure,
-- and the server simply composes the STM a action with the
-- other reads on its mailbox, using the underlying matchSTM API
-- from distributed-process.
--
-- NB: this function cannot be used with a prioristised process
-- definition.
handleExternal :: forall s a. (Serializable a) => STM a -> ActionHandler s a -> ExternDispatcher s
-- | Version of handleExternal that ignores state.
handleExternal_ :: forall s a. (Serializable a) => STM a -> StatelessHandler s a -> ExternDispatcher s
-- | Handle call style API interactions using arbitrary STM
-- actions.
--
-- The usual CallHandler is preceded by an stm action that, when
-- evaluated, yields a value, and a second expression that is used to
-- send a reply back to the caller. The corrolary client API is
-- callSTM.
handleCallExternal :: forall s r w. (Serializable r) => STM r -> (w -> STM ()) -> CallHandler s r w -> ExternDispatcher s
-- | A safe variant of the Server Portion of the Managed
-- Process API. Most of these operations have the same names as
-- similar operations in the impure Server module (re-exported
-- by the primary API in ManagedProcess). To remove the
-- ambiguity, some combination of either qualification and/or the
-- hiding clause will be required.
--
--
-- - Restricted Server Callbacks
--
--
-- The idea behind this module is to provide safe callbacks, i.e.,
-- server code that is free from side effects. This safety is enforced by
-- the type system via the RestrictedProcess monad. A StateT
-- interface is provided for code running in the
-- RestrictedProcess monad, so that server side state can be
-- managed safely without resorting to IO (or code running in the
-- Process monad).
module Control.Distributed.Process.ManagedProcess.Server.Restricted
-- | Restricted (i.e., pure, free from side effects) execution environment
-- for callcastinfo handlers to execute in.
data RestrictedProcess s a
-- | The result of a call handler's execution.
data Result a
-- | reply with the given term
Reply :: a -> Result a
-- | reply with the given term and enter timeout
Timeout :: Delay -> a -> Result a
-- | reply with the given term and hibernate
Hibernate :: TimeInterval -> a -> Result a
-- | stop the process with the given reason
Stop :: ExitReason -> Result a
-- | The result of a safe cast handler's execution.
data RestrictedAction
-- | continue executing
RestrictedContinue :: RestrictedAction
-- | timeout if no messages are received
RestrictedTimeout :: Delay -> RestrictedAction
-- | hibernate (i.e., sleep)
RestrictedHibernate :: TimeInterval -> RestrictedAction
-- | stop/terminate the server process
RestrictedStop :: ExitReason -> RestrictedAction
-- | A version of
-- "Control.Distributed.Process.ManagedProcess.Server.handleCall" that
-- takes a handler which executes in RestrictedProcess.
handleCall :: forall s a b. (Serializable a, Serializable b) => (a -> RestrictedProcess s (Result b)) -> Dispatcher s
-- | A version of
-- "Control.Distributed.Process.ManagedProcess.Server.handleCallIf" that
-- takes a handler which executes in RestrictedProcess.
handleCallIf :: forall s a b. (Serializable a, Serializable b) => Condition s a -> (a -> RestrictedProcess s (Result b)) -> Dispatcher s
-- | A version of
-- "Control.Distributed.Process.ManagedProcess.Server.handleCast" that
-- takes a handler which executes in RestrictedProcess.
handleCast :: forall s a. (Serializable a) => (a -> RestrictedProcess s RestrictedAction) -> Dispatcher s
-- | A version of
-- "Control.Distributed.Process.ManagedProcess.Server.handleCastIf" that
-- takes a handler which executes in RestrictedProcess.
handleCastIf :: forall s a. (Serializable a) => Condition s a -> (a -> RestrictedProcess s RestrictedAction) -> Dispatcher s
-- | A version of
-- "Control.Distributed.Process.ManagedProcess.Server.handleInfo" that
-- takes a handler which executes in RestrictedProcess.
handleInfo :: forall s a. (Serializable a) => (a -> RestrictedProcess s RestrictedAction) -> DeferredDispatcher s
-- | Handle exit signals
handleExit :: forall s a. (Serializable a) => (a -> RestrictedProcess s RestrictedAction) -> ExitSignalDispatcher s
-- | Handle timeouts
handleTimeout :: forall s. (Delay -> RestrictedProcess s RestrictedAction) -> TimeoutHandler s
-- | Put a new process state state
putState :: s -> RestrictedProcess s ()
-- | Get the current process state
getState :: RestrictedProcess s s
-- | Apply the given expression to the current process state
modifyState :: (s -> s) -> RestrictedProcess s ()
-- | Instructs the process to send a reply and continue running.
reply :: forall s r. (Serializable r) => r -> RestrictedProcess s (Result r)
-- | Continue without giving a reply to the caller - equivalent to
-- continue, but usable in a callback passed to the
-- handleCall family of functions.
noReply :: forall s r. (Serializable r) => Result r -> RestrictedProcess s (Result r)
-- | Halt process execution during a call handler, without paying any
-- attention to the expected return type.
haltNoReply :: forall s r. (Serializable r) => ExitReason -> RestrictedProcess s (Result r)
-- | Instructs the process to continue running and receiving messages.
continue :: forall s. RestrictedProcess s RestrictedAction
-- | Instructs the process loop to wait for incoming messages until
-- Delay is exceeded. If no messages are handled during this
-- period, the timeout handler will be called. Note that this
-- alters the process timeout permanently such that the given
-- Delay will remain in use until changed.
timeoutAfter :: forall s. Delay -> RestrictedProcess s RestrictedAction
-- | Instructs the process to hibernate for the given
-- TimeInterval. Note that no messages will be removed from the
-- mailbox until after hibernation has ceased. This is equivalent to
-- evaluating liftIO . threadDelay.
hibernate :: forall s. TimeInterval -> RestrictedProcess s RestrictedAction
-- | Instructs the process to terminate, giving the supplied reason. If a
-- valid shutdownHandler is installed, it will be called with the
-- ExitReason returned from this call, along with the process
-- state.
stop :: forall s. ExitReason -> RestrictedProcess s RestrictedAction
-- | Log a trace message using the underlying Process's say
say :: String -> RestrictedProcess s ()
instance GHC.Base.Applicative (Control.Distributed.Process.ManagedProcess.Server.Restricted.RestrictedProcess s)
instance Control.Monad.IO.Class.MonadIO (Control.Distributed.Process.ManagedProcess.Server.Restricted.RestrictedProcess s)
instance Control.Monad.State.Class.MonadState s (Control.Distributed.Process.ManagedProcess.Server.Restricted.RestrictedProcess s)
instance GHC.Base.Monad (Control.Distributed.Process.ManagedProcess.Server.Restricted.RestrictedProcess s)
instance GHC.Base.Functor (Control.Distributed.Process.ManagedProcess.Server.Restricted.RestrictedProcess s)
-- | Unsafe variant of the Managed Process Client API. This module
-- implements the client portion of a Managed Process using the unsafe
-- variants of cloud haskell's messaging primitives. It relies on the
-- -extras implementation of UnsafePrimitives, which forces
-- evaluation for types that provide an NFData instance. Direct
-- use of the underlying unsafe primitives (from the distributed-process
-- library) without NFData instances is unsupported.
--
-- IMPORTANT NOTE: As per the platform documentation, it is not possible
-- to guarantee that an NFData instance will force
-- evaluation in the same way that a Binary instance would (when
-- encoding to a byte string). Please read the unsafe primitives
-- documentation carefully and make sure you know what you're doing. You
-- have been warned.
--
-- See Control.Distributed.Process.Extras. See
-- Control.Distributed.Process.Extras.UnsafePrimitives. See
-- Control.Distributed.Process.UnsafePrimitives.
module Control.Distributed.Process.ManagedProcess.UnsafeClient
-- | Send a control message over a ControlPort. This version of
-- shutdown uses unsafe primitives.
sendControlMessage :: Serializable m => ControlPort m -> m -> Process ()
-- | Send a signal instructing the process to terminate. This version of
-- shutdown uses unsafe primitives.
shutdown :: ProcessId -> Process ()
-- | Make a synchronous call - uses unsafe primitives.
call :: forall s a b. (Addressable s, NFSerializable a, NFSerializable b) => s -> a -> Process b
-- | Safe version of call that returns information about the error
-- if the operation fails - uses unsafe primitives.
safeCall :: forall s a b. (Addressable s, NFSerializable a, NFSerializable b) => s -> a -> Process (Either ExitReason b)
-- | Version of safeCall that returns Nothing if the
-- operation fails. Uses unsafe primitives.
tryCall :: forall s a b. (Addressable s, NFSerializable a, NFSerializable b) => s -> a -> Process (Maybe b)
-- | Make a synchronous call, but timeout and return Nothing if a
-- reply is not received within the specified time interval - uses
-- unsafe primitives.
callTimeout :: forall s a b. (Addressable s, NFSerializable a, NFSerializable b) => s -> a -> TimeInterval -> Process (Maybe b)
-- | Block for TimeInterval waiting for any matching
-- CallResponse
flushPendingCalls :: forall b. (NFSerializable b) => TimeInterval -> (b -> Process b) -> Process (Maybe b)
-- | Invokes call out of band, and returns an "async handle."
-- Uses unsafe primitives.
callAsync :: forall s a b. (Addressable s, NFSerializable a, NFSerializable b) => s -> a -> Process (Async b)
-- | Sends a cast message to the server identified by
-- server - uses unsafe primitives.
cast :: forall a m. (Addressable a, NFSerializable m) => a -> m -> Process ()
-- | Sends a channel message to the server and returns a
-- ReceivePort - uses unsafe primitives.
callChan :: forall s a b. (Addressable s, NFSerializable a, NFSerializable b) => s -> a -> Process (ReceivePort b)
-- | A synchronous version of callChan.
syncCallChan :: forall s a b. (Addressable s, NFSerializable a, NFSerializable b) => s -> a -> Process b
-- | A safe version of syncCallChan, which returns Left
-- ExitReason if the call fails.
syncSafeCallChan :: forall s a b. (Addressable s, NFSerializable a, NFSerializable b) => s -> a -> Process (Either ExitReason b)
-- | The Prioritised Server portion of the Managed Process API.
module Control.Distributed.Process.ManagedProcess.Server.Priority
-- | Prioritise a call handler
prioritiseCall :: forall s a b. (Serializable a, Serializable b) => (s -> a -> Priority b) -> DispatchPriority s
-- | Prioritise a call handler, ignoring the server's state
prioritiseCall_ :: forall s a b. (Serializable a, Serializable b) => (a -> Priority b) -> DispatchPriority s
-- | Prioritise a cast handler
prioritiseCast :: forall s a. (Serializable a) => (s -> a -> Priority ()) -> DispatchPriority s
-- | Prioritise a cast handler, ignoring the server's state
prioritiseCast_ :: forall s a. (Serializable a) => (a -> Priority ()) -> DispatchPriority s
-- | Prioritise an info handler
prioritiseInfo :: forall s a. (Serializable a) => (s -> a -> Priority ()) -> DispatchPriority s
-- | Prioritise an info handler, ignoring the server's state
prioritiseInfo_ :: forall s a. (Serializable a) => (a -> Priority ()) -> DispatchPriority s
-- | Sets an explicit priority from 1..100. Values > 100 are rounded to
-- 100, and values < 1 are set to 0.
setPriority :: Int -> Priority m
-- | Create a filter from a FilterHandler.
check :: forall s. FilterHandler s -> DispatchFilter s
-- | A raw filter (targetting raw messages).
raw :: forall s. (s -> Message -> Process Bool) -> (s -> Message -> Process (Maybe (Filter s))) -> FilterHandler s
-- | A raw filter that ignores the server state in its condition
-- expression.
raw_ :: forall s. (Message -> Process Bool) -> (s -> Message -> Process (Maybe (Filter s))) -> FilterHandler s
-- | An API filter (targetting call, cast, and chan
-- messages).
api :: forall s m b. (Serializable m, Serializable b) => (s -> m -> Process Bool) -> (s -> Message m b -> Process (Filter s)) -> FilterHandler s
-- | An API filter that ignores the server state in its condition
-- expression.
api_ :: forall m b s. (Serializable m, Serializable b) => (m -> Process Bool) -> (s -> Message m b -> Process (Filter s)) -> FilterHandler s
-- | An info filter (targetting info messages of a specific type)
info :: forall s m. (Serializable m) => (s -> m -> Process Bool) -> (s -> m -> Process (Filter s)) -> FilterHandler s
-- | An info filter that ignores the server state in its condition
-- expression.
info_ :: forall s m. (Serializable m) => (m -> Process Bool) -> (s -> m -> Process (Filter s)) -> FilterHandler s
-- | Refuse messages for which the given expression evaluates to
-- True.
refuse :: forall s m. (Serializable m) => (m -> Bool) -> DispatchFilter s
-- | Create a filter expression that will reject all messages of a specific
-- type.
reject :: forall s m r. (Show r) => r -> s -> m -> Process (Filter s)
-- | A version of reject that deals with API messages (i.e.
-- call, cast, etc) and in the case of a call
-- interaction, will reject the messages and reply to the sender
-- accordingly (with CallRejected).
rejectApi :: forall s m b r. (Show r, Serializable m, Serializable b) => r -> s -> Message m b -> Process (Filter s)
-- | Modify the server state every time a message is recieved.
store :: (s -> s) -> DispatchFilter s
-- | Motify the server state when messages of a certain type arrive...
storeM :: forall s m. (Serializable m) => (s -> m -> Process s) -> DispatchFilter s
-- | Create a filter expression that will crash (i.e. stop) the server.
crash :: forall s. s -> ExitReason -> Process (Filter s)
-- | Ensure that the server state is consistent with the given expression
-- each time a message arrives/is processed. If the expression evaluates
-- to True then the filter will evaluate to FilterOk,
-- otherwise FilterStop (which will cause the server loop to stop
-- with ExitOther filterFail).
ensure :: forall s. (s -> Bool) -> DispatchFilter s
-- | As ensure but runs in the Process monad, and matches
-- only inputs of type m.
ensureM :: forall s m. (Serializable m) => (s -> m -> Process Bool) -> DispatchFilter s
-- | Given as the result of evaluating a DispatchFilter. This type
-- is intended for internal use. For an API for working with filters, see
-- Control.Distributed.Process.ManagedProcess.Priority.
data Filter s
-- | Provides dispatch from a variety of inputs to a typed filter handler.
data DispatchFilter s
-- | Message type used internally by the call, cast, and rpcChan
-- APIs.
data Message a b
-- | Evaluate any matching info handler with the supplied datum
-- after waiting for at least TimeInterval. The process state
-- (for the resulting Action s) is also given and the process
-- loop will go on as per Server.continue.
--
-- Informally, evaluating this expression (such that the Action
-- is given as the result of a handler or filter) will ensure that the
-- supplied message (datum) is availble for processing no sooner than
-- TimeInterval.
--
-- Currently, this expression creates an Action that triggers
-- immediate evaluation in the process loop before continuing with the
-- given state. The process loop stores a user timeout for the
-- given time interval, which is trigerred like a wait/drain timeout.
-- This implementation is subject to change.
evalAfter :: forall s m. (Serializable m) => TimeInterval -> m -> s -> Action s
-- | The current (user supplied) timeout.
currentTimeout :: GenProcess s Delay
-- | Evaluates to the user defined state for the currently executing server
-- loop.
processState :: GenProcess s s
-- | The ProcessDefinition for the current loop.
processDefinition :: GenProcess s (ProcessDefinition s)
-- | The list of filters for the current loop.
processFilters :: GenProcess s ([DispatchFilter s])
-- | Evaluates to the UnhandledMessagePolicy for the current loop.
processUnhandledMsgPolicy :: GenProcess s UnhandledMessagePolicy
-- | Set the user timeout applied whilst a prioritised process loop is in a
-- blocking receive.
setUserTimeout :: Delay -> GenProcess s ()
-- | Set the current process state.
setProcessState :: s -> GenProcess s ()
-- | StateT based monad for prioritised process loops.
data GenProcess s a
-- | Peek at the next available message in the internal priority queue,
-- without removing it.
peek :: GenProcess s (Maybe Message)
-- | Push a message to the head of the internal priority queue.
push :: forall s. Message -> GenProcess s ()
-- | Add a user timer, bound to the given datum.
addUserTimer :: Timer -> Message -> GenProcess s TimerKey
instance GHC.Show.Show Control.Distributed.Process.ManagedProcess.Server.Priority.RejectedByServer
-- | The Client Portion of the Managed Process API.
module Control.Distributed.Process.ManagedProcess.Client
-- | Send a control message over a ControlPort.
sendControlMessage :: Serializable m => ControlPort m -> m -> Process ()
-- | 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.
shutdown :: ProcessId -> Process ()
-- | 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!
call :: forall s a b. (Addressable s, Serializable a, Serializable b) => s -> a -> Process b
-- | 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.
safeCall :: forall s a b. (Addressable s, Serializable a, Serializable b) => s -> a -> Process (Either ExitReason b)
-- | 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).
tryCall :: forall s a b. (Addressable s, Serializable a, Serializable b) => s -> a -> Process (Maybe b)
-- | 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.
callTimeout :: forall s a b. (Addressable s, Serializable a, Serializable b) => s -> a -> TimeInterval -> Process (Maybe b)
-- | Attempt to flush out any pending call responses.
flushPendingCalls :: forall b. (Serializable b) => TimeInterval -> (b -> Process b) -> Process (Maybe b)
-- | Invokes call out of band, and returns an async
-- handle.
callAsync :: forall s a b. (Addressable s, Serializable a, Serializable b) => s -> a -> Process (Async b)
-- | Sends a cast message to the server identified by
-- server. The server will not send a response. Like Cloud
-- Haskell's send primitive, cast is fully asynchronous and
-- never fails - therefore casting to a non-existent (e.g.,
-- dead) server process will not generate an error.
cast :: forall a m. (Addressable a, Serializable m) => a -> m -> Process ()
-- | 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).
callChan :: forall s a b. (Addressable s, Serializable a, Serializable b) => s -> a -> Process (ReceivePort b)
-- | A synchronous version of callChan.
syncCallChan :: forall s a b. (Addressable s, Serializable a, Serializable b) => s -> a -> Process b
-- | A safe version of syncCallChan, which returns Left
-- ExitReason if the call fails.
syncSafeCallChan :: forall s a b. (Addressable s, Serializable a, Serializable b) => s -> a -> Process (Either ExitReason b)
-- | 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.
callSTM :: forall s a b. (Addressable s) => s -> (a -> STM ()) -> STM b -> a -> Process (Either ExitReason b)
-- | This module provides a high(er) level API for building complex
-- Process implementations by abstracting out the management of
-- the process' mailbox, reply/response handling, timeouts, process
-- hiberation, error handling and shutdown/stop procedures. It is
-- modelled along similar lines to OTP's gen_server API -
-- http://www.erlang.org/doc/man/gen_server.html.
--
-- In particular, a managed process will interoperate cleanly with
-- the supervisor API in distributed-process-supervision.
--
--
-- - API Overview For The Impatient
--
--
-- Once started, a managed process will consume messages from its
-- mailbox and pass them on to user defined handlers based on the
-- types received (mapped to those accepted by the handlers) and
-- optionally by also evaluating user supplied predicates to determine
-- which handler(s) should run. Each handler returns a
-- ProcessAction which specifies how we should proceed. If none of
-- the handlers is able to process a message (because their types are
-- incompatible), then the unhandledMessagePolicy will be applied.
--
-- The ProcessAction type defines the ways in which our process
-- can respond to its inputs, whether by continuing to read incoming
-- messages, setting an optional timeout, sleeping for a while, or
-- stopping. The optional timeout behaves a little differently to the
-- other process actions: If no messages are received within the
-- specified time span, a user defined timeoutHandler will be
-- called in order to determine the next action.
--
-- The ProcessDefinition type also defines a
-- shutdownHandler, which is called whenever the process exits,
-- whether because a callback has returned stop as the next
-- action, or as the result of unhandled exit signal or similar
-- asynchronous exceptions thrown in (or to) the process itself.
--
-- The handlers are split into groups: apiHandlers,
-- infoHandlers, and extHandlers.
--
--
--
-- Use serve for a process that sits reading its mailbox and
-- generally behaves as you'd expect. Use pserve and
-- PrioritisedProcessDefinition for a server that manages its
-- mailbox more comprehensively and handles errors a bit differently.
-- Both use the same client API.
--
-- DO NOT mask in handler code, unless you can guarantee it won't be long
-- running and absolutely won't block kill signals from a supervisor.
--
-- Do look at the various API offerings, as there are several, at
-- different levels of abstraction.
--
--
-- - Managed Process Mailboxes
--
--
-- Managed processes come in two flavours, with different runtime
-- characteristics and (to some extent) semantics. These flavours are
-- differentiated by the way in which they handle the server process
-- mailbox - all client interactions remain the same.
--
-- The vanilla managed process mailbox, provided by the
-- serve API, is roughly akin to a tail recursive listen
-- function that calls a list of passed in matchers. We might naively
-- implement it roughly like this:
--
--
-- loop :: stateT -> [(stateT -> Message -> Maybe stateT)] -> Process ()
-- loop state handlers = do
-- st2 <- receiveWait $ map (\d -> handleMessage (d state)) handlers
-- case st2 of
-- Nothing -> {- we're done serving -} return ()
-- Just s2 -> loop s2 handlers
--
--
-- Obviously all the details have been ellided, but this is the essential
-- premise behind a managed process loop. The process keeps
-- reading from its mailbox indefinitely, until either a handler
-- instructs it to stop, or an asynchronous exception (or exit signal -
-- in the form of an async ProcessExitException) terminates it.
-- This kind of mailbox has fairly intuitive runtime characteristics
-- compared to a plain server process (i.e. one implemented
-- without the use of this library): messages will pile up in its mailbox
-- whilst handlers are running, and each handler will be checked against
-- the mailbox based on the type of messages it recognises. We can
-- potentially end up scanning a very large mailbox trying to match each
-- handler, which can be a performance bottleneck depending on expected
-- traffic patterns.
--
-- For most simple server processes, this technique works well and is
-- easy to reason about a use. See the sections on error and exit
-- handling later on for more details about serve based managed
-- processes.
--
--
-- - Prioritised Mailboxes
--
--
-- A prioritised mailbox serves two purposes. The first of these is to
-- allow a managed process author to specify that certain classes of
-- message should be prioritised by the server loop. This is achieved by
-- draining the real process mailbox into an internal priority
-- queue, and running the server's handlers repeatedly over its contents,
-- which are dequeued in priority order. The obvious consequence of this
-- approach leads to the second purpose (or the accidental side effect,
-- depending on your point of view) of a prioritised mailbox, which is
-- that we avoid scanning a large mailbox when searching for messages
-- that match the handlers we anticipate running most frequently (or
-- those messages that we deem most important).
--
-- There are several consequences to this approach. One is that we do
-- quite a bit more work to manage the process mailbox behind the scenes,
-- therefore we have additional space overhead to consider (although we
-- are also reducing the size of the mailbox, so there is some counter
-- balance here). The other is that if we do not see the anticipated
-- traffic patterns at runtime, then we might spend more time attempting
-- to prioritise infrequent messages than we would have done simply
-- receiving them! We do however, gain a degree of safety with regards
-- message loss that the serve based vanilla mailbox cannot
-- offer. See the sections on error and exit handling later on for more
-- details about these.
--
-- A Prioritised pserve loop maintains its internal state -
-- including the user defined server state - in an IORef,
-- ensuring it is held consistently between executions, even in the face
-- of unhandled exceptions.
--
--
-- - Defining Prioritised Process Definitions
--
--
-- A PrioritisedProcessDefintion combines the usual
-- ProcessDefintion - containing the cast/call API, error,
-- termination and info handlers - with a list of Priority
-- entries, which are used at runtime to prioritise the server's inputs.
-- Note that it is only messages which are prioritised; The server's
-- various handlers are still evaluated in the order in which they are
-- specified in the ProcessDefinition.
--
-- Prioritisation does not guarantee that a prioritised message/type will
-- be processed before other traffic - indeed doing so in a
-- multi-threaded runtime would be very hard - but in the absence of
-- races between multiple processes, if two messages are both present in
-- the process' own mailbox, they will be applied to the
-- ProcessDefinition's handlers in priority order.
--
-- A prioritised process should probably be configured with a
-- Priority list to be useful. Creating a prioritised process
-- without any priorities could be a potential waste of computational
-- resources, and it is worth thinking carefully about whether or not
-- prioritisation is truly necessary in your design before choosing to
-- use it.
--
-- Using a prioritised process is as simple as calling pserve
-- instead of serve, and passing an initialised
-- PrioritisedProcessDefinition.
--
--
-- - The Cast and Call Protocols
--
--
-- Deliberate interactions with a managed process usually falls
-- into one of two categories. A cast interaction involves a
-- client sending a message asynchronously and the server handling this
-- input. No reply is sent to the client. On the other hand, a
-- call is a remote procedure call, where the client sends
-- a message and waits for a reply from the server.
--
-- All expressions given to apiHandlers have to conform to the
-- cast or call protocol. The protocol (messaging) implementation
-- is hidden from the user; API functions for creating user defined
-- apiHandlers are given instead, which take expressions (i.e.,
-- a function or lambda expression) and create the appropriate
-- Dispatcher for handling the cast (or call).
--
-- These cast and call protocols are for dealing with expected
-- inputs. They will usually form the explicit public API for the
-- process, and be exposed by providing module level functions that defer
-- to the cast or call client API, giving the process author an
-- opportunity to enforce the correct input and response types. For
-- example:
--
--
-- {- Ask the server to add two numbers -}
-- add :: ProcessId -> Double -> Double -> Double
-- add pid x y = call pid (Add x y)
--
--
-- Note here that the return type from the call is inferred and
-- will not be enforced by the type system. If the server sent a
-- different type back in the reply, then the caller might be blocked
-- indefinitely! In fact, the result of mis-matching the expected return
-- type (in the client facing API) with the actual type returned by the
-- server is more severe in practise. The underlying types that implement
-- the call protocol carry information about the expected return
-- type. If there is a mismatch between the input and output types that
-- the client API uses and those which the server declares it can handle,
-- then the message will be considered unroutable - no handler will be
-- executed against it and the unhandled message policy will be applied.
-- You should, therefore, take great care to align these types since the
-- default unhandled message policy is to terminate the server! That
-- might seem pretty extreme, but you can alter the unhandled message
-- policy and/or use the various overloaded versions of the call API in
-- order to detect errors on the server such as this.
--
-- The cost of potential type mismatches between the client and server is
-- the main disadvantage of this looser coupling between them. This
-- mechanism does however, allow servers to handle a variety of messages
-- without specifying the entire protocol to be supported in excruciating
-- detail. For that, we would want session types, which are beyond
-- the scope of this library.
--
--
-- - Handling Unexpected/Info Messages
--
--
-- An explicit protocol for communicating with the process can be
-- configured using cast and call, but it is not possible
-- to prevent other kinds of messages from being sent to the process
-- mailbox. When any message arrives for which there are no handlers able
-- to process its content, the UnhandledMessagePolicy will be
-- applied. Sometimes it is desirable to process incoming messages which
-- aren't part of the protocol, rather than let the policy deal with
-- them. This is particularly true when incoming messages are important
-- to the process, but their point of origin is outside the author's
-- control. Handling signals such as
-- ProcessMonitorNotification is a typical example of this:
--
--
-- handleInfo_ (\(ProcessMonitorNotification _ _ r) -> say $ show r >> continue_)
--
--
--
-- - Handling Process State
--
--
-- The ProcessDefinition is parameterised by the type of state it
-- maintains. A process that has no state will have the type
-- ProcessDefinition () and can be bootstrapped by evaluating
-- statelessProcess.
--
-- All call/cast handlers come in two flavours, those which take the
-- process state as an input and those which do not. Handlers that ignore
-- the process state have to return a function that takes the state and
-- returns the required action. Versions of the various action generating
-- functions ending in an underscore are provided to simplify this:
--
--
-- statelessProcess {
-- apiHandlers = [
-- handleCall_ (\(n :: Int) -> return (n * 2))
-- , handleCastIf_ (\(c :: String, _ :: Delay) -> c == "timeout")
-- (\("timeout", (d :: Delay)) -> timeoutAfter_ d)
-- ]
-- , timeoutHandler = \_ _ -> stop $ ExitOther "timeout"
-- }
--
--
--
-- - Avoiding Side Effects
--
--
-- If you wish to only write side-effect free code in your server
-- definition, then there is an explicit API for doing so. Instead of
-- using the handler definition functions in this module, import the
-- pure server module instead, which provides a StateT based monad
-- for building referentially transparent callbacks.
--
-- See
-- Control.Distributed.Process.ManagedProcess.Server.Restricted
-- for details and API documentation.
--
--
--
-- Error handling appears in several contexts and process definitions can
-- hook into these with relative ease. Catching exceptions inside handle
-- functions is no different to ordinary exception handling in monadic
-- code.
--
--
-- handleCall (\x y ->
-- catch (hereBeDragons x y)
-- (\(e :: SmaugTheTerribleException) ->
-- return (Left (show e))))
--
--
-- The caveats mentioned in Control.Distributed.Process.Extras
-- about exit signal handling are very important here - it is strongly
-- advised that you do not catch exceptions of type
-- ProcessExitException unless you plan to re-throw them again.
--
--
-- - Structured Exit Handling
--
--
-- Because Control.Distributed.Process.ProcessExitException is a
-- ubiquitous signalling mechanism in Cloud Haskell, it is treated unlike
-- other asynchronous exceptions. The ProcessDefinition
-- exitHandlers field accepts a list of handlers that, for a
-- specific exit reason, can decide how the process should respond. If
-- none of these handlers matches the type of reason then the
-- process will exit. with DiedException why. In addition, a
-- private exit handler is installed for exit signals where
-- (reason :: ExitReason) == ExitShutdown, which is an of
-- exit signal used explicitly by supervision APIs. This
-- behaviour, which cannot be overriden, is to gracefully shut down the
-- process, calling the shutdownHandler as usual, before
-- stopping with reason given as the final outcome.
--
-- Example: handling custom data is ProcessExitException
--
--
-- handleExit (\state from (sigExit :: SomeExitData) -> continue s)
--
--
-- Under some circumstances, handling exit signals is perfectly
-- legitimate. Handling of other forms of asynchronous exception
-- (e.g., exceptions not generated by an exit signal) is not
-- supported by this API. Cloud Haskell's primitives for exception
-- handling will work normally in managed process callbacks, but
-- you are strongly advised against swallowing exceptions in general, or
-- masking, unless you have carefully considered the consequences.
--
--
-- - Different Mailbox Types and Exceptions: Message Loss
--
--
-- Neither the vanilla nor the prioritised mailbox
-- implementations will allow you to handle arbitrary asynchronous
-- exceptions outside of your handler code. The way in which the two
-- mailboxes handle unexpected asynchronous exceptions differs
-- significantly however. The first consideration pertains to potential
-- message loss.
--
-- Consider a plain Cloud Haskell expression such as the following:
--
--
-- catch (receiveWait [ match ((m :: SomeType) -> doSomething m) ])
-- ((e :: SomeCustomAsyncException) -> handleExFrom e pid)
--
--
-- It is entirely possible that receiveWait will succeed in
-- matching a message of type SomeType from the mailbox and
-- removing it, to be handed to the supplied expression
-- doSomething. Should an asynchronous exception arrive at this
-- moment in time, though the handler might run and allow the server to
-- recover, the message will be permanently lost.
--
-- The mailbox exposed by serve operates in exactly this way, and
-- as such it is advisible to avoid swallowing asynchronous exceptions,
-- since doing so can introduce the possibility of unexpected message
-- loss.
--
-- The prioritised mailbox exposed by pserve on the other hand,
-- does not suffer this scenario. Whilst the mailbox is drained into the
-- internal priority queue, asynchronous exceptions are masked, and only
-- once the queue has been updated are they removed. In addition, it is
-- possible to peek at the priority queue without removing a
-- message, thereby ensuring that should the handler fail or an
-- asynchronous exception arrive whilst processing the message, we can
-- resume handling our message immediately upon recovering from the
-- exception. This behaviour allows the process to guarantee against
-- message loss, whilst avoiding masking within handlers, which is
-- generally bad form (and can potentially lead to zombie processes, when
-- supervised servers refuse to respond to kill signals whilst
-- stuck in a long running handler).
--
-- Also note that a process' internal state is subject to the same
-- semantics, such that the arrival of an asynchronous exception
-- (including exit signals!) can lead to handlers (especially exit and
-- shutdown handlers) running with a stale version of their state. For
-- this reason - since we cannot guarantee an up to date state in the
-- presence of these semantics - a shutdown handler for a serve
-- loop will always have its state passed as LastKnown stateT.
--
--
-- - Different Mailbox Types and Exceptions: Error Recovery And
-- Shutdown
--
--
-- If any asynchronous exception goes unhandled by a vanilla
-- process, the server will immediately exit without running the user
-- supplied shutdownHandler. It is very important to note that
-- in Cloud Haskell, link failures generate asynchronous exceptions in
-- the target and these will NOT be caught by the serve API and
-- will therefore cause the process to exit /without running the
-- termination handler/ callback. If your termination handler is set up
-- to do important work (such as resource cleanup) then you should avoid
-- linking you process and use monitors instead. If your code absolutely
-- must run its termination handlers in the face of any unhandled (async)
-- exception, consider using a prioritised mailbox, which handles this.
-- Alternatively, consider arranging your processes in a supervision
-- tree, and using a shutdown strategy to ensure that siblings terminate
-- cleanly (based off a supervisor's ordered shutdown signal) in order to
-- ensure cleanup code can run reliably.
--
-- As mentioned above, a prioritised mailbox behaves differently in the
-- face of unhandled asynchronous exceptions. Whilst pserve still
-- offers no means for handling arbitrary async exceptions outside your
-- handlers - and you should avoid handling them within, to the maximum
-- extent possible - it does execute its receiving process in such a way
-- that any unhandled exception will be caught and rethrown. Because of
-- this, and the fact that a prioritised process manages its internal
-- state in an IORef, shutdown handlers are guaranteed to run
-- even in the face of async exceptions. These are run with the latest
-- version of the server state available, given as CleanShutdown
-- stateT when the process is terminating normally (i.e. for reasons
-- ExitNormal or ExitShutdown), and LastKnown
-- stateT when an exception terminated the server process abruptly.
-- The latter acknowledges that we cannot guarantee the exception did not
-- interrupt us after the last handler ran and returned an updated state,
-- but prior to storing the update.
--
-- Although shutdown handlers are run even in the face of unhandled
-- exceptions (and prior to re-throwing, when there is one present), they
-- are not run in a masked state. In fact, exceptions are explicitly
-- unmasked prior to executing a handler, therefore it is possible for a
-- shutdown handler to terminate abruptly. Once again, supervision
-- hierarchies are a better way to ensure consistent cleanup occurs when
-- valued resources are held by a process.
--
--
-- - Special Clients: Control Channels
--
--
-- For advanced users and those requiring very low latency, a prioritised
-- process definition might not be suitable, since it performs
-- considerable work behind the scenes. There are also designs
-- that need to segregate a process' control plane from other
-- kinds of traffic it is expected to receive. For such use cases, a
-- control channel may prove a better choice, since typed channels
-- are already prioritised during the mailbox scans that the base
-- receiveWait and receiveTimeout primitives from
-- distribute-process provides.
--
-- In order to utilise a control channel in a server, it must be
-- passed to the corresponding handleControlChan function (or its
-- stateless variant). The control channel is created by evaluating
-- newControlChan, in the same way that we create regular typed
-- channels.
--
-- In order for clients to communicate with a server via its control
-- channel however, they must pass a handle to a ControlPort,
-- which can be obtained by evaluating channelControlPort on the
-- ControlChannel. A ControlPort is Serializable,
-- so they can alternatively be sent to other processes.
--
-- Control channel traffic will only be prioritised over other
-- traffic if the handlers using it are present before others (e.g.,
-- handleInfo, handleCast, etc) in the process definition. It is
-- not possible to combine prioritised processes with control
-- channels. Attempting to do so will satisfy the compiler, but crash
-- with a runtime error once you attempt to evaluate the prioritised
-- server loop (i.e., pserve).
--
-- Since the primary purpose of control channels is to simplify and
-- optimise client-server communication over a single channel, this
-- module provides an alternate server loop in the form of
-- chanServe. Instead of passing an initialised
-- ProcessDefinition, this API takes an expression from a
-- ControlChannel to ProcessDefinition, operating in the
-- Process monad. Providing the opaque reference in this fashion
-- is useful, since the type of messages the control channel carries will
-- not correlate directly to the inter-process traffic we use internally.
--
-- Although control channels are intended for use as a single control
-- plane (via chanServe), it is possible to use them as a
-- more strictly typed communications backbone, since they do enforce
-- absolute type safety in client code, being bound to a particular type
-- on creation. For rpc (i.e., call) interaction however, it is
-- not possible to have the server reply to a control channel, since
-- they're a one way pipe. It is possible to alleviate this
-- situation by passing a request type than contains a typed channel
-- bound to the expected reply type, enabling client and server to match
-- on both the input and output types as specifically as possible. Note
-- that this still does not guarantee an agreement on types between all
-- parties at runtime however.
--
-- An example of how to do this follows:
--
--
-- data Request = Request String (SendPort String)
-- deriving (Typeable, Generic)
-- instance Binary Request where
--
-- -- note that our initial caller needs an mvar to obtain the control port...
-- echoServer :: MVar (ControlPort Request) -> Process ()
-- echoServer mv = do
-- cc <- newControlChan :: Process (ControlChannel Request)
-- liftIO $ putMVar mv $ channelControlPort cc
-- let s = statelessProcess {
-- apiHandlers = [
-- handleControlChan_ cc (\(Request m sp) -> sendChan sp m >> continue_)
-- ]
-- }
-- serve () (statelessInit Infinity) s
--
-- echoClient :: String -> ControlPort Request -> Process String
-- echoClient str cp = do
-- (sp, rp) <- newChan
-- sendControlMessage cp $ Request str sp
-- receiveChan rp
--
--
--
-- - Communicating with the outside world: External (STM) Input
-- Channels
--
--
-- Both client and server APIs provide a mechanism for interacting with a
-- running server process via STM. This is primarily intended for code
-- that runs outside of Cloud Haskell's Process monad, but can
-- also be used as a channel for sending and/or receiving
-- non-serializable data to or from a managed process. Obviously if you
-- attempt to do this across a remote boundary, things will go
-- spectacularly wrong. The APIs provided do not attempt to restrain
-- this, or to impose any particular scheme on the programmer, therefore
-- you're on your own when it comes to writing the STM code for
-- reading and writing data between client and server.
--
-- For code running inside the Process monad and passing
-- Serializable thunks, there is no real advantage to this approach, and
-- indeed there are several serious disadvantages - none of Cloud
-- Haskell's ordering guarantees will hold when passing data to and from
-- server processes in this fashion, nor are there any guarantees the
-- runtime system can make with regards interleaving between messages
-- passed across Cloud Haskell's communication fabric vs. data shared via
-- STM. This is true even when client(s) and server(s) reside on the same
-- local node.
--
-- A server wishing to receive data via STM can do so using the
-- handleExternal API. By way of example, here is a simple echo
-- server implemented using STM:
--
--
-- demoExternal = do
-- inChan <- liftIO newTQueueIO
-- replyQ <- liftIO newTQueueIO
-- let procDef = statelessProcess {
-- apiHandlers = [
-- handleExternal
-- (readTQueue inChan)
-- (\s (m :: String) -> do
-- liftIO $ atomically $ writeTQueue replyQ m
-- continue s)
-- ]
-- }
-- let txt = "hello 2-way stm foo"
-- pid <- spawnLocal $ serve () (statelessInit Infinity) procDef
-- echoTxt <- liftIO $ do
-- -- firstly we write something that the server can receive
-- atomically $ writeTQueue inChan txt
-- -- then sit and wait for it to write something back to us
-- atomically $ readTQueue replyQ
--
-- say (show $ echoTxt == txt)
--
--
-- For request/reply channels such as this, a convenience based on the
-- call API is also provided, which allows the server author to write an
-- ordinary call handler, and the client author to utilise an API that
-- monitors the server and does the usual stuff you'd expect an RPC style
-- client to do. Here is another example of this in use, demonstrating
-- the callSTM and handleCallExternal APIs in practise.
--
--
-- data StmServer = StmServer { serverPid :: ProcessId
-- , writerChan :: TQueue String
-- , readerChan :: TQueue String
-- }
--
-- instance Resolvable StmServer where
-- resolve = return . Just . serverPid
--
-- echoStm :: StmServer -> String -> Process (Either ExitReason String)
-- echoStm StmServer{..} = callSTM serverPid
-- (writeTQueue writerChan)
-- (readTQueue readerChan)
--
-- launchEchoServer :: CallHandler () String String -> Process StmServer
-- launchEchoServer handler = do
-- (inQ, replyQ) <- liftIO $ do
-- cIn <- newTQueueIO
-- cOut <- newTQueueIO
-- return (cIn, cOut)
--
-- let procDef = statelessProcess {
-- apiHandlers = [
-- handleCallExternal
-- (readTQueue inQ)
-- (writeTQueue replyQ)
-- handler
-- ]
-- }
--
-- pid <- spawnLocal $ serve () (statelessInit Infinity) procDef
-- return $ StmServer pid inQ replyQ
--
-- testExternalCall :: TestResult Bool -> Process ()
-- testExternalCall result = do
-- let txt = "hello stm-call foo"
-- srv <- launchEchoServer (\st (msg :: String) -> reply msg st)
-- echoStm srv txt >>= stash result . (== Right txt)
--
--
--
-- - Performance Considerations
--
--
-- The various server loops are fairly optimised, but there is a
-- definite cost associated with scanning the mailbox to match on
-- protocol messages, plus additional costs in space and time due to
-- mapping over all available info handlers for non-protocol
-- (i.e., neither call nor cast) messages. These are
-- exacerbated significantly when using prioritisation, whilst using a
-- single control channel is very fast and carries little overhead.
--
-- From the client perspective, it's important to remember that the
-- call protocol will wait for a reply in most cases, triggering a
-- full O(n) scan of the caller's mailbox. If the mailbox is extremely
-- full and calls are regularly made, this may have a significant impact
-- on the caller. The callChan family of client API functions
-- can alleviate this, by using (and matching on) a private typed channel
-- instead, but the server must be written to accomodate this. Similar
-- gains can be had using a control channel and providing a typed
-- reply channel in the request data, however the call mechanism
-- does not support this notion, so not only are we unable to use the
-- various reply functions, client code should also consider
-- monitoring the server's pid and handling server failures whilst
-- waiting on
module Control.Distributed.Process.ManagedProcess
-- | Return type for and InitHandler expression.
data InitResult s
InitOk :: s -> Delay -> InitResult s
InitStop :: String -> InitResult s
InitIgnore :: InitResult s
-- | An expression used to initialise a process with its state
type InitHandler a s = a -> Process (InitResult s)
-- | Starts the message handling loop for a managed process
-- configured with the supplied process definition, after calling the
-- init handler with its initial arguments. Note that this function does
-- not return until the server exits.
serve :: a -> InitHandler a s -> ProcessDefinition s -> Process ()
-- | Starts the message handling loop for a prioritised managed
-- process, configured with the supplied process definition, after
-- calling the init handler with its initial arguments. Note that this
-- function does not return until the server exits.
pserve :: a -> InitHandler a s -> PrioritisedProcessDefinition s -> Process ()
-- | Starts the message handling loop for a managed process,
-- configured with a typed control channel. The caller supplied
-- expression is evaluated with an opaque reference to the channel, which
-- must be passed when calling handleControlChan. The meaning
-- and behaviour of the init handler and initial arguments are the same
-- as those given to serve. Note that this function does not
-- return until the server exits.
chanServe :: (Serializable b) => a -> InitHandler a s -> (ControlChannel b -> Process (ProcessDefinition s)) -> Process ()
-- | Wraps any process loop and ensures that it adheres to the
-- managed process start/stop semantics, i.e., evaluating the
-- InitHandler with an initial state and delay will either
-- die due to InitStop, exit silently (due to
-- InitIgnore) or evaluate the process' loop. The
-- supplied loop must evaluate to ExitNormal, otherwise
-- the calling processing will die with whatever
-- ExitReason is given.
runProcess :: (s -> Delay -> Process ExitReason) -> a -> InitHandler a s -> Process ()
-- | Turns a standard ProcessDefinition into a
-- PrioritisedProcessDefinition, by virtue of the supplied list of
-- DispatchPriority expressions.
prioritised :: ProcessDefinition s -> [DispatchPriority s] -> PrioritisedProcessDefinition s
-- | Stores the functions that determine runtime behaviour in response to
-- incoming messages and a policy for responding to unhandled messages.
data ProcessDefinition s
ProcessDefinition :: [Dispatcher s] -> [DeferredDispatcher s] -> [ExternDispatcher s] -> [ExitSignalDispatcher s] -> TimeoutHandler s -> ShutdownHandler s -> UnhandledMessagePolicy -> ProcessDefinition s
-- | functions that handle call/cast messages
[apiHandlers] :: ProcessDefinition s -> [Dispatcher s]
-- | functions that handle non call/cast messages
[infoHandlers] :: ProcessDefinition s -> [DeferredDispatcher s]
-- | functions that handle control channel and STM inputs
[externHandlers] :: ProcessDefinition s -> [ExternDispatcher s]
-- | functions that handle exit signals
[exitHandlers] :: ProcessDefinition s -> [ExitSignalDispatcher s]
-- | a function that handles timeouts
[timeoutHandler] :: ProcessDefinition s -> TimeoutHandler s
-- | a function that is run just before the process exits
[shutdownHandler] :: ProcessDefinition s -> ShutdownHandler s
-- | how to deal with unhandled messages
[unhandledMessagePolicy] :: ProcessDefinition s -> UnhandledMessagePolicy
-- | A ProcessDefinition decorated with DispatchPriority
-- for certain input domains.
data PrioritisedProcessDefinition s
PrioritisedProcessDefinition :: ProcessDefinition s -> [DispatchPriority s] -> [DispatchFilter s] -> RecvTimeoutPolicy -> PrioritisedProcessDefinition s
[processDef] :: PrioritisedProcessDefinition s -> ProcessDefinition s
[priorities] :: PrioritisedProcessDefinition s -> [DispatchPriority s]
[filters] :: PrioritisedProcessDefinition s -> [DispatchFilter s]
[recvTimeout] :: PrioritisedProcessDefinition s -> RecvTimeoutPolicy
-- | For a PrioritisedProcessDefinition, this policy determines for
-- how long the receive loop should continue draining the process'
-- mailbox before processing its received mail (in priority order).
--
-- If a prioritised managed process is receiving a lot of messages
-- (into its real mailbox), the server might never get around to
-- actually processing its inputs. This (mandatory) policy provides a
-- guarantee that eventually (i.e., after a specified number of received
-- messages or time interval), the server will stop removing messages
-- from its mailbox and process those it has already received.
data RecvTimeoutPolicy
RecvMaxBacklog :: Int -> RecvTimeoutPolicy
RecvTimer :: TimeInterval -> RecvTimeoutPolicy
-- | Priority of a message, encoded as an Int
data Priority a
-- | Dispatcher for prioritised handlers
data DispatchPriority s
-- | An expression used to handle process termination
type ShutdownHandler s = ExitState s -> ExitReason -> Process ()
-- | An expression used to handle process timeouts
type TimeoutHandler s = ActionHandler s Delay
-- | Wraps a predicate that is used to determine whether or not a handler
-- is valid based on some combination of the current process state, the
-- type and/or value of the input message or both.
data Condition s m
-- | An action (server state transition) in the Process monad
type Action s = Process (ProcessAction s)
-- | The action taken by a process after a handler has run and its updated
-- state. See
-- "Control.Distributed.Process.ManagedProcess.Server.continue"
-- "Control.Distributed.Process.ManagedProcess.Server.timeoutAfter"
-- "Control.Distributed.Process.ManagedProcess.Server.hibernate"
-- "Control.Distributed.Process.ManagedProcess.Server.stop"
-- "Control.Distributed.Process.ManagedProcess.Server.stopWith"
--
-- Also see "Control.Distributed.Process.Management.Priority.act" and
-- "Control.Distributed.Process.ManagedProcess.Priority.runAfter".
--
-- And other actions. This type should not be used directly.
data ProcessAction s
-- | An action (server state transition) causing a reply to a caller, in
-- the Process monad
type Reply b s = Process (ProcessReply b s)
-- | Returned from handlers for the synchronous call protocol,
-- encapsulates the reply data and the action to take after
-- sending the reply. A handler can return NoReply if they wish
-- to ignore the call.
data ProcessReply r s
-- | An expression used to handle a message
type ActionHandler s a = s -> a -> Action s
-- | An expression used to handle a message and providing a reply
type CallHandler s a b = s -> a -> Reply b s
-- | An expression used to handle a cast message
type CastHandler s a = ActionHandler s a
-- | An expression used to ignore server state during handling
type StatelessHandler s a = a -> (s -> Action s)
-- | An expression used to handle a call message where the reply is
-- deferred via the CallRef
type DeferredCallHandler s a b = CallRef b -> CallHandler s a b
-- | An expression used to handle a call message ignoring server
-- state
type StatelessCallHandler s a b = CallRef b -> a -> Reply b s
-- | An expression used to handle an info message
type InfoHandler s a = ActionHandler s a
-- | An expression used to handle a channel message
type ChannelHandler s a b = SendPort b -> ActionHandler s a
-- | An expression used to handle a channel message in a stateless
-- process
type StatelessChannelHandler s a b = SendPort b -> StatelessHandler s a
-- | Policy for handling unexpected messages, i.e., messages which are not
-- sent using the call or cast APIs, and which are not
-- handled by any of the handleInfo handlers.
data UnhandledMessagePolicy
-- | stop immediately, giving ExitOther UnhandledInput as
-- the reason
Terminate :: UnhandledMessagePolicy
-- | forward the message to the given recipient
DeadLetter :: ProcessId -> UnhandledMessagePolicy
-- | log messages, then behave identically to Drop
Log :: UnhandledMessagePolicy
-- | dequeue and then drop/ignore the message
Drop :: UnhandledMessagePolicy
-- | Wraps a consumer of the call API
data CallRef a
-- | Informs a shutdown handler of whether it is running due to a
-- clean shutdown, or in response to an unhandled exception.
data ExitState s
-- | given when an ordered shutdown is underway
CleanShutdown :: s -> ExitState s
LastKnown :: s -> ExitState s
-- | True if the ExitState is CleanShutdown,
-- otherwise False.
isCleanShutdown :: ExitState s -> Bool
-- | Evaluates to the s state datum in the given
-- ExitState.
exitState :: ExitState s -> s
-- | A default ProcessDefinition, with no api, info or exit handler.
-- The default timeoutHandler simply continues, the
-- shutdownHandler is a no-op and the
-- unhandledMessagePolicy is Terminate.
defaultProcess :: ProcessDefinition s
-- | Creates a default PrioritisedProcessDefinition from a list of
-- DispatchPriority. See defaultProcess for the underlying
-- definition.
defaultProcessWithPriorities :: [DispatchPriority s] -> PrioritisedProcessDefinition s
-- | A basic, stateless ProcessDefinition. See defaultProcess
-- for the default field values.
statelessProcess :: ProcessDefinition ()
-- | A default, state unaware InitHandler that can be used
-- with statelessProcess. This simply returns InitOk with
-- the empty state (i.e., unit) and the given Delay.
statelessInit :: Delay -> InitHandler () ()
-- | Provides a means for servers to listen on a separate, typed
-- control channel, thereby segregating the channel from their
-- regular (and potentially busy) mailbox.
data ControlChannel m
-- | The writable end of a ControlChannel.
data ControlPort m
-- | Creates a new ControlChannel.
newControlChan :: (Serializable m) => Process (ControlChannel m)
-- | Obtain an opaque expression for communicating with a
-- ControlChannel.
channelControlPort :: ControlChannel m -> ControlPort m