packages feed

extensible-effects-concurrent 0.29.2 → 0.30.0

raw patch · 35 files changed

+4110/−1536 lines, 35 filesdep +hashabledep −bytestringdep −paralleldep −processdep ~data-defaultdep ~deepseqdep ~filepathPVP ok

version bump matches the API change (PVP)

Dependencies added: hashable

Dependencies removed: bytestring, parallel, process, tagged

Dependency ranges changed: data-default, deepseq, filepath, lens, text

API changes (from Hackage documentation)

- Control.Eff.Concurrent: data Timeout
- Control.Eff.Concurrent.Process.Timer: TimeoutMicros :: Int -> Timeout
- Control.Eff.Concurrent.Process.Timer: [fromTimeoutMicros] :: Timeout -> Int
- Control.Eff.Concurrent.Process.Timer: instance Control.DeepSeq.NFData Control.Eff.Concurrent.Process.Timer.Timeout
- Control.Eff.Concurrent.Process.Timer: instance GHC.Classes.Eq Control.Eff.Concurrent.Process.Timer.Timeout
- Control.Eff.Concurrent.Process.Timer: instance GHC.Classes.Ord Control.Eff.Concurrent.Process.Timer.Timeout
- Control.Eff.Concurrent.Process.Timer: instance GHC.Enum.Enum Control.Eff.Concurrent.Process.Timer.Timeout
- Control.Eff.Concurrent.Process.Timer: instance GHC.Num.Num Control.Eff.Concurrent.Process.Timer.Timeout
- Control.Eff.Concurrent.Process.Timer: instance GHC.Real.Integral Control.Eff.Concurrent.Process.Timer.Timeout
- Control.Eff.Concurrent.Process.Timer: instance GHC.Real.Real Control.Eff.Concurrent.Process.Timer.Timeout
- Control.Eff.Concurrent.Process.Timer: instance GHC.Show.Show Control.Eff.Concurrent.Process.Timer.Timeout
- Control.Eff.Concurrent.Process.Timer: newtype Timeout
- Control.Eff.Concurrent.Protocol.Observer.Queue: type ObservationQueueReader a = Reader (ObservationQueue a)
- Control.Eff.Concurrent.Protocol.StatefulServer: type Model a = ();
- Control.Eff.Concurrent.Protocol.Supervisor: AlreadyStarted :: ChildId p -> Endpoint (ServerPdu p) -> SpawnErr p
- Control.Eff.Concurrent.Protocol.Supervisor: data SpawnErr p
- Control.Eff.Concurrent.Protocol.Supervisor: data Sup (p :: Type)
- Control.Eff.Concurrent.Protocol.Supervisor: data family StartArgument a q
- Control.Eff.Concurrent.Protocol.Supervisor: getDiagnosticInfo :: forall p e q0. (HasCallStack, HasProcesses e q0, TangibleSup p) => Endpoint (Sup p) -> Eff e Text
- Control.Eff.Concurrent.Protocol.Supervisor: instance (Control.Eff.Log.Handler.LogIo q, Control.Eff.Concurrent.Protocol.Supervisor.TangibleSup p, Control.Eff.Concurrent.Protocol.Tangible (Control.Eff.Concurrent.Protocol.Supervisor.ChildId p), Data.Typeable.Internal.Typeable (Control.Eff.Concurrent.Protocol.EffectfulServer.ServerPdu p), Control.Eff.Concurrent.Protocol.EffectfulServer.Server p (Control.Eff.Concurrent.Process.Processes q), Control.Eff.Concurrent.Process.HasProcesses (Control.Eff.Concurrent.Protocol.EffectfulServer.ServerEffects p (Control.Eff.Concurrent.Process.Processes q)) q) => Control.Eff.Concurrent.Protocol.StatefulServer.Server (Control.Eff.Concurrent.Protocol.Supervisor.Sup p) (Control.Eff.Concurrent.Process.Processes q)
- Control.Eff.Concurrent.Protocol.Supervisor: instance (Data.Typeable.Internal.Typeable (Control.Eff.Concurrent.Protocol.EffectfulServer.ServerPdu p), GHC.Show.Show (Control.Eff.Concurrent.Protocol.Supervisor.ChildId p)) => GHC.Show.Show (Control.Eff.Concurrent.Protocol.Supervisor.SpawnErr p)
- Control.Eff.Concurrent.Protocol.Supervisor: instance Control.DeepSeq.NFData (Control.Eff.Concurrent.Protocol.Supervisor.ChildId p) => Control.DeepSeq.NFData (Control.Eff.Concurrent.Protocol.Pdu (Control.Eff.Concurrent.Protocol.Supervisor.Sup p) ('Control.Eff.Concurrent.Protocol.Synchronous r))
- Control.Eff.Concurrent.Protocol.Supervisor: instance Control.DeepSeq.NFData (Control.Eff.Concurrent.Protocol.Supervisor.ChildId p) => Control.DeepSeq.NFData (Control.Eff.Concurrent.Protocol.Supervisor.SpawnErr p)
- Control.Eff.Concurrent.Protocol.Supervisor: instance Data.Typeable.Internal.Typeable p => Control.Eff.Concurrent.Protocol.HasPdu (Control.Eff.Concurrent.Protocol.Supervisor.Sup p)
- Control.Eff.Concurrent.Protocol.Supervisor: instance GHC.Classes.Eq (Control.Eff.Concurrent.Protocol.Supervisor.ChildId p) => GHC.Classes.Eq (Control.Eff.Concurrent.Protocol.Supervisor.SpawnErr p)
- Control.Eff.Concurrent.Protocol.Supervisor: instance GHC.Classes.Ord (Control.Eff.Concurrent.Protocol.Supervisor.ChildId p) => GHC.Classes.Ord (Control.Eff.Concurrent.Protocol.Supervisor.SpawnErr p)
- Control.Eff.Concurrent.Protocol.Supervisor: instance GHC.Generics.Generic (Control.Eff.Concurrent.Protocol.Supervisor.SpawnErr p)
- Control.Eff.Concurrent.Protocol.Supervisor: instance GHC.Show.Show (Control.Eff.Concurrent.Protocol.Supervisor.ChildId p) => GHC.Show.Show (Control.Eff.Concurrent.Protocol.Pdu (Control.Eff.Concurrent.Protocol.Supervisor.Sup p) ('Control.Eff.Concurrent.Protocol.Synchronous r))
- Control.Eff.Concurrent.Protocol.Supervisor: isSupervisorAlive :: forall p q0 e. (HasCallStack, Member Logs e, Typeable p, HasProcesses e q0) => Endpoint (Sup p) -> Eff e Bool
- Control.Eff.Concurrent.Protocol.Supervisor: lookupChild :: forall p e q0. (HasCallStack, Member Logs e, HasProcesses e q0, TangibleSup p, Typeable (ServerPdu p)) => Endpoint (Sup p) -> ChildId p -> Eff e (Maybe (Endpoint (ServerPdu p)))
- Control.Eff.Concurrent.Protocol.Supervisor: monitorSupervisor :: forall p q0 e. (HasCallStack, Member Logs e, HasProcesses e q0, TangibleSup p) => Endpoint (Sup p) -> Eff e MonitorReference
- Control.Eff.Concurrent.Protocol.Supervisor: spawnChild :: forall p q0 e. (HasCallStack, Member Logs e, HasProcesses e q0, TangibleSup p, Typeable (ServerPdu p)) => Endpoint (Sup p) -> ChildId p -> Eff e (Either (SpawnErr p) (Endpoint (ServerPdu p)))
- Control.Eff.Concurrent.Protocol.Supervisor: spawnOrLookup :: forall p q0 e. (HasCallStack, Member Logs e, HasProcesses e q0, TangibleSup p, Typeable (ServerPdu p)) => Endpoint (Sup p) -> ChildId p -> Eff e (Endpoint (ServerPdu p))
- Control.Eff.Concurrent.Protocol.Supervisor: startSupervisor :: forall p e. (HasCallStack, LogIo (Processes e), TangibleSup p, Server (Sup p) (Processes e)) => StartArgument (Sup p) (Processes e) -> Eff (Processes e) (Endpoint (Sup p))
- Control.Eff.Concurrent.Protocol.Supervisor: stopChild :: forall p e q0. (HasCallStack, Member Logs e, HasProcesses e q0, TangibleSup p) => Endpoint (Sup p) -> ChildId p -> Eff e Bool
- Control.Eff.Concurrent.Protocol.Supervisor: stopSupervisor :: (HasCallStack, HasProcesses e q, Member Logs e, Lifted IO e, TangibleSup p) => Endpoint (Sup p) -> Eff e ()
- Control.Eff.Concurrent.Protocol.Supervisor: supConfigChildStopTimeout :: StartArgument (Sup p) (Processes q) -> Timeout
- Control.Eff.Concurrent.Protocol.Supervisor: type family ChildId p
- Control.Eff.Concurrent.Pure: data Timeout
- Control.Eff.Concurrent.SingleThreaded: data Timeout
+ Control.Eff.Concurrent: TimeoutMicros :: Int -> Timeout
+ Control.Eff.Concurrent: [Delay] :: Timeout -> Process r (ResumeProcess ())
+ Control.Eff.Concurrent: [ExitNormallyWith] :: forall a. (Typeable a, Show a, NFData a) => a -> Interrupt 'NoRecovery
+ Control.Eff.Concurrent: [ExitOtherProcessNotRunning] :: ProcessId -> Interrupt 'NoRecovery
+ Control.Eff.Concurrent: [InterruptedBy] :: forall a. (Typeable a, Show a, NFData a) => a -> Interrupt 'Recoverable
+ Control.Eff.Concurrent: [NormalExitRequestedWith] :: forall a. (Typeable a, Show a, NFData a) => a -> Interrupt 'Recoverable
+ Control.Eff.Concurrent: [fromTimeoutMicros] :: Timeout -> Int
+ Control.Eff.Concurrent: delay :: forall r q. (HasProcesses r q, HasCallStack) => Timeout -> Eff r ()
+ Control.Eff.Concurrent: newtype Timeout
+ Control.Eff.Concurrent: receiveAfterWithTitle :: forall a r q. (HasCallStack, HasProcesses r q, Typeable a, NFData a, Show a) => Timeout -> ProcessTitle -> Eff r (Maybe a)
+ Control.Eff.Concurrent: receiveSelectedAfterWithTitle :: forall a r q. (HasCallStack, HasProcesses r q, Show a, Typeable a) => MessageSelector a -> Timeout -> ProcessTitle -> Eff r (Either TimerElapsed a)
+ Control.Eff.Concurrent: receiveSelectedWithMonitorAfterWithTitle :: forall a r q. (HasCallStack, HasProcesses r q, Show a, Typeable a) => ProcessId -> MessageSelector a -> Timeout -> ProcessTitle -> Eff r (Either (Either ProcessDown TimerElapsed) a)
+ Control.Eff.Concurrent: sendAfterWithTitle :: forall r q message. (HasCallStack, HasProcesses r q, Typeable message, NFData message) => ProcessTitle -> ProcessId -> Timeout -> (TimerReference -> message) -> Eff r TimerReference
+ Control.Eff.Concurrent: startTimerWithTitle :: forall r q. (HasCallStack, HasProcesses r q) => ProcessTitle -> Timeout -> Eff r TimerReference
+ Control.Eff.Concurrent.Misc: showSPrecTypeable :: forall message. Typeable message => Int -> ShowS
+ Control.Eff.Concurrent.Misc: showSTypeRep :: SomeTypeRep -> ShowS
+ Control.Eff.Concurrent.Misc: showSTypeRepPrec :: Int -> SomeTypeRep -> ShowS
+ Control.Eff.Concurrent.Misc: showSTypeable :: forall message. Typeable message => ShowS
+ Control.Eff.Concurrent.Process: ProcessBusySleeping :: ProcessState
+ Control.Eff.Concurrent.Process: TimeoutMicros :: Int -> Timeout
+ Control.Eff.Concurrent.Process: [Delay] :: Timeout -> Process r (ResumeProcess ())
+ Control.Eff.Concurrent.Process: [ExitNormallyWith] :: forall a. (Typeable a, Show a, NFData a) => a -> Interrupt 'NoRecovery
+ Control.Eff.Concurrent.Process: [ExitOtherProcessNotRunning] :: ProcessId -> Interrupt 'NoRecovery
+ Control.Eff.Concurrent.Process: [InterruptedBy] :: forall a. (Typeable a, Show a, NFData a) => a -> Interrupt 'Recoverable
+ Control.Eff.Concurrent.Process: [NormalExitRequestedWith] :: forall a. (Typeable a, Show a, NFData a) => a -> Interrupt 'Recoverable
+ Control.Eff.Concurrent.Process: [fromTimeoutMicros] :: Timeout -> Int
+ Control.Eff.Concurrent.Process: delay :: forall r q. (HasProcesses r q, HasCallStack) => Timeout -> Eff r ()
+ Control.Eff.Concurrent.Process: instance Control.DeepSeq.NFData Control.Eff.Concurrent.Process.Timeout
+ Control.Eff.Concurrent.Process: instance GHC.Classes.Eq Control.Eff.Concurrent.Process.Timeout
+ Control.Eff.Concurrent.Process: instance GHC.Classes.Ord Control.Eff.Concurrent.Process.Timeout
+ Control.Eff.Concurrent.Process: instance GHC.Enum.Enum Control.Eff.Concurrent.Process.Timeout
+ Control.Eff.Concurrent.Process: instance GHC.Num.Num Control.Eff.Concurrent.Process.Timeout
+ Control.Eff.Concurrent.Process: instance GHC.Real.Integral Control.Eff.Concurrent.Process.Timeout
+ Control.Eff.Concurrent.Process: instance GHC.Real.Real Control.Eff.Concurrent.Process.Timeout
+ Control.Eff.Concurrent.Process: instance GHC.Show.Show Control.Eff.Concurrent.Process.Timeout
+ Control.Eff.Concurrent.Process: newtype Timeout
+ Control.Eff.Concurrent.Process.Timer: receiveAfterWithTitle :: forall a r q. (HasCallStack, HasProcesses r q, Typeable a, NFData a, Show a) => Timeout -> ProcessTitle -> Eff r (Maybe a)
+ Control.Eff.Concurrent.Process.Timer: receiveSelectedAfterWithTitle :: forall a r q. (HasCallStack, HasProcesses r q, Show a, Typeable a) => MessageSelector a -> Timeout -> ProcessTitle -> Eff r (Either TimerElapsed a)
+ Control.Eff.Concurrent.Process.Timer: receiveSelectedWithMonitorAfterWithTitle :: forall a r q. (HasCallStack, HasProcesses r q, Show a, Typeable a) => ProcessId -> MessageSelector a -> Timeout -> ProcessTitle -> Eff r (Either (Either ProcessDown TimerElapsed) a)
+ Control.Eff.Concurrent.Process.Timer: sendAfterWithTitle :: forall r q message. (HasCallStack, HasProcesses r q, Typeable message, NFData message) => ProcessTitle -> ProcessId -> Timeout -> (TimerReference -> message) -> Eff r TimerReference
+ Control.Eff.Concurrent.Process.Timer: startTimerWithTitle :: forall r q. (HasCallStack, HasProcesses r q) => ProcessTitle -> Timeout -> Eff r TimerReference
+ Control.Eff.Concurrent.Protocol.Broker: AlreadyStarted :: ChildId p -> Endpoint (ServerPdu p) -> SpawnErr p
+ Control.Eff.Concurrent.Protocol.Broker: [ChildNotFound] :: ChildId child -> Endpoint (Broker child) -> ChildNotFound child
+ Control.Eff.Concurrent.Protocol.Broker: [OnBrokerShuttingDown] :: Endpoint (Broker p) -> ChildEvent p
+ Control.Eff.Concurrent.Protocol.Broker: [OnChildDown] :: Endpoint (Broker p) -> ChildId p -> Endpoint (ServerPdu p) -> Interrupt 'NoRecovery -> ChildEvent p
+ Control.Eff.Concurrent.Protocol.Broker: [OnChildSpawned] :: Endpoint (Broker p) -> ChildId p -> Endpoint (ServerPdu p) -> ChildEvent p
+ Control.Eff.Concurrent.Protocol.Broker: brokerConfigChildStopTimeout :: StartArgument (Broker p) (Processes q) -> Timeout
+ Control.Eff.Concurrent.Protocol.Broker: callById :: forall destination protocol result e q0. (HasCallStack, Member Logs e, HasProcesses e q0, Lifted IO e, Lifted IO q0, TangibleBroker protocol, TangiblePdu destination ( 'Synchronous result), TangiblePdu protocol ( 'Synchronous result), Embeds (ServerPdu destination) protocol, Ord (ChildId destination), Tangible (ChildId destination), Typeable (ServerPdu destination), Tangible result, NFData (Pdu protocol ( 'Synchronous result)), NFData (Pdu (ServerPdu destination) ( 'Synchronous result)), Show (Pdu (ServerPdu destination) ( 'Synchronous result))) => Endpoint (Broker destination) -> ChildId destination -> Pdu protocol ( 'Synchronous result) -> Timeout -> Eff e result
+ Control.Eff.Concurrent.Protocol.Broker: castById :: forall destination protocol e q0. (HasCallStack, Member Logs e, HasProcesses e q0, TangibleBroker protocol, TangiblePdu destination 'Asynchronous, TangiblePdu protocol 'Asynchronous) => Endpoint (Broker destination) -> ChildId destination -> Pdu protocol 'Asynchronous -> Eff e ()
+ Control.Eff.Concurrent.Protocol.Broker: data Broker (p :: Type)
+ Control.Eff.Concurrent.Protocol.Broker: data ChildEvent p
+ Control.Eff.Concurrent.Protocol.Broker: data ChildNotFound child
+ Control.Eff.Concurrent.Protocol.Broker: data SpawnErr p
+ Control.Eff.Concurrent.Protocol.Broker: data family StartArgument a q
+ Control.Eff.Concurrent.Protocol.Broker: getDiagnosticInfo :: forall p e q0. (HasCallStack, HasProcesses e q0, TangibleBroker p) => Endpoint (Broker p) -> Eff e Text
+ Control.Eff.Concurrent.Protocol.Broker: instance (Control.Eff.Log.Handler.LogIo q, Control.Eff.Concurrent.Protocol.Broker.TangibleBroker p, Control.Eff.Concurrent.Protocol.Tangible (Control.Eff.Concurrent.Protocol.Broker.ChildId p), Data.Typeable.Internal.Typeable (Control.Eff.Concurrent.Protocol.EffectfulServer.ServerPdu p), Control.Eff.Concurrent.Protocol.EffectfulServer.Server p (Control.Eff.Concurrent.Process.Processes q), Control.Eff.Concurrent.Process.HasProcesses (Control.Eff.Concurrent.Protocol.EffectfulServer.ServerEffects p (Control.Eff.Concurrent.Process.Processes q)) q) => Control.Eff.Concurrent.Protocol.StatefulServer.Server (Control.Eff.Concurrent.Protocol.Broker.Broker p) (Control.Eff.Concurrent.Process.Processes q)
+ Control.Eff.Concurrent.Protocol.Broker: instance (Data.Typeable.Internal.Typeable (Control.Eff.Concurrent.Protocol.EffectfulServer.ServerPdu p), GHC.Show.Show (Control.Eff.Concurrent.Protocol.Broker.ChildId p)) => GHC.Show.Show (Control.Eff.Concurrent.Protocol.Broker.SpawnErr p)
+ Control.Eff.Concurrent.Protocol.Broker: instance (Data.Typeable.Internal.Typeable p, Data.Typeable.Internal.Typeable (Control.Eff.Concurrent.Protocol.EffectfulServer.ServerPdu p), GHC.Show.Show (Control.Eff.Concurrent.Protocol.Broker.ChildId p)) => GHC.Show.Show (Control.Eff.Concurrent.Protocol.Broker.ChildEvent p)
+ Control.Eff.Concurrent.Protocol.Broker: instance (Data.Typeable.Internal.Typeable p, GHC.Show.Show (Control.Eff.Concurrent.Protocol.Broker.ChildId p)) => GHC.Show.Show (Control.Eff.Concurrent.Protocol.Pdu (Control.Eff.Concurrent.Protocol.Broker.Broker p) r)
+ Control.Eff.Concurrent.Protocol.Broker: instance (GHC.Show.Show (Control.Eff.Concurrent.Protocol.Broker.ChildId child), Data.Typeable.Internal.Typeable child) => GHC.Show.Show (Control.Eff.Concurrent.Protocol.Broker.ChildNotFound child)
+ Control.Eff.Concurrent.Protocol.Broker: instance Control.DeepSeq.NFData (Control.Eff.Concurrent.Protocol.Broker.ChildId c) => Control.DeepSeq.NFData (Control.Eff.Concurrent.Protocol.Broker.ChildNotFound c)
+ Control.Eff.Concurrent.Protocol.Broker: instance Control.DeepSeq.NFData (Control.Eff.Concurrent.Protocol.Broker.ChildId p) => Control.DeepSeq.NFData (Control.Eff.Concurrent.Protocol.Broker.ChildEvent p)
+ Control.Eff.Concurrent.Protocol.Broker: instance Control.DeepSeq.NFData (Control.Eff.Concurrent.Protocol.Broker.ChildId p) => Control.DeepSeq.NFData (Control.Eff.Concurrent.Protocol.Broker.SpawnErr p)
+ Control.Eff.Concurrent.Protocol.Broker: instance Control.DeepSeq.NFData (Control.Eff.Concurrent.Protocol.Broker.ChildId p) => Control.DeepSeq.NFData (Control.Eff.Concurrent.Protocol.Pdu (Control.Eff.Concurrent.Protocol.Broker.Broker p) r)
+ Control.Eff.Concurrent.Protocol.Broker: instance Data.Typeable.Internal.Typeable p => Control.Eff.Concurrent.Protocol.HasPdu (Control.Eff.Concurrent.Protocol.Broker.Broker p)
+ Control.Eff.Concurrent.Protocol.Broker: instance Data.Typeable.Internal.Typeable p => Control.Eff.Concurrent.Protocol.HasPduPrism (Control.Eff.Concurrent.Protocol.Broker.Broker p) (Control.Eff.Concurrent.Protocol.Observer.ObserverRegistry (Control.Eff.Concurrent.Protocol.Broker.ChildEvent p))
+ Control.Eff.Concurrent.Protocol.Broker: instance GHC.Classes.Eq (Control.Eff.Concurrent.Protocol.Broker.ChildId p) => GHC.Classes.Eq (Control.Eff.Concurrent.Protocol.Broker.SpawnErr p)
+ Control.Eff.Concurrent.Protocol.Broker: instance GHC.Classes.Ord (Control.Eff.Concurrent.Protocol.Broker.ChildId p) => GHC.Classes.Ord (Control.Eff.Concurrent.Protocol.Broker.SpawnErr p)
+ Control.Eff.Concurrent.Protocol.Broker: instance GHC.Generics.Generic (Control.Eff.Concurrent.Protocol.Broker.ChildEvent p)
+ Control.Eff.Concurrent.Protocol.Broker: instance GHC.Generics.Generic (Control.Eff.Concurrent.Protocol.Broker.SpawnErr p)
+ Control.Eff.Concurrent.Protocol.Broker: isBrokerAlive :: forall p q0 e. (HasCallStack, Member Logs e, Typeable p, HasProcesses e q0) => Endpoint (Broker p) -> Eff e Bool
+ Control.Eff.Concurrent.Protocol.Broker: lookupChild :: forall p e q0. (HasCallStack, Member Logs e, HasProcesses e q0, TangibleBroker p, Typeable (ServerPdu p)) => Endpoint (Broker p) -> ChildId p -> Eff e (Maybe (Endpoint (ServerPdu p)))
+ Control.Eff.Concurrent.Protocol.Broker: monitorBroker :: forall p q0 e. (HasCallStack, Member Logs e, HasProcesses e q0, TangibleBroker p) => Endpoint (Broker p) -> Eff e MonitorReference
+ Control.Eff.Concurrent.Protocol.Broker: spawnChild :: forall p q0 e. (HasCallStack, Member Logs e, HasProcesses e q0, TangibleBroker p, Typeable (ServerPdu p)) => Endpoint (Broker p) -> ChildId p -> Eff e (Either (SpawnErr p) (Endpoint (ServerPdu p)))
+ Control.Eff.Concurrent.Protocol.Broker: spawnOrLookup :: forall p q0 e. (HasCallStack, Member Logs e, HasProcesses e q0, TangibleBroker p, Typeable (ServerPdu p)) => Endpoint (Broker p) -> ChildId p -> Eff e (Endpoint (ServerPdu p))
+ Control.Eff.Concurrent.Protocol.Broker: startLink :: forall p e. (HasCallStack, LogIo (Processes e), TangibleBroker p, Server (Broker p) (Processes e)) => StartArgument (Broker p) (Processes e) -> Eff (Processes e) (Endpoint (Broker p))
+ Control.Eff.Concurrent.Protocol.Broker: statefulChild :: forall p e. (HasCallStack, LogIo (Processes e), TangibleBroker (Stateful p), Server (Broker (Stateful p)) (Processes e)) => Timeout -> (ChildId p -> StartArgument p (Processes e)) -> StartArgument (Broker (Stateful p)) (Processes e)
+ Control.Eff.Concurrent.Protocol.Broker: stopBroker :: (HasCallStack, HasProcesses e q, Member Logs e, Lifted IO e, TangibleBroker p) => Endpoint (Broker p) -> Eff e ()
+ Control.Eff.Concurrent.Protocol.Broker: stopChild :: forall p e q0. (HasCallStack, Member Logs e, HasProcesses e q0, TangibleBroker p) => Endpoint (Broker p) -> ChildId p -> Eff e Bool
+ Control.Eff.Concurrent.Protocol.Broker: type family ChildId p
+ Control.Eff.Concurrent.Protocol.Observer.Queue: instance Data.Default.Class.Default (Control.Eff.Concurrent.Protocol.StatefulServer.Model (Control.Eff.Concurrent.Protocol.Observer.Queue.ObservationQueue event))
+ Control.Eff.Concurrent.Protocol.Observer.Queue: type Reader a = Reader (ObservationQueue a)
+ Control.Eff.Concurrent.Protocol.StatefulServer: coerceEffects :: forall inner outer a e. (Coercible (Model inner) (Model outer), Coercible (Model outer) (Model inner), Coercible (Settings outer) (Settings inner)) => Eff (ModelState inner : (SettingsReader inner : e)) a -> Eff (ModelState outer : (SettingsReader outer : e)) a
+ Control.Eff.Concurrent.Protocol.StatefulServer: logModel :: forall m e q. (Show (Model m), Member Logs e, HasProcesses e q, Member (ModelState m) e) => Text -> Eff e ()
+ Control.Eff.Concurrent.Protocol.StatefulServer: mapEffects :: forall inner outer a e. (Settings outer -> Settings inner) -> Lens' (Model outer) (Model inner) -> Eff (ModelState inner : (SettingsReader inner : e)) a -> Eff (ModelState outer : (SettingsReader outer : e)) a
+ Control.Eff.Concurrent.Protocol.StatefulServer: preuseModel :: forall a b e. Member (ModelState a) e => Getting (First b) (Model a) b -> Eff e (Maybe b)
+ Control.Eff.Concurrent.Protocol.StatefulServer: title :: (Server a q, Typeable a) => StartArgument a q -> ProcessTitle
+ Control.Eff.Concurrent.Protocol.Watchdog: CrashesPerSeconds :: CrashCount -> CrashTimeSpan -> CrashRate
+ Control.Eff.Concurrent.Protocol.Watchdog: MkChildWatch :: Endpoint (Broker child) -> Set (CrashReport (ChildId child)) -> ChildWatch child
+ Control.Eff.Concurrent.Protocol.Watchdog: MkCrashReport :: TimerReference -> UTCTime -> Interrupt 'NoRecovery -> CrashReport a
+ Control.Eff.Concurrent.Protocol.Watchdog: MkExonerationTimer :: !a -> !TimerReference -> ExonerationTimer a
+ Control.Eff.Concurrent.Protocol.Watchdog: [_crashCount] :: CrashRate -> CrashCount
+ Control.Eff.Concurrent.Protocol.Watchdog: [_crashReason] :: CrashReport a -> Interrupt 'NoRecovery
+ Control.Eff.Concurrent.Protocol.Watchdog: [_crashTimeSpan] :: CrashRate -> CrashTimeSpan
+ Control.Eff.Concurrent.Protocol.Watchdog: [_crashTime] :: CrashReport a -> UTCTime
+ Control.Eff.Concurrent.Protocol.Watchdog: [_crashes] :: ChildWatch child -> Set (CrashReport (ChildId child))
+ Control.Eff.Concurrent.Protocol.Watchdog: [_exonerationTimerReference] :: CrashReport a -> TimerReference
+ Control.Eff.Concurrent.Protocol.Watchdog: [_parent] :: ChildWatch child -> Endpoint (Broker child)
+ Control.Eff.Concurrent.Protocol.Watchdog: attachPermanent :: forall child q e h. (HasCallStack, LogsTo h e, Typeable child, HasPdu (ServerPdu child), Tangible (ChildId child), Ord (ChildId child), HasProcesses e q) => Endpoint (Watchdog child) -> Endpoint (Broker child) -> Eff e ()
+ Control.Eff.Concurrent.Protocol.Watchdog: attachTemporary :: forall child q e h. (HasCallStack, LogsTo h e, Typeable child, HasPdu (ServerPdu child), Tangible (ChildId child), Ord (ChildId child), HasProcesses e q) => Endpoint (Watchdog child) -> Endpoint (Broker child) -> Eff e ()
+ Control.Eff.Concurrent.Protocol.Watchdog: crashCount :: Lens' CrashRate CrashCount
+ Control.Eff.Concurrent.Protocol.Watchdog: crashReason :: Lens' (CrashReport a) (Interrupt 'NoRecovery)
+ Control.Eff.Concurrent.Protocol.Watchdog: crashTime :: Lens' (CrashReport a) UTCTime
+ Control.Eff.Concurrent.Protocol.Watchdog: crashTimeSpan :: Lens' CrashRate CrashTimeSpan
+ Control.Eff.Concurrent.Protocol.Watchdog: crashes :: Lens' (ChildWatch child) (Set (CrashReport (ChildId child)))
+ Control.Eff.Concurrent.Protocol.Watchdog: crashesPerSeconds :: CrashCount -> CrashTimeSpan -> CrashRate
+ Control.Eff.Concurrent.Protocol.Watchdog: data ChildWatch child
+ Control.Eff.Concurrent.Protocol.Watchdog: data CrashRate
+ Control.Eff.Concurrent.Protocol.Watchdog: data CrashReport a
+ Control.Eff.Concurrent.Protocol.Watchdog: data ExonerationTimer a
+ Control.Eff.Concurrent.Protocol.Watchdog: data Watchdog (child :: Type)
+ Control.Eff.Concurrent.Protocol.Watchdog: exonerationTimerReference :: Lens' (CrashReport a) TimerReference
+ Control.Eff.Concurrent.Protocol.Watchdog: getCrashReports :: forall child q e h. (HasCallStack, LogsTo h e, Typeable child, HasPdu (ServerPdu child), Tangible (ChildId child), Ord (ChildId child), HasProcesses e q, Lifted IO q, Lifted IO e, Member Logs e) => Endpoint (Watchdog child) -> Eff e (Map (ChildId child) (ChildWatch child))
+ Control.Eff.Concurrent.Protocol.Watchdog: instance (Data.Typeable.Internal.Typeable child, Control.Eff.Concurrent.Protocol.HasPdu (Control.Eff.Concurrent.Protocol.EffectfulServer.ServerPdu child), Control.Eff.Concurrent.Protocol.Tangible (Control.Eff.Concurrent.Protocol.Broker.ChildId child), GHC.Classes.Ord (Control.Eff.Concurrent.Protocol.Broker.ChildId child), GHC.Classes.Eq (Control.Eff.Concurrent.Protocol.Broker.ChildId child), Control.Eff.Internal.Lifted GHC.Types.IO e, Data.OpenUnion.Member Control.Eff.Log.Handler.Logs e) => Control.Eff.Concurrent.Protocol.StatefulServer.Server (Control.Eff.Concurrent.Protocol.Watchdog.Watchdog child) (Control.Eff.Concurrent.Process.Processes e)
+ Control.Eff.Concurrent.Protocol.Watchdog: instance (Data.Typeable.Internal.Typeable child, Data.Typeable.Internal.Typeable (Control.Eff.Concurrent.Protocol.Broker.ChildId child), GHC.Show.Show (Control.Eff.Concurrent.Protocol.Broker.ChildId child)) => GHC.Show.Show (Control.Eff.Concurrent.Protocol.Watchdog.ChildWatch child)
+ Control.Eff.Concurrent.Protocol.Watchdog: instance (GHC.Show.Show (Control.Eff.Concurrent.Protocol.Broker.ChildId child), Data.Typeable.Internal.Typeable (Control.Eff.Concurrent.Protocol.Broker.ChildId child), Data.Typeable.Internal.Typeable child) => GHC.Show.Show (Control.Eff.Concurrent.Protocol.StatefulServer.Model (Control.Eff.Concurrent.Protocol.Watchdog.Watchdog child))
+ Control.Eff.Concurrent.Protocol.Watchdog: instance (GHC.Show.Show (Control.Eff.Concurrent.Protocol.Broker.ChildId child), Data.Typeable.Internal.Typeable child, Data.Typeable.Internal.Typeable (Control.Eff.Concurrent.Protocol.EffectfulServer.ServerPdu child)) => GHC.Show.Show (Control.Eff.Concurrent.Protocol.Pdu (Control.Eff.Concurrent.Protocol.Watchdog.Watchdog child) r)
+ Control.Eff.Concurrent.Protocol.Watchdog: instance (GHC.Show.Show a, Data.Typeable.Internal.Typeable a) => GHC.Show.Show (Control.Eff.Concurrent.Protocol.Watchdog.CrashReport a)
+ Control.Eff.Concurrent.Protocol.Watchdog: instance Control.DeepSeq.NFData (Control.Eff.Concurrent.Protocol.Broker.ChildId child) => Control.DeepSeq.NFData (Control.Eff.Concurrent.Protocol.Pdu (Control.Eff.Concurrent.Protocol.Watchdog.Watchdog child) r)
+ Control.Eff.Concurrent.Protocol.Watchdog: instance Control.DeepSeq.NFData (Control.Eff.Concurrent.Protocol.Watchdog.ChildWatch child)
+ Control.Eff.Concurrent.Protocol.Watchdog: instance Control.DeepSeq.NFData Control.Eff.Concurrent.Protocol.Watchdog.CrashRate
+ Control.Eff.Concurrent.Protocol.Watchdog: instance Control.DeepSeq.NFData a => Control.DeepSeq.NFData (Control.Eff.Concurrent.Protocol.Watchdog.ExonerationTimer a)
+ Control.Eff.Concurrent.Protocol.Watchdog: instance Data.Default.Class.Default (Control.Eff.Concurrent.Protocol.StatefulServer.Model (Control.Eff.Concurrent.Protocol.Watchdog.Watchdog child))
+ Control.Eff.Concurrent.Protocol.Watchdog: instance Data.Default.Class.Default Control.Eff.Concurrent.Protocol.Watchdog.CrashRate
+ Control.Eff.Concurrent.Protocol.Watchdog: instance Data.Typeable.Internal.Typeable child => Control.Eff.Concurrent.Protocol.HasPdu (Control.Eff.Concurrent.Protocol.Watchdog.Watchdog child)
+ Control.Eff.Concurrent.Protocol.Watchdog: instance Data.Typeable.Internal.Typeable child => Control.Eff.Concurrent.Protocol.HasPduPrism (Control.Eff.Concurrent.Protocol.Watchdog.Watchdog child) (Control.Eff.Concurrent.Protocol.Observer.Observer (Control.Eff.Concurrent.Protocol.Broker.ChildEvent child))
+ Control.Eff.Concurrent.Protocol.Watchdog: instance GHC.Classes.Eq Control.Eff.Concurrent.Protocol.Watchdog.CrashRate
+ Control.Eff.Concurrent.Protocol.Watchdog: instance GHC.Classes.Eq a => GHC.Classes.Eq (Control.Eff.Concurrent.Protocol.Watchdog.ExonerationTimer a)
+ Control.Eff.Concurrent.Protocol.Watchdog: instance GHC.Classes.Ord Control.Eff.Concurrent.Protocol.Watchdog.CrashRate
+ Control.Eff.Concurrent.Protocol.Watchdog: instance GHC.Classes.Ord a => GHC.Classes.Ord (Control.Eff.Concurrent.Protocol.Watchdog.ExonerationTimer a)
+ Control.Eff.Concurrent.Protocol.Watchdog: instance GHC.Show.Show Control.Eff.Concurrent.Protocol.Watchdog.BrokerWatch
+ Control.Eff.Concurrent.Protocol.Watchdog: instance GHC.Show.Show Control.Eff.Concurrent.Protocol.Watchdog.CrashRate
+ Control.Eff.Concurrent.Protocol.Watchdog: instance GHC.Show.Show a => GHC.Show.Show (Control.Eff.Concurrent.Protocol.Watchdog.ExonerationTimer a)
+ Control.Eff.Concurrent.Protocol.Watchdog: instance forall k (a :: k). Control.DeepSeq.NFData (Control.Eff.Concurrent.Protocol.Watchdog.CrashReport a)
+ Control.Eff.Concurrent.Protocol.Watchdog: instance forall k (a :: k). GHC.Classes.Eq (Control.Eff.Concurrent.Protocol.Watchdog.CrashReport a)
+ Control.Eff.Concurrent.Protocol.Watchdog: instance forall k (a :: k). GHC.Classes.Ord (Control.Eff.Concurrent.Protocol.Watchdog.CrashReport a)
+ Control.Eff.Concurrent.Protocol.Watchdog: parent :: Lens' (ChildWatch child) (Endpoint (Broker child))
+ Control.Eff.Concurrent.Protocol.Watchdog: startLink :: forall child e q h. (HasCallStack, Typeable child, LogsTo h (Processes q), Member Logs q, HasProcesses e q, Tangible (ChildId child), Ord (ChildId child), HasPdu (ServerPdu child), Lifted IO q) => CrashRate -> Eff e (Endpoint (Watchdog child))
+ Control.Eff.Concurrent.Protocol.Watchdog: type CrashCount = Int
+ Control.Eff.Concurrent.Protocol.Watchdog: type CrashTimeSpan = Int
+ Control.Eff.Concurrent.Pure: TimeoutMicros :: Int -> Timeout
+ Control.Eff.Concurrent.Pure: [Delay] :: Timeout -> Process r (ResumeProcess ())
+ Control.Eff.Concurrent.Pure: [ExitNormallyWith] :: forall a. (Typeable a, Show a, NFData a) => a -> Interrupt 'NoRecovery
+ Control.Eff.Concurrent.Pure: [ExitOtherProcessNotRunning] :: ProcessId -> Interrupt 'NoRecovery
+ Control.Eff.Concurrent.Pure: [InterruptedBy] :: forall a. (Typeable a, Show a, NFData a) => a -> Interrupt 'Recoverable
+ Control.Eff.Concurrent.Pure: [NormalExitRequestedWith] :: forall a. (Typeable a, Show a, NFData a) => a -> Interrupt 'Recoverable
+ Control.Eff.Concurrent.Pure: [fromTimeoutMicros] :: Timeout -> Int
+ Control.Eff.Concurrent.Pure: delay :: forall r q. (HasProcesses r q, HasCallStack) => Timeout -> Eff r ()
+ Control.Eff.Concurrent.Pure: logCallStack :: forall e. (HasCallStack, Member Logs e) => Severity -> Eff e ()
+ Control.Eff.Concurrent.Pure: logMultiLine :: forall e. (HasCallStack, Member Logs e) => Severity -> [Text] -> Eff e ()
+ Control.Eff.Concurrent.Pure: logMultiLine' :: forall e. (HasCallStack, Member Logs e) => Severity -> [String] -> Eff e ()
+ Control.Eff.Concurrent.Pure: newtype Timeout
+ Control.Eff.Concurrent.Pure: receiveAfterWithTitle :: forall a r q. (HasCallStack, HasProcesses r q, Typeable a, NFData a, Show a) => Timeout -> ProcessTitle -> Eff r (Maybe a)
+ Control.Eff.Concurrent.Pure: receiveSelectedAfterWithTitle :: forall a r q. (HasCallStack, HasProcesses r q, Show a, Typeable a) => MessageSelector a -> Timeout -> ProcessTitle -> Eff r (Either TimerElapsed a)
+ Control.Eff.Concurrent.Pure: receiveSelectedWithMonitorAfterWithTitle :: forall a r q. (HasCallStack, HasProcesses r q, Show a, Typeable a) => ProcessId -> MessageSelector a -> Timeout -> ProcessTitle -> Eff r (Either (Either ProcessDown TimerElapsed) a)
+ Control.Eff.Concurrent.Pure: sendAfterWithTitle :: forall r q message. (HasCallStack, HasProcesses r q, Typeable message, NFData message) => ProcessTitle -> ProcessId -> Timeout -> (TimerReference -> message) -> Eff r TimerReference
+ Control.Eff.Concurrent.Pure: startTimerWithTitle :: forall r q. (HasCallStack, HasProcesses r q) => ProcessTitle -> Timeout -> Eff r TimerReference
+ Control.Eff.Concurrent.SingleThreaded: TimeoutMicros :: Int -> Timeout
+ Control.Eff.Concurrent.SingleThreaded: [Delay] :: Timeout -> Process r (ResumeProcess ())
+ Control.Eff.Concurrent.SingleThreaded: [ExitNormallyWith] :: forall a. (Typeable a, Show a, NFData a) => a -> Interrupt 'NoRecovery
+ Control.Eff.Concurrent.SingleThreaded: [ExitOtherProcessNotRunning] :: ProcessId -> Interrupt 'NoRecovery
+ Control.Eff.Concurrent.SingleThreaded: [InterruptedBy] :: forall a. (Typeable a, Show a, NFData a) => a -> Interrupt 'Recoverable
+ Control.Eff.Concurrent.SingleThreaded: [NormalExitRequestedWith] :: forall a. (Typeable a, Show a, NFData a) => a -> Interrupt 'Recoverable
+ Control.Eff.Concurrent.SingleThreaded: [fromTimeoutMicros] :: Timeout -> Int
+ Control.Eff.Concurrent.SingleThreaded: delay :: forall r q. (HasProcesses r q, HasCallStack) => Timeout -> Eff r ()
+ Control.Eff.Concurrent.SingleThreaded: logCallStack :: forall e. (HasCallStack, Member Logs e) => Severity -> Eff e ()
+ Control.Eff.Concurrent.SingleThreaded: logMultiLine :: forall e. (HasCallStack, Member Logs e) => Severity -> [Text] -> Eff e ()
+ Control.Eff.Concurrent.SingleThreaded: logMultiLine' :: forall e. (HasCallStack, Member Logs e) => Severity -> [String] -> Eff e ()
+ Control.Eff.Concurrent.SingleThreaded: newtype Timeout
+ Control.Eff.Concurrent.SingleThreaded: receiveAfterWithTitle :: forall a r q. (HasCallStack, HasProcesses r q, Typeable a, NFData a, Show a) => Timeout -> ProcessTitle -> Eff r (Maybe a)
+ Control.Eff.Concurrent.SingleThreaded: receiveSelectedAfterWithTitle :: forall a r q. (HasCallStack, HasProcesses r q, Show a, Typeable a) => MessageSelector a -> Timeout -> ProcessTitle -> Eff r (Either TimerElapsed a)
+ Control.Eff.Concurrent.SingleThreaded: receiveSelectedWithMonitorAfterWithTitle :: forall a r q. (HasCallStack, HasProcesses r q, Show a, Typeable a) => ProcessId -> MessageSelector a -> Timeout -> ProcessTitle -> Eff r (Either (Either ProcessDown TimerElapsed) a)
+ Control.Eff.Concurrent.SingleThreaded: sendAfterWithTitle :: forall r q message. (HasCallStack, HasProcesses r q, Typeable message, NFData message) => ProcessTitle -> ProcessId -> Timeout -> (TimerReference -> message) -> Eff r TimerReference
+ Control.Eff.Concurrent.SingleThreaded: startTimerWithTitle :: forall r q. (HasCallStack, HasProcesses r q) => ProcessTitle -> Timeout -> Eff r TimerReference
+ Control.Eff.Log: logCallStack :: forall e. (HasCallStack, Member Logs e) => Severity -> Eff e ()
+ Control.Eff.Log: logMultiLine :: forall e. (HasCallStack, Member Logs e) => Severity -> [Text] -> Eff e ()
+ Control.Eff.Log: logMultiLine' :: forall e. (HasCallStack, Member Logs e) => Severity -> [String] -> Eff e ()
+ Control.Eff.Log.Handler: logCallStack :: forall e. (HasCallStack, Member Logs e) => Severity -> Eff e ()
+ Control.Eff.Log.Handler: logMultiLine :: forall e. (HasCallStack, Member Logs e) => Severity -> [Text] -> Eff e ()
+ Control.Eff.Log.Handler: logMultiLine' :: forall e. (HasCallStack, Member Logs e) => Severity -> [String] -> Eff e ()
- Control.Eff.Concurrent: ProcessDown :: !MonitorReference -> !SomeExitReason -> !ProcessId -> ProcessDown
+ Control.Eff.Concurrent: ProcessDown :: !MonitorReference -> !Interrupt 'NoRecovery -> !ProcessId -> ProcessDown
- Control.Eff.Concurrent: [ExitProcessCancelled] :: Interrupt 'NoRecovery
+ Control.Eff.Concurrent: [ExitProcessCancelled] :: Maybe ProcessId -> Interrupt 'NoRecovery
- Control.Eff.Concurrent: [downReason] :: ProcessDown -> !SomeExitReason
+ Control.Eff.Concurrent: [downReason] :: ProcessDown -> !Interrupt 'NoRecovery
- Control.Eff.Concurrent: callWithTimeout :: forall result destination protocol r q. (HasProcesses r q, TangiblePdu destination ( 'Synchronous result), TangiblePdu protocol ( 'Synchronous result), Tangible result, Member Logs r, Lifted IO q, Lifted IO r, HasCallStack, Embeds destination protocol) => Endpoint destination -> Pdu protocol ( 'Synchronous result) -> Timeout -> Eff r result
+ Control.Eff.Concurrent: callWithTimeout :: forall result destination protocol r q. (HasProcesses r q, TangiblePdu destination ( 'Synchronous result), TangiblePdu protocol ( 'Synchronous result), Tangible result, Member Logs r, HasCallStack, Embeds destination protocol) => Endpoint destination -> Pdu protocol ( 'Synchronous result) -> Timeout -> Eff r result
- Control.Eff.Concurrent: cancelTimer :: forall r q. (Lifted IO q, HasCallStack, HasProcesses r q) => TimerReference -> Eff r ()
+ Control.Eff.Concurrent: cancelTimer :: forall r q. (HasCallStack, HasProcesses r q) => TimerReference -> Eff r ()
- Control.Eff.Concurrent: fromEndpoint :: forall protocol_aTMy protocol_aUUM. Iso (Endpoint protocol_aTMy) (Endpoint protocol_aUUM) ProcessId ProcessId
+ Control.Eff.Concurrent: fromEndpoint :: forall protocol_aW1d protocol_aX9r. Iso (Endpoint protocol_aW1d) (Endpoint protocol_aX9r) ProcessId ProcessId
- Control.Eff.Concurrent: receiveAfter :: forall a r q. (Lifted IO q, HasCallStack, HasProcesses r q, Typeable a, NFData a, Show a) => Timeout -> Eff r (Maybe a)
+ Control.Eff.Concurrent: receiveAfter :: forall a r q. (HasCallStack, HasProcesses r q, Typeable a, NFData a, Show a) => Timeout -> Eff r (Maybe a)
- Control.Eff.Concurrent: receiveSelectedAfter :: forall a r q. (Lifted IO q, HasCallStack, HasProcesses r q, Show a) => MessageSelector a -> Timeout -> Eff r (Either TimerElapsed a)
+ Control.Eff.Concurrent: receiveSelectedAfter :: forall a r q. (HasCallStack, HasProcesses r q, Show a, Typeable a) => MessageSelector a -> Timeout -> Eff r (Either TimerElapsed a)
- Control.Eff.Concurrent: receiveSelectedWithMonitorAfter :: forall a r q. (Lifted IO q, HasCallStack, HasProcesses r q, Show a) => ProcessId -> MessageSelector a -> Timeout -> Eff r (Either (Either ProcessDown TimerElapsed) a)
+ Control.Eff.Concurrent: receiveSelectedWithMonitorAfter :: forall a r q. (HasCallStack, HasProcesses r q, Show a, Typeable a) => ProcessId -> MessageSelector a -> Timeout -> Eff r (Either (Either ProcessDown TimerElapsed) a)
- Control.Eff.Concurrent: receiverPid :: forall a_aHxu. Lens' (Receiver a_aHxu) ProcessId
+ Control.Eff.Concurrent: receiverPid :: forall a_aIyj. Lens' (Receiver a_aIyj) ProcessId
- Control.Eff.Concurrent: sendAfter :: forall r q message. (Lifted IO q, HasCallStack, HasProcesses r q, Typeable message, NFData message) => ProcessId -> Timeout -> (TimerReference -> message) -> Eff r TimerReference
+ Control.Eff.Concurrent: sendAfter :: forall r q message. (HasCallStack, HasProcesses r q, Typeable message, NFData message) => ProcessId -> Timeout -> (TimerReference -> message) -> Eff r TimerReference
- Control.Eff.Concurrent: sendMessage :: forall r q o. (HasProcesses r q, HasCallStack, Typeable o, NFData o) => ProcessId -> o -> Eff r ()
+ Control.Eff.Concurrent: sendMessage :: forall o r q. (HasProcesses r q, HasCallStack, Typeable o, NFData o) => ProcessId -> o -> Eff r ()
- Control.Eff.Concurrent: startTimer :: forall r q. (Lifted IO q, HasCallStack, HasProcesses r q) => Timeout -> Eff r TimerReference
+ Control.Eff.Concurrent: startTimer :: forall r q. (HasCallStack, HasProcesses r q) => Timeout -> Eff r TimerReference
- Control.Eff.Concurrent.Process: ProcessDown :: !MonitorReference -> !SomeExitReason -> !ProcessId -> ProcessDown
+ Control.Eff.Concurrent.Process: ProcessDown :: !MonitorReference -> !Interrupt 'NoRecovery -> !ProcessId -> ProcessDown
- Control.Eff.Concurrent.Process: [ExitProcessCancelled] :: Interrupt 'NoRecovery
+ Control.Eff.Concurrent.Process: [ExitProcessCancelled] :: Maybe ProcessId -> Interrupt 'NoRecovery
- Control.Eff.Concurrent.Process: [downReason] :: ProcessDown -> !SomeExitReason
+ Control.Eff.Concurrent.Process: [downReason] :: ProcessDown -> !Interrupt 'NoRecovery
- Control.Eff.Concurrent.Process: receiverPid :: forall a_aHxu. Lens' (Receiver a_aHxu) ProcessId
+ Control.Eff.Concurrent.Process: receiverPid :: forall a_aIyj. Lens' (Receiver a_aIyj) ProcessId
- Control.Eff.Concurrent.Process: sendMessage :: forall r q o. (HasProcesses r q, HasCallStack, Typeable o, NFData o) => ProcessId -> o -> Eff r ()
+ Control.Eff.Concurrent.Process: sendMessage :: forall o r q. (HasProcesses r q, HasCallStack, Typeable o, NFData o) => ProcessId -> o -> Eff r ()
- Control.Eff.Concurrent.Process.Timer: cancelTimer :: forall r q. (Lifted IO q, HasCallStack, HasProcesses r q) => TimerReference -> Eff r ()
+ Control.Eff.Concurrent.Process.Timer: cancelTimer :: forall r q. (HasCallStack, HasProcesses r q) => TimerReference -> Eff r ()
- Control.Eff.Concurrent.Process.Timer: receiveAfter :: forall a r q. (Lifted IO q, HasCallStack, HasProcesses r q, Typeable a, NFData a, Show a) => Timeout -> Eff r (Maybe a)
+ Control.Eff.Concurrent.Process.Timer: receiveAfter :: forall a r q. (HasCallStack, HasProcesses r q, Typeable a, NFData a, Show a) => Timeout -> Eff r (Maybe a)
- Control.Eff.Concurrent.Process.Timer: receiveSelectedAfter :: forall a r q. (Lifted IO q, HasCallStack, HasProcesses r q, Show a) => MessageSelector a -> Timeout -> Eff r (Either TimerElapsed a)
+ Control.Eff.Concurrent.Process.Timer: receiveSelectedAfter :: forall a r q. (HasCallStack, HasProcesses r q, Show a, Typeable a) => MessageSelector a -> Timeout -> Eff r (Either TimerElapsed a)
- Control.Eff.Concurrent.Process.Timer: receiveSelectedWithMonitorAfter :: forall a r q. (Lifted IO q, HasCallStack, HasProcesses r q, Show a) => ProcessId -> MessageSelector a -> Timeout -> Eff r (Either (Either ProcessDown TimerElapsed) a)
+ Control.Eff.Concurrent.Process.Timer: receiveSelectedWithMonitorAfter :: forall a r q. (HasCallStack, HasProcesses r q, Show a, Typeable a) => ProcessId -> MessageSelector a -> Timeout -> Eff r (Either (Either ProcessDown TimerElapsed) a)
- Control.Eff.Concurrent.Process.Timer: sendAfter :: forall r q message. (Lifted IO q, HasCallStack, HasProcesses r q, Typeable message, NFData message) => ProcessId -> Timeout -> (TimerReference -> message) -> Eff r TimerReference
+ Control.Eff.Concurrent.Process.Timer: sendAfter :: forall r q message. (HasCallStack, HasProcesses r q, Typeable message, NFData message) => ProcessId -> Timeout -> (TimerReference -> message) -> Eff r TimerReference
- Control.Eff.Concurrent.Process.Timer: startTimer :: forall r q. (Lifted IO q, HasCallStack, HasProcesses r q) => Timeout -> Eff r TimerReference
+ Control.Eff.Concurrent.Process.Timer: startTimer :: forall r q. (HasCallStack, HasProcesses r q) => Timeout -> Eff r TimerReference
- Control.Eff.Concurrent.Protocol: fromEndpoint :: forall protocol_aTMy protocol_aUUM. Iso (Endpoint protocol_aTMy) (Endpoint protocol_aUUM) ProcessId ProcessId
+ Control.Eff.Concurrent.Protocol: fromEndpoint :: forall protocol_aW1d protocol_aX9r. Iso (Endpoint protocol_aW1d) (Endpoint protocol_aX9r) ProcessId ProcessId
- Control.Eff.Concurrent.Protocol.Client: callWithTimeout :: forall result destination protocol r q. (HasProcesses r q, TangiblePdu destination ( 'Synchronous result), TangiblePdu protocol ( 'Synchronous result), Tangible result, Member Logs r, Lifted IO q, Lifted IO r, HasCallStack, Embeds destination protocol) => Endpoint destination -> Pdu protocol ( 'Synchronous result) -> Timeout -> Eff r result
+ Control.Eff.Concurrent.Protocol.Client: callWithTimeout :: forall result destination protocol r q. (HasProcesses r q, TangiblePdu destination ( 'Synchronous result), TangiblePdu protocol ( 'Synchronous result), Tangible result, Member Logs r, HasCallStack, Embeds destination protocol) => Endpoint destination -> Pdu protocol ( 'Synchronous result) -> Timeout -> Eff r result
- Control.Eff.Concurrent.Protocol.EffectfulServer: serverTitle :: (Server a e, Typeable (ServerPdu a)) => Init a e -> ProcessTitle
+ Control.Eff.Concurrent.Protocol.EffectfulServer: serverTitle :: (Server a e, Typeable a) => Init a e -> ProcessTitle
- Control.Eff.Concurrent.Protocol.Observer.Queue: await :: forall event r. (Member (ObservationQueueReader event) r, HasCallStack, MonadIO (Eff r), Typeable event, Member Logs r) => Eff r event
+ Control.Eff.Concurrent.Protocol.Observer.Queue: await :: forall event r. (Member (Reader event) r, HasCallStack, MonadIO (Eff r), Typeable event, Member Logs r) => Eff r event
- Control.Eff.Concurrent.Protocol.Observer.Queue: flush :: forall event r. (Member (ObservationQueueReader event) r, HasCallStack, MonadIO (Eff r), Typeable event, Member Logs r) => Eff r [event]
+ Control.Eff.Concurrent.Protocol.Observer.Queue: flush :: forall event r. (Member (Reader event) r, HasCallStack, MonadIO (Eff r), Typeable event, Member Logs r) => Eff r [event]
- Control.Eff.Concurrent.Protocol.Observer.Queue: observe :: forall event eventSource e q len b. (HasCallStack, HasProcesses e q, LogIo q, LogIo e, IsObservable eventSource event, Integral len, Server (ObservationQueue event) (Processes q), Tangible (Pdu eventSource 'Asynchronous)) => len -> Endpoint eventSource -> Eff (ObservationQueueReader event : e) b -> Eff e b
+ Control.Eff.Concurrent.Protocol.Observer.Queue: observe :: forall event eventSource e q len b h. (HasCallStack, HasProcesses e q, LogsTo h e, LogsTo h q, LogsTo h (Processes q), Lifted IO e, Lifted IO q, IsObservable eventSource event, Integral len, Server (ObservationQueue event) (Processes q), Tangible (Pdu eventSource 'Asynchronous)) => len -> Endpoint eventSource -> Eff (Reader event : e) b -> Eff e b
- Control.Eff.Concurrent.Protocol.Observer.Queue: tryRead :: forall event r. (Member (ObservationQueueReader event) r, HasCallStack, MonadIO (Eff r), Typeable event, Member Logs r) => Eff r (Maybe event)
+ Control.Eff.Concurrent.Protocol.Observer.Queue: tryRead :: forall event r. (Member (Reader event) r, HasCallStack, MonadIO (Eff r), Typeable event, Member Logs r) => Eff r (Maybe event)
- Control.Eff.Concurrent.Protocol.StatefulServer: start :: forall a r q h. (HasCallStack, Typeable a, LogsTo h (Processes q), Server (Stateful a) (Processes q), Server a (Processes q), HasProcesses r q) => StartArgument a (Processes q) -> Eff r (Endpoint (Protocol a))
+ Control.Eff.Concurrent.Protocol.StatefulServer: start :: forall a r q h. (HasCallStack, Typeable a, Server (Stateful a) (Processes q), Server a (Processes q), HandleLogWriter h, LogsTo h (Processes q), HasProcesses r q) => StartArgument a (Processes q) -> Eff r (Endpoint (Protocol a))
- Control.Eff.Concurrent.Pure: ProcessDown :: !MonitorReference -> !SomeExitReason -> !ProcessId -> ProcessDown
+ Control.Eff.Concurrent.Pure: ProcessDown :: !MonitorReference -> !Interrupt 'NoRecovery -> !ProcessId -> ProcessDown
- Control.Eff.Concurrent.Pure: [ExitProcessCancelled] :: Interrupt 'NoRecovery
+ Control.Eff.Concurrent.Pure: [ExitProcessCancelled] :: Maybe ProcessId -> Interrupt 'NoRecovery
- Control.Eff.Concurrent.Pure: [downReason] :: ProcessDown -> !SomeExitReason
+ Control.Eff.Concurrent.Pure: [downReason] :: ProcessDown -> !Interrupt 'NoRecovery
- Control.Eff.Concurrent.Pure: callWithTimeout :: forall result destination protocol r q. (HasProcesses r q, TangiblePdu destination ( 'Synchronous result), TangiblePdu protocol ( 'Synchronous result), Tangible result, Member Logs r, Lifted IO q, Lifted IO r, HasCallStack, Embeds destination protocol) => Endpoint destination -> Pdu protocol ( 'Synchronous result) -> Timeout -> Eff r result
+ Control.Eff.Concurrent.Pure: callWithTimeout :: forall result destination protocol r q. (HasProcesses r q, TangiblePdu destination ( 'Synchronous result), TangiblePdu protocol ( 'Synchronous result), Tangible result, Member Logs r, HasCallStack, Embeds destination protocol) => Endpoint destination -> Pdu protocol ( 'Synchronous result) -> Timeout -> Eff r result
- Control.Eff.Concurrent.Pure: cancelTimer :: forall r q. (Lifted IO q, HasCallStack, HasProcesses r q) => TimerReference -> Eff r ()
+ Control.Eff.Concurrent.Pure: cancelTimer :: forall r q. (HasCallStack, HasProcesses r q) => TimerReference -> Eff r ()
- Control.Eff.Concurrent.Pure: fromEndpoint :: forall protocol_aTMy protocol_aUUM. Iso (Endpoint protocol_aTMy) (Endpoint protocol_aUUM) ProcessId ProcessId
+ Control.Eff.Concurrent.Pure: fromEndpoint :: forall protocol_aW1d protocol_aX9r. Iso (Endpoint protocol_aW1d) (Endpoint protocol_aX9r) ProcessId ProcessId
- Control.Eff.Concurrent.Pure: receiveAfter :: forall a r q. (Lifted IO q, HasCallStack, HasProcesses r q, Typeable a, NFData a, Show a) => Timeout -> Eff r (Maybe a)
+ Control.Eff.Concurrent.Pure: receiveAfter :: forall a r q. (HasCallStack, HasProcesses r q, Typeable a, NFData a, Show a) => Timeout -> Eff r (Maybe a)
- Control.Eff.Concurrent.Pure: receiveSelectedAfter :: forall a r q. (Lifted IO q, HasCallStack, HasProcesses r q, Show a) => MessageSelector a -> Timeout -> Eff r (Either TimerElapsed a)
+ Control.Eff.Concurrent.Pure: receiveSelectedAfter :: forall a r q. (HasCallStack, HasProcesses r q, Show a, Typeable a) => MessageSelector a -> Timeout -> Eff r (Either TimerElapsed a)
- Control.Eff.Concurrent.Pure: receiveSelectedWithMonitorAfter :: forall a r q. (Lifted IO q, HasCallStack, HasProcesses r q, Show a) => ProcessId -> MessageSelector a -> Timeout -> Eff r (Either (Either ProcessDown TimerElapsed) a)
+ Control.Eff.Concurrent.Pure: receiveSelectedWithMonitorAfter :: forall a r q. (HasCallStack, HasProcesses r q, Show a, Typeable a) => ProcessId -> MessageSelector a -> Timeout -> Eff r (Either (Either ProcessDown TimerElapsed) a)
- Control.Eff.Concurrent.Pure: receiverPid :: forall a_aHxu. Lens' (Receiver a_aHxu) ProcessId
+ Control.Eff.Concurrent.Pure: receiverPid :: forall a_aIyj. Lens' (Receiver a_aIyj) ProcessId
- Control.Eff.Concurrent.Pure: sendAfter :: forall r q message. (Lifted IO q, HasCallStack, HasProcesses r q, Typeable message, NFData message) => ProcessId -> Timeout -> (TimerReference -> message) -> Eff r TimerReference
+ Control.Eff.Concurrent.Pure: sendAfter :: forall r q message. (HasCallStack, HasProcesses r q, Typeable message, NFData message) => ProcessId -> Timeout -> (TimerReference -> message) -> Eff r TimerReference
- Control.Eff.Concurrent.Pure: sendMessage :: forall r q o. (HasProcesses r q, HasCallStack, Typeable o, NFData o) => ProcessId -> o -> Eff r ()
+ Control.Eff.Concurrent.Pure: sendMessage :: forall o r q. (HasProcesses r q, HasCallStack, Typeable o, NFData o) => ProcessId -> o -> Eff r ()
- Control.Eff.Concurrent.Pure: startTimer :: forall r q. (Lifted IO q, HasCallStack, HasProcesses r q) => Timeout -> Eff r TimerReference
+ Control.Eff.Concurrent.Pure: startTimer :: forall r q. (HasCallStack, HasProcesses r q) => Timeout -> Eff r TimerReference
- Control.Eff.Concurrent.SingleThreaded: ProcessDown :: !MonitorReference -> !SomeExitReason -> !ProcessId -> ProcessDown
+ Control.Eff.Concurrent.SingleThreaded: ProcessDown :: !MonitorReference -> !Interrupt 'NoRecovery -> !ProcessId -> ProcessDown
- Control.Eff.Concurrent.SingleThreaded: [ExitProcessCancelled] :: Interrupt 'NoRecovery
+ Control.Eff.Concurrent.SingleThreaded: [ExitProcessCancelled] :: Maybe ProcessId -> Interrupt 'NoRecovery
- Control.Eff.Concurrent.SingleThreaded: [downReason] :: ProcessDown -> !SomeExitReason
+ Control.Eff.Concurrent.SingleThreaded: [downReason] :: ProcessDown -> !Interrupt 'NoRecovery
- Control.Eff.Concurrent.SingleThreaded: callWithTimeout :: forall result destination protocol r q. (HasProcesses r q, TangiblePdu destination ( 'Synchronous result), TangiblePdu protocol ( 'Synchronous result), Tangible result, Member Logs r, Lifted IO q, Lifted IO r, HasCallStack, Embeds destination protocol) => Endpoint destination -> Pdu protocol ( 'Synchronous result) -> Timeout -> Eff r result
+ Control.Eff.Concurrent.SingleThreaded: callWithTimeout :: forall result destination protocol r q. (HasProcesses r q, TangiblePdu destination ( 'Synchronous result), TangiblePdu protocol ( 'Synchronous result), Tangible result, Member Logs r, HasCallStack, Embeds destination protocol) => Endpoint destination -> Pdu protocol ( 'Synchronous result) -> Timeout -> Eff r result
- Control.Eff.Concurrent.SingleThreaded: cancelTimer :: forall r q. (Lifted IO q, HasCallStack, HasProcesses r q) => TimerReference -> Eff r ()
+ Control.Eff.Concurrent.SingleThreaded: cancelTimer :: forall r q. (HasCallStack, HasProcesses r q) => TimerReference -> Eff r ()
- Control.Eff.Concurrent.SingleThreaded: fromEndpoint :: forall protocol_aTMy protocol_aUUM. Iso (Endpoint protocol_aTMy) (Endpoint protocol_aUUM) ProcessId ProcessId
+ Control.Eff.Concurrent.SingleThreaded: fromEndpoint :: forall protocol_aW1d protocol_aX9r. Iso (Endpoint protocol_aW1d) (Endpoint protocol_aX9r) ProcessId ProcessId
- Control.Eff.Concurrent.SingleThreaded: receiveAfter :: forall a r q. (Lifted IO q, HasCallStack, HasProcesses r q, Typeable a, NFData a, Show a) => Timeout -> Eff r (Maybe a)
+ Control.Eff.Concurrent.SingleThreaded: receiveAfter :: forall a r q. (HasCallStack, HasProcesses r q, Typeable a, NFData a, Show a) => Timeout -> Eff r (Maybe a)
- Control.Eff.Concurrent.SingleThreaded: receiveSelectedAfter :: forall a r q. (Lifted IO q, HasCallStack, HasProcesses r q, Show a) => MessageSelector a -> Timeout -> Eff r (Either TimerElapsed a)
+ Control.Eff.Concurrent.SingleThreaded: receiveSelectedAfter :: forall a r q. (HasCallStack, HasProcesses r q, Show a, Typeable a) => MessageSelector a -> Timeout -> Eff r (Either TimerElapsed a)
- Control.Eff.Concurrent.SingleThreaded: receiveSelectedWithMonitorAfter :: forall a r q. (Lifted IO q, HasCallStack, HasProcesses r q, Show a) => ProcessId -> MessageSelector a -> Timeout -> Eff r (Either (Either ProcessDown TimerElapsed) a)
+ Control.Eff.Concurrent.SingleThreaded: receiveSelectedWithMonitorAfter :: forall a r q. (HasCallStack, HasProcesses r q, Show a, Typeable a) => ProcessId -> MessageSelector a -> Timeout -> Eff r (Either (Either ProcessDown TimerElapsed) a)
- Control.Eff.Concurrent.SingleThreaded: receiverPid :: forall a_aHxu. Lens' (Receiver a_aHxu) ProcessId
+ Control.Eff.Concurrent.SingleThreaded: receiverPid :: forall a_aIyj. Lens' (Receiver a_aIyj) ProcessId
- Control.Eff.Concurrent.SingleThreaded: sendAfter :: forall r q message. (Lifted IO q, HasCallStack, HasProcesses r q, Typeable message, NFData message) => ProcessId -> Timeout -> (TimerReference -> message) -> Eff r TimerReference
+ Control.Eff.Concurrent.SingleThreaded: sendAfter :: forall r q message. (HasCallStack, HasProcesses r q, Typeable message, NFData message) => ProcessId -> Timeout -> (TimerReference -> message) -> Eff r TimerReference
- Control.Eff.Concurrent.SingleThreaded: sendMessage :: forall r q o. (HasProcesses r q, HasCallStack, Typeable o, NFData o) => ProcessId -> o -> Eff r ()
+ Control.Eff.Concurrent.SingleThreaded: sendMessage :: forall o r q. (HasProcesses r q, HasCallStack, Typeable o, NFData o) => ProcessId -> o -> Eff r ()
- Control.Eff.Concurrent.SingleThreaded: startTimer :: forall r q. (Lifted IO q, HasCallStack, HasProcesses r q) => Timeout -> Eff r TimerReference
+ Control.Eff.Concurrent.SingleThreaded: startTimer :: forall r q. (HasCallStack, HasProcesses r q) => Timeout -> Eff r TimerReference

Files

ChangeLog.md view
@@ -1,5 +1,38 @@ # Changelog for extensible-effects-concurrent +## 0.30.0+- Improve inline code documentation+- **Supervisor:** +    - Rename to **[Broker](./src/Control/Eff/Concurrent/Protocol/Broker.hs)**  +    - Add the `ChildEvent` needed by the new **watchdog**  +    - Add `callById` and `castById`+- [Process](./src/Control/Eff/Concurrent/Process.hs)+    - Introduce a new `Interrupt NoRecovery` clause: `ExitOtherProcessNotRunning`+    - Change the second parameter of `ProcessDown` from `SomeExitReason` to `Interrupt NoRecovery`+    - Introduce new `Interrupt` reasons+      for all categories with an existential+      parameter, that must have `NFData`, `Show`+      and `Typeable` constraints.+    - Introduce a new timing primitive: `Delay`   +- [StatefulServer](./src/Control/Eff/Concurrent/Protocol/StatefulServer.hs)+    - _Upgrade_ the associated **type alias** `Model`  to an associated **type**.+    - Add `mapEffects` +    - Add `coerceEffects`        +- Export the `TimeoutMicros` constructor+- Add **[Watchdog](./src/Control/Eff/Concurrent/Protocol/Watchdog.hs)** a server that watches +  a **[Broker](./src/Control/Eff/Concurrent/Protocol/Broker.hs)** and restarts crashed+  processes registered at the broker.+        +- **[Logging](./src/Control/Eff/Log.hs)**+    - Add `logCallStack`+    - Add `logMultiLine`      ++-  **[Timer](./src/Control/Eff/Concurrent/Process/Timer.hs)**+    - Allow timers to have custom titles via:+        - `sendAfterWithTitle`+        - `startTimerWithTitle`+    - Switch to use the new `Delay` primitive    +             ## 0.29.2 - Improve `Supervisor` API: Use `Init` from the effectful server as start   argument type.
README.md view
@@ -20,6 +20,56 @@ The fundamental approach to modelling applications in Erlang is based on the concept of concurrent, communicating processes. ++### Example Code++```haskell+module Main where++import           Control.Eff+import           Control.Eff.Concurrent++main :: IO ()+main = defaultMain example++example :: Eff Effects ()+example = do+  person <- spawn "alice" alice+  replyToMe <- self+  sendMessage person replyToMe+  personName <- receiveMessage+  logInfo' ("I just met " ++ personName)++alice :: Eff Effects ()+alice = do+  logInfo "I am waiting for someone to ask me..."+  sender <- receiveMessage+  sendMessage sender ("Alice" :: String)+  logInfo' (show sender ++ " message received.")++```+This is taken from [example-4](./examples/example-4/Main.hs).+++**Running** this example causes this output:++```text+DEBUG      no proc  scheduler loop entered                                       at ForkIOScheduler.hs:209+DEBUG        init!1 enter process                                                at ForkIOScheduler.hs:691+NOTICE       init!1 ++++++++ main process started ++++++++                       at ForkIOScheduler.hs:579+DEBUG       alice!2 enter process                                                at ForkIOScheduler.hs:691+INFO        alice!2 I am waiting for someone to ask me...                        at Main.hs:19+INFO        alice!2 !1 message received.                                         at Main.hs:22+DEBUG       alice!2 exit: Process finished successfully                          at ForkIOScheduler.hs:729+INFO         init!1 I just met Alice                                             at Main.hs:15+NOTICE       init!1 ++++++++ main process returned ++++++++                      at ForkIOScheduler.hs:581+DEBUG        init!1 exit: Process finished successfully                          at ForkIOScheduler.hs:729+DEBUG      no proc  scheduler loop returned                                      at ForkIOScheduler.hs:211+DEBUG      no proc  scheduler cleanup begin                                      at ForkIOScheduler.hs:205+NOTICE     no proc  cancelling processes: []                                     at ForkIOScheduler.hs:222+NOTICE     no proc  all processes cancelled                                      at ForkIOScheduler.hs:239+```+ The mental model of the programming framework regards objects as **processes** with an isolated internal state.  @@ -65,72 +115,174 @@ a process is gone, when a process exits; hence restarting a process will not be bothered by left-over, possibly inconsistent, state.  -### Higher Level Abstractions+### Timers  -Processes can receive only message of type `Dynamic`.+[The Timer module](src/Control/Eff/Concurrent/Process/Timer.hs) contains functions to send messages after a time+has passed, and reiceive messages with timeouts. -In order to leverage Haskells type-safety, a bit of support code is available.+### More Type-Safety: [The Protocol Metaphor](./src/Control/Eff/Concurrent/Protocol.hs) -There is a **data family** called **`Api`** allowing to model **calls** and -**casts**, as well as event management and process supervision.+As the library carefully leaves the realm of untyped messages, it uses the +concept of a **protocol** that governs the communication+between concurrent processes, which are either **protocol servers** or+**clients** as a metaphor. -These facilities are very important to build **non-defensive**, **let-it-crash**-applications, resilient to runtime errors.   +The communication is initiated by the client. -### Additional services+The idea is to indicate such a _protocol_ using a **custom data type**,+e.g. `data TemperatureSensorReader` or `data SqlServer`.+ +The library consists some tricks to restrict the kinds of messages that+are acceptable when communicating with processes _adhering to the protocol_. -Currently a custom **logging effect** is also part of the code base.+This _protocol_ is not encoded in the users code, but rather something that+the programmer keeps in his head.  -## Usage and Implementation+In order to be appreciated by authors of real world applications, the +protocol can be defined by giving an abstract message sum-type and +code for spawning server processes. -### Example Code+It focusses on these questions: -```haskell-module Main where+1. What messages does a process accept?+2. When sending a certain message, should the sender wait for an answer? -import           Control.Eff-import           Control.Eff.Concurrent+#### Protocol Phantom Type -main :: IO ()-main = defaultMain example+In this library, the key to a _protocol_ is a single type,+that could even be a so called __phantom type__, i.e. a+type without any runtime values: -example :: Eff Effects ()-example = do-  person <- spawn "alice" alice-  replyToMe <- self-  sendMessage person replyToMe-  personName <- receiveMessage-  logInfo' ("I just met " ++ personName)+```haskell  -alice :: Eff Effects ()-alice = do-  logInfo "I am waiting for someone to ask me..."-  sender <- receiveMessage-  sendMessage sender ("Alice" :: String)-  logInfo' (show sender ++ " message received.")+data UserRegistry -- look mom, no constructors!!  -```-This is taken from [example-4](./examples/example-4/Main.hs).+```  +Such a type exists only for the type system. -**Running** this example causes this output:+It can only be used as a parameter to certain type constructors,+and for defining type class and type family instances, e.g. -```text-DEBUG     scheduler loop entered                                                   ForkIOScheduler.hs line 157-DEBUG            !1 enter process                                                            ForkIOScheduler.hs line 549-NOTICE           !1 ++++++++ main process started ++++++++                                   ForkIOScheduler.hs line 461-DEBUG            !2 enter process                                                            ForkIOScheduler.hs line 549-INFO             !2 I am waiting for someone to ask me...                                               Main.hs line 26-INFO             !2 !1 just needed to know it.                                                          Main.hs line 29-DEBUG            !2 exit normally                                                            ForkIOScheduler.hs line 568-INFO             !1 I just met Alice                                                                    Main.hs line 34-NOTICE           !1 ++++++++ main process returned ++++++++                                  ForkIOScheduler.hs line 463-DEBUG            !1 exit normally                                                            ForkIOScheduler.hs line 568-DEBUG     scheduler loop returned                                                  ForkIOScheduler.hs line 159-DEBUG     scheduler cleanup begin                                                  ForkIOScheduler.hs line 154-NOTICE    cancelling processes: []                                                 ForkIOScheduler.hs line 168-NOTICE    all processes cancelled                                                  ForkIOScheduler.hs line 179++```haskell ++newtype Endpoint protocol = MkServer { _processId :: ProcessId }++data UserRegistry++startUserRegistry :: Eff e (Endpoint UserRegistry)+startUserRegistry = +  error "just an example"+ ```++Here the `Endpoint` has a type parameter `protocol` but the type is not+used by the constructor to hold any values, hence we can use `UserRegistry`,+as a parameter, since `UserRegistry` has no value constructors.++#### Protocol Data Units++Messages that belong to a protocol are called __protocol data units (PDU)__.++#### Protocol Servers Endpoints++The `ProcessId` of a process identifies the messages box that+`receiveMessage` will use, when waiting for an incoming message.++While it defines _where_ the messages are collected, it does +not restrict or inform about _what_ data is handled by a process.++An [Endpoint](https://hackage.haskell.org/package/extensible-effects-concurrent/docs/Control-Eff-Concurrent-Protocol.html#t:Endpoint) is a wrapper +around the `ProcessId` that takes a _type parameter_.++The type does not have to have any values, it can be a phantom type.++This type serves only the tag the process as a server accepting messages identified by the [HasPdu](https://hackage.haskell.org/package/extensible-effects-concurrent/docs/Control-Eff-Concurrent-Protocol.html#t:HasPdu)   type class.++#### Server/Protocol Composability++Usually a protocol consists of some really custom PDUs and+some PDUs that more or less are found in many protocols,+like event listener registration and event notification.++It is therefore helpful to be able to compose protocols.++The machinery in this library allows to list several +PDU instances understood by endpoints of a given protocol+phantom type.++#### Protocol Clients   ++_Clients_ use a protocol by sending `Pdu`s indexed by +some protocol phantom type to a server process.++Clients use `Endpoint`s to address these servers, and the+functions defined in [the corresponding module](https://hackage.haskell.org/package/extensible-effects-concurrent/docs/Control-Eff-Concurrent-Protocol-Client.html).++Most important are these two functions:  ++* [cast](https://hackage.haskell.org/package/extensible-effects-concurrent/docs/Control-Eff-Concurrent-Protocol-Client.html#v:cast)+  to send fire-and-forget messages+   +* [call](https://hackage.haskell.org/package/extensible-effects-concurrent/docs/Control-Eff-Concurrent-Protocol-Client.html#v:cast)+  to send RPC-style requests and wait (block) for the responses.+  Also, when the server is not running, or crashes in +  while waiting, the calling process is interrupted++#### Protocol Servers   ++This library offers an API for defining practically safe to+ use __protocol servers__:++* [EffectfulServer](./src/Control/Eff/Concurrent/Protocol/EffectfulServer.hs) This modules defines the framework+  of a process, that has a callback function that is repeatedly+  called when ever a message was received.+  +  The callback may rely on any extra effects (extensible effects).    ++* [StatefulServer](./src/Control/Eff/Concurrent/Protocol/StatefulServer.hs) A server based on the `EffectfulServer` that includes the definition of+  in internal state called __Model__, and some nice +  helper functions to access the model. These functions+  allow the use of lenses. +  Unlike the effectful server, the effects that the +  callback functions can use are defined in this module.++* [CallbackServer](./src/Control/Eff/Concurrent/Protocol/CallbackServer.hs) A server based on the `EffectfulServer` that does not require a type class+   instance like the stateful and effectful servers do.+   It can be used to define _inline_ servers.+  +#### Events and Observers++A parameterized protocol for event handling is provided in +the module:++* [Observer](./src/Control/Eff/Concurrent/Protocol/Observer.hs) + ++#### Brokers and Watchdogs++A key part of a robust system is monitoring and possibly restarting +stuff that crashes, this is done in conjunction by two modules:++* [Broker](./src/Control/Eff/Concurrent/Protocol/Broker.hs)+* [Watchdog](./src/Control/Eff/Concurrent/Protocol/Watchdog.hs)++A client of a process that might be restarted cannot use the `ProcessId` +directly, but has to use an abstract ID and lookup the `ProcessId` from a +process **[broker](./src/Control/Eff/Concurrent/Protocol/Broker.hs)**, that manages the current `ProcessId` of protocol server+processes.++That way, when ever the server process registered at a broker crashes,+**a [watchdog](./src/Control/Eff/Concurrent/Protocol/Watchdog.hs) process** can (re-)start the crashed server.  ++### Additional services++Currently a **logging effect** is also part of the code base.++## Usage and Implementation++Should work with `stack`, `cabal` and `nix`.   ### Required GHC Extensions 
examples/example-1/Main.hs view
@@ -76,7 +76,7 @@   go  testServerLoop :: Eff Effects (Endpoint TestProtocol)-testServerLoop = Callback.start (Callback.callbacks handleReq "test-server-1")+testServerLoop = Callback.startLink (Callback.callbacks handleReq "test-server-1")  where   handleReq :: Endpoint TestProtocol -> Event TestProtocol -> Eff Effects ()   handleReq me (OnCall rt cm) =
examples/example-2/Main.hs view
@@ -13,6 +13,7 @@ import           Control.DeepSeq import qualified Data.Text as T import           Data.Type.Pretty+import           Data.Default  main :: IO () main = defaultMain (void counterExample)@@ -90,7 +91,7 @@  instance (LogIo q) => Server SupiCounter (Processes q) where -  type instance Model SupiCounter =+  newtype instance Model SupiCounter = SupiCounterModel     ( Integer     , ObserverRegistry CounterChanged     , Maybe (ReplyTarget SupiCounter (Maybe ()))@@ -98,39 +99,57 @@    data instance StartArgument SupiCounter (Processes q) = MkEmptySupiCounter -  setup _ _ = return ((0, emptyObserverRegistry, Nothing), ())+  setup _ _ = return (SupiCounterModel (0, emptyObserverRegistry, Nothing), ())    update _ _ = \case     OnCall rt callReq ->       case callReq of         ToPdu1 Cnt ->-          sendReply rt =<< useModel @SupiCounter _1+          sendReply rt =<< useModel supiCounter         ToPdu2 _ -> error "unreachable"         ToPdu3 (Whoopediedoo c) ->-          modifyModel @SupiCounter (_3 .~ if c then Just rt else Nothing)+          modifyModel @SupiCounter (supiTarget .~ if c then Just rt else Nothing)      OnCast castReq ->       case castReq of         ToPdu1 Inc -> do-          val' <- view _1 <$> modifyAndGetModel @SupiCounter (_1 %~ (+ 1))-          zoomModel @SupiCounter _2 (observerRegistryNotify (CounterChanged val'))+          val' <- view supiCounter <$> modifyAndGetModel (supiCounter %~ (+ 1))+          zoomModel supiRegistry (observerRegistryNotify (CounterChanged val'))           when (val' > 5) $-            getAndModifyModel @SupiCounter (_3 .~ Nothing)-            >>= traverse_ (\rt' -> sendReply rt' (Just ())) . view _3+            getAndModifyModel  (supiTarget .~ Nothing)+            >>= traverse_ (\rt' -> sendReply rt' (Just ())) . view supiTarget         ToPdu2 x ->-          zoomModel @SupiCounter _2 (observerRegistryHandlePdu x)+          zoomModel supiRegistry (observerRegistryHandlePdu x)         ToPdu3 _ -> error "unreachable"      OnDown pd -> do-      wasRemoved <- zoomModel @SupiCounter _2 (observerRegistryRemoveProcess @CounterChanged (downProcess pd))+      wasRemoved <- zoomModel supiRegistry (observerRegistryRemoveProcess @CounterChanged (downProcess pd))       if wasRemoved         then logDebug ("removed: "    <> T.pack (show pd))         else logError ("unexpected: " <> T.pack (show pd))      other -> logWarning (T.pack (show other)) +supiCounter :: Lens' (Model SupiCounter) Integer+supiCounter =+  lens+    (\(SupiCounterModel (x,_,_)) -> x)+    (\(SupiCounterModel (_,y,z)) x -> SupiCounterModel (x,y,z))++supiRegistry :: Lens' (Model SupiCounter) (ObserverRegistry CounterChanged)+supiRegistry =+  lens+    (\(SupiCounterModel (_,y,_)) -> y)+    (\(SupiCounterModel (x,_,z)) y -> SupiCounterModel (x,y,z))++supiTarget :: Lens' (Model SupiCounter) (Maybe (ReplyTarget SupiCounter (Maybe ())))+supiTarget =+  lens+    (\(SupiCounterModel (_,_,z)) -> z)+    (\(SupiCounterModel (x,y,_)) z -> SupiCounterModel (x,y,z))+ spawnCounter :: (LogIo q) => Eff (Processes q) ( Endpoint SupiCounter )-spawnCounter = start MkEmptySupiCounter+spawnCounter = startLink MkEmptySupiCounter   deriving instance Show (Pdu Counter x)@@ -138,10 +157,11 @@ logCounterObservations   :: (LogIo q, Typeable q)   => Eff (Processes q) (Endpoint (Observer CounterChanged))-logCounterObservations = start OCCStart+logCounterObservations = startLink OCCStart  instance Member Logs q => Server (Observer CounterChanged) (Processes q) where   data instance StartArgument (Observer CounterChanged) (Processes q) = OCCStart+  newtype instance Model (Observer CounterChanged) = CounterChangedModel () deriving Default   update _ _ e =     case e of       OnCast (Observed msg) -> logInfo' ("observerRegistryNotify: " ++ show msg)
examples/example-embedded-protocols/Main.hs view
@@ -1,8 +1,7 @@ {-# LANGUAGE QuantifiedConstraints #-} {-# LANGUAGE UndecidableInstances #-}-{-# LANGUAGE NumericUnderscores #-} -- | Another  example for the library that uses embedded protocols with multiple server back--- ends and a polymorphic client, as well as the 'Supervisor.Sup' module to start multiple+-- ends and a polymorphic client, as well as the 'Broker' module to start multiple -- back-ends. -- -- @since 0.29.0@@ -12,10 +11,9 @@ import           Control.Eff import           Control.Eff.State.Lazy as State import           Control.Eff.Concurrent-import           Control.Eff.Concurrent.Process.Timer import           Control.Eff.Concurrent.Protocol.EffectfulServer as Effectful import           Control.Eff.Concurrent.Protocol.StatefulServer as Stateful-import           Control.Eff.Concurrent.Protocol.Supervisor as Supervisor+import           Control.Eff.Concurrent.Protocol.Broker as Broker import           Control.Lens import           Control.Monad import           Data.Dynamic@@ -24,6 +22,7 @@ import           Data.String import qualified Data.Text as T import GHC.Stack (HasCallStack)+import Data.Default  main :: IO () main =@@ -31,13 +30,13 @@  embeddedExample :: HasCallStack => Eff Effects () embeddedExample = do-  b1 <- Stateful.start InitBackend1-  b2Sup <- startBackend2Sup-  b2 <- Supervisor.spawnOrLookup @Backend2 b2Sup 1-  _ <- Supervisor.spawnOrLookup b2Sup 2-  b2_2 <- Supervisor.spawnOrLookup b2Sup 2-  b2_3 <- Supervisor.spawnOrLookup b2Sup 3-  app <- Stateful.start InitApp+  b1 <- Stateful.startLink InitBackend1+  b2Broker <- startBackend2Broker+  b2 <- Broker.spawnOrLookup @Backend2 b2Broker 1+  _ <- Broker.spawnOrLookup b2Broker 2+  b2_2 <- Broker.spawnOrLookup b2Broker 2+  b2_3 <- Broker.spawnOrLookup b2Broker 3+  app <- Stateful.startLink InitApp   cast app DoThis   cast app DoThis   cast app DoThis@@ -65,11 +64,11 @@   cast app DoThis   call app (SetBackend (Just (SomeBackend b2_2)))   cast app DoThis-  _ <- Supervisor.stopChild b2Sup 2+  _ <- Broker.stopChild b2Broker 2   cast app DoThis   call app (SetBackend (Just (SomeBackend b2_3)))   cast app DoThis-  Supervisor.stopSupervisor b2Sup+  Broker.stopBroker b2Broker   cast app DoThis  @@ -78,20 +77,20 @@ -- Application layer  instance Stateful.Server App Effects where-  type instance Model App = Maybe SomeBackend+  newtype instance Model App = MkApp (Maybe SomeBackend) deriving Default   data instance StartArgument App Effects = InitApp   update me _x e =     case e of       OnCall rt (SetBackend b) -> do         logInfo "setting backend"-        oldB <- getAndPutModel @App b+        MkApp oldB <- getAndPutModel @App (MkApp b)         traverse_ (`backendForgetObserver` me) oldB         traverse_ (`backendRegisterObserver` me) b         sendReply rt ()       OnCast (AppBackendEvent be) ->         logInfo ("got backend event: " <> T.pack (show be))       OnCast DoThis ->-        do m <- getModel @App+        do MkApp m <- getModel @App            case m of             Nothing -> logInfo "doing this without backend"             Just b -> handleInterrupts (logWarning . T.pack . show) $ do@@ -203,31 +202,34 @@  instance Stateful.Server Backend1 Effects where   type instance Protocol Backend1 = (Backend, ObserverRegistry BackendEvent)-  type instance Model Backend1 = (Int, ObserverRegistry BackendEvent)+  newtype instance Model Backend1 = MkBackend1 (Int, ObserverRegistry BackendEvent)   data instance StartArgument Backend1 Effects = InitBackend1-  setup _ _ = pure ( (0, emptyObserverRegistry), () )+  setup _ _ = pure ( MkBackend1 (0, emptyObserverRegistry), () )   update me _ e = do     model <- getModel @Backend1     case e of       OnCall rt (ToPduLeft GetBackendInfo) ->         sendReply           (toEmbeddedReplyTarget @(Stateful.Protocol Backend1) @Backend rt)-          ("Backend1 " <> show me <> " " <> show (model ^. _1))+          ("Backend1 " <> show me <> " " <> show (model ^. modelBackend1 . _1))       OnCast (ToPduLeft BackendWork) -> do         logInfo "working..."-        modifyModel @Backend1 (over _1 (+ 1))+        modifyModel @Backend1 (over (modelBackend1 . _1) (+ 1))       OnCast (ToPduRight x) -> do         logInfo "event registration stuff ..."-        zoomModel @Backend1 _2 (observerRegistryHandlePdu x)+        zoomModel @Backend1 (modelBackend1 . _2) (observerRegistryHandlePdu x)       OnDown pd -> do         logWarning (T.pack (show pd))-        wasObserver <- zoomModel @Backend1 _2 (observerRegistryRemoveProcess @BackendEvent (downProcess pd))+        wasObserver <- zoomModel @Backend1 (modelBackend1 . _2) (observerRegistryRemoveProcess @BackendEvent (downProcess pd))         when wasObserver $           logNotice "observer removed"       _ -> logWarning ("unexpected: " <> T.pack (show e)) --- Backend 2 is behind a supervisor+modelBackend1 :: Iso' (Model Backend1)  (Int, ObserverRegistry BackendEvent)+modelBackend1 = iso (\(MkBackend1 x) -> x) MkBackend1 +-- Backend 2 is behind a broker+ data Backend2 deriving Typeable  instance HasPdu Backend2 where@@ -286,10 +288,10 @@           logNotice "observer removed"       _ -> logWarning ("unexpected: " <> T.pack (show e)) -type instance Supervisor.ChildId Backend2 = Int+type instance Broker.ChildId Backend2 = Int -startBackend2Sup :: Eff Effects (Endpoint (Supervisor.Sup Backend2))-startBackend2Sup = Supervisor.startSupervisor (Supervisor.MkSupConfig (TimeoutMicros 1_000_000) InitBackend2)+startBackend2Broker :: Eff Effects (Endpoint (Broker.Broker Backend2))+startBackend2Broker = Broker.startLink (Broker.MkBrokerConfig (TimeoutMicros 1_000_000) InitBackend2)  -- EXPERIMENTING data EP a where
extensible-effects-concurrent.cabal view
@@ -1,6 +1,6 @@ cabal-version:  2.0 name:           extensible-effects-concurrent-version:        0.29.2+version:        0.30.0 description:    Please see the README on GitHub at <https://github.com/sheyll/extensible-effects-concurrent#readme> synopsis:       Message passing concurrency as extensible-effect homepage:       https://github.com/sheyll/extensible-effects-concurrent#readme@@ -40,10 +40,10 @@   build-depends:       async >= 2.2 && <3,       base >= 4.12 && <5,-      bytestring >= 0.10 && < 0.11,       data-default >= 0.7 && < 0.8,       deepseq >= 1.4 && < 1.5,       directory,+      hashable >= 1.2,       hostname,       exceptions >= 0.10 && < 0.11,       safe-exceptions >= 0.1 && < 0.2,@@ -51,14 +51,10 @@       time >= 1.8 && < 1.9,       mtl >= 2.2 && < 2.3,       containers >=0.5.8 && <0.7,-      QuickCheck,       lens >= 4.14 && < 4.18,-      parallel >= 3.2 && < 3.3,-      process >= 1.6 && < 1.7,       monad-control >= 1.0 && < 1.1,       extensible-effects >= 5 && < 6,       stm >= 2.4.5 && <2.6,-      tagged >= 0.8 && < 0.9,       transformers-base >= 0.4 && < 0.5,       text >= 1.2 && < 1.3,       network >= 2 && < 4,@@ -82,25 +78,26 @@                   Control.Eff.LogWriter.UDP,                   Control.Eff.LogWriter.UnixSocket,                   Control.Eff.Concurrent,-                  Control.Eff.Concurrent.Pure,-                  Control.Eff.Concurrent.SingleThreaded,+                  Control.Eff.Concurrent.Misc,                   Control.Eff.Concurrent.Protocol,                   Control.Eff.Concurrent.Protocol.CallbackServer,                   Control.Eff.Concurrent.Protocol.Client,                   Control.Eff.Concurrent.Protocol.EffectfulServer,                   Control.Eff.Concurrent.Protocol.StatefulServer,-                  Control.Eff.Concurrent.Protocol.Supervisor,+                  Control.Eff.Concurrent.Protocol.Broker,+                  Control.Eff.Concurrent.Protocol.Watchdog,                   Control.Eff.Concurrent.Protocol.Wrapper,                   Control.Eff.Concurrent.Process,                   Control.Eff.Concurrent.Process.Timer,                   Control.Eff.Concurrent.Process.ForkIOScheduler,                   Control.Eff.Concurrent.Process.Interactive,                   Control.Eff.Concurrent.Process.SingleThreadedScheduler,-                  Control.Eff.Concurrent.Protocol.Observer-                  Control.Eff.Concurrent.Protocol.Observer.Queue+                  Control.Eff.Concurrent.Protocol.Observer,+                  Control.Eff.Concurrent.Protocol.Observer.Queue,+                  Control.Eff.Concurrent.Pure,+                  Control.Eff.Concurrent.SingleThreaded   other-modules:-                Control.Eff.Concurrent.Misc,-                Control.Eff.Concurrent.Protocol.Supervisor.InternalState,+                Control.Eff.Concurrent.Protocol.Broker.InternalState,                 Paths_extensible_effects_concurrent   default-extensions:                      AllowAmbiguousTypes,@@ -120,6 +117,7 @@                      LambdaCase,                      OverloadedStrings,                      MultiParamTypeClasses,+                     NumericUnderscores,                      RankNTypes,                      ScopedTypeVariables,                      StandaloneDeriving,@@ -140,10 +138,8 @@   hs-source-dirs: examples/example-1   build-depends:                 base-              , data-default               , extensible-effects-concurrent               , extensible-effects-              , lens               , text               , deepseq               , pretty-types >= 0.2.3.1 && < 0.4@@ -159,6 +155,7 @@                       , GADTs                       , GeneralizedNewtypeDeriving                       , LambdaCase+                      , NumericUnderscores                       , OverloadedStrings                       , RankNTypes                       , ScopedTypeVariables@@ -190,6 +187,7 @@                       , FunctionalDependencies                       , GADTs                       , GeneralizedNewtypeDeriving+                      , NumericUnderscores                       , LambdaCase                       , RankNTypes                       , ScopedTypeVariables@@ -205,14 +203,9 @@   hs-source-dirs: examples/example-3   build-depends:                 base-              , data-default-              , directory               , extensible-effects-concurrent               , extensible-effects-              , filepath               , lens-              , text-              , pretty-types >= 0.2.3.1 && < 0.4   ghc-options: -Wall -threaded -fno-full-laziness   default-language: Haskell2010   default-extensions:   AllowAmbiguousTypes@@ -224,6 +217,7 @@                       , GADTs                       , GeneralizedNewtypeDeriving                       , LambdaCase+                      , NumericUnderscores                       , OverloadedStrings                       , RankNTypes                       , ScopedTypeVariables@@ -240,10 +234,6 @@                 base               , extensible-effects-concurrent               , extensible-effects-              , deepseq-              , text-              , deepseq-              , pretty-types >= 0.2.3.1 && < 0.4   ghc-options: -Wall -threaded -fno-full-laziness   default-language: Haskell2010   default-extensions:   AllowAmbiguousTypes@@ -255,6 +245,7 @@                       , GADTs                       , GeneralizedNewtypeDeriving                       , LambdaCase+                      , NumericUnderscores                       , OverloadedStrings                       , RankNTypes                       , ScopedTypeVariables@@ -271,10 +262,10 @@   build-depends:                 base               , deepseq+              , data-default               , extensible-effects-concurrent               , extensible-effects               , lens-              , pretty-types >= 0.2.3.1 && < 0.4               , text   ghc-options: -Wall -threaded -fno-full-laziness   default-language: Haskell2010@@ -288,6 +279,7 @@                       , GADTs                       , GeneralizedNewtypeDeriving                       , LambdaCase+                      , NumericUnderscores                       , OverloadedStrings                       , RankNTypes                       , ScopedTypeVariables@@ -312,9 +304,10 @@               , LoopTests               , ObserverTests               , ProcessBehaviourTestCases-              , SupervisorTests+              , BrokerTests               , SingleThreadedScheduler-  ghc-options: -Wall -threaded -fno-full-laziness+              , WatchdogTests+  ghc-options: -Wall -threaded -fno-full-laziness -Wno-missing-signatures   build-depends:                 async               , base@@ -347,6 +340,7 @@                       , GADTs                       , GeneralizedNewtypeDeriving                       , LambdaCase+                      , NumericUnderscores                       , OverloadedStrings                       , RankNTypes                       , ScopedTypeVariables
src/Control/Eff/Concurrent.hs view
@@ -81,6 +81,7 @@                                                 , fromProcessTitle                                                 , ProcessDetails(..)                                                 , fromProcessDetails+                                                , Timeout(TimeoutMicros, fromTimeoutMicros)                                                 , StrictDynamic()                                                 , toStrictDynamic                                                 , fromStrictDynamic@@ -89,6 +90,7 @@                                                 , ProcessId(..)                                                 , fromProcessId                                                 , yieldProcess+                                                , delay                                                 , sendMessage                                                 , sendAnyMessage                                                 , makeReference@@ -162,16 +164,20 @@                                                 , receiverPid                                                 ) import           Control.Eff.Concurrent.Process.Timer-                                                ( Timeout(fromTimeoutMicros)-                                                , TimerReference()+                                                ( TimerReference()                                                 , TimerElapsed(fromTimerElapsed)                                                 , sendAfter                                                 , startTimer+                                                , sendAfterWithTitle+                                                , startTimerWithTitle                                                 , selectTimerElapsed                                                 , cancelTimer                                                 , receiveAfter                                                 , receiveSelectedAfter                                                 , receiveSelectedWithMonitorAfter+                                                , receiveAfterWithTitle+                                                , receiveSelectedAfterWithTitle+                                                , receiveSelectedWithMonitorAfterWithTitle                                                 )  import           Control.Eff.Concurrent.Protocol
src/Control/Eff/Concurrent/Misc.hs view
@@ -36,14 +36,17 @@ -- -- @since 0.24.0 showSTypeRepPrec :: Int -> SomeTypeRep -> ShowS-showSTypeRepPrec d (SomeTypeRep tr) sIn =-  let (con, conArgs) = splitApps tr-   in case conArgs of-        [] -> showString (tyConName con) sIn-        _ ->-          showParen-            (d >= 10)-            (showString (tyConName con) . showChar ':' .-              foldr1 (\f acc -> showChar '-' . f . acc)-                     (showSTypeRepPrec 10 <$> conArgs))-            sIn+showSTypeRepPrec _d (SomeTypeRep tr) =+  let+      (con, conArgs) = splitApps tr++      renderArgs =+        foldr1 (\f acc -> showString ", " . f . acc)+               (showSTypeRepPrec 10 <$> conArgs)++   in  showString (tyConName con) .+          if not (null conArgs) then+                showChar '<'+              . renderArgs+              . showChar '>'+          else id
src/Control/Eff/Concurrent/Process.hs view
@@ -17,7 +17,7 @@     -- ** Process Effect Aliases   , SafeProcesses   , Processes-    -- ** Process Effect Contraints+    -- ** Process Effect Constraints   , HasProcesses   , HasSafeProcesses       -- ** Process Info@@ -25,6 +25,10 @@   , fromProcessTitle   , ProcessDetails(..)   , fromProcessDetails++      -- ** Process Timer+  , Timeout(TimeoutMicros, fromTimeoutMicros)+   , -- ** Message Data     StrictDynamic()   , toStrictDynamic@@ -39,6 +43,8 @@   , ProcessState(..)   -- ** Yielding   , yieldProcess+  -- ** Waiting/Sleeping+  , delay   -- ** Sending Messages   , sendMessage   , sendAnyMessage@@ -79,7 +85,7 @@   -- *** Interrupting Processes   , interrupt   , sendInterrupt-  -- *** Exitting Processes+  -- *** Exiting Processes   , exitBecause   , exitNormally   , exitWithError@@ -188,6 +194,11 @@   --   -- @since 0.12.0   YieldProcess :: Process r (ResumeProcess ())+  -- | Simply wait until the time in the given 'Timeout' has elapsed and return.+  --+  -- @since 0.30.0+  Delay :: Timeout -> Process r (ResumeProcess ())+   -- | Return the current 'ProcessId'   SelfPid :: Process r (ResumeProcess ProcessId)   -- | Start a new process, the new process will execute an effect, the function@@ -236,10 +247,19 @@   -- if one of the processes crashes (i.e. 'isCrash' returns 'True'), the other   -- is shutdown with the 'Interrupt' 'LinkedProcessCrashed'.   --+  -- You might wonder: Why not tearing down the linked process when exiting+  -- normally?+  -- I thought about this. If a process exits normally, it should have the+  -- opportunity to shutdown stuff explicitly.+  -- And if you want to make sure that there are no dangling child processes+  -- after e.g. a broker crash, you can always use 'monitor'.+  --   -- @since 0.12.0   Link :: ProcessId -> Process r (ResumeProcess ())   -- | Unlink the calling process from the other process.   --+  -- See 'Link' for a discussion on linking.+  --   -- @since 0.12.0   Unlink :: ProcessId -> Process r (ResumeProcess ()) @@ -254,6 +274,7 @@   showsPrec d = \case     FlushMessages -> showString "flush messages"     YieldProcess  -> showString "yield process"+    Delay t       -> showString "delay process until: " . shows t     SelfPid       -> showString "lookup the current process id"     Spawn   t _   -> showString "spawn a new process: " . shows t     SpawnLink t _ -> showString "spawn a new process and link to it" . shows t@@ -322,7 +343,16 @@ instance Show ProcessDetails where   showsPrec _ (MkProcessDetails t) = showString (T.unpack t) +-- | A number of micro seconds.+--+-- @since 0.12.0+newtype Timeout = TimeoutMicros {fromTimeoutMicros :: Int}+  deriving (NFData, Ord,Eq, Num, Integral, Real, Enum, Typeable) +instance Show Timeout where+  showsPrec d (TimeoutMicros t) =+    showParen (d >= 10) (showString "timeout: " . shows t . showString " µs")+ -- | Data flows between 'Process'es via these messages. -- -- This is just a newtype wrapper around 'Dynamic'.@@ -468,7 +498,8 @@     ProcessBooting              -- ^ The process has just been started but not                                 --   scheduled yet.   | ProcessIdle                 -- ^ The process yielded it's time slice-  | ProcessBusy                 -- ^ The process is busy with non-blocking+  | ProcessBusy                 -- ^ The process is busy with a non-blocking operation+  | ProcessBusySleeping         -- ^ The process is sleeping until the 'Timeout' given to 'Delay'   | ProcessBusyUpdatingDetails  -- ^ The process is busy with 'UpdateProcessDetails'   | ProcessBusySending          -- ^ The process is busy with sending a message   | ProcessBusySendingShutdown  -- ^ The process is busy with killing@@ -504,15 +535,20 @@  -- | Get the 'ExitRecovery' toExitRecovery :: Interrupt r -> ExitRecovery-toExitRecovery = \case-  NormalExitRequested           -> Recoverable-  (OtherProcessNotRunning    _)  -> Recoverable-  (TimeoutInterrupt       _)  -> Recoverable-  (LinkedProcessCrashed _)  -> Recoverable-  (ErrorInterrupt         _)  -> Recoverable-  ExitNormally              -> NoRecovery-  (ExitUnhandledError _) -> NoRecovery-  ExitProcessCancelled                    -> NoRecovery+toExitRecovery =+  \case+    NormalExitRequested -> Recoverable+    (NormalExitRequestedWith _) -> Recoverable+    (OtherProcessNotRunning _) -> Recoverable+    (TimeoutInterrupt _) -> Recoverable+    (LinkedProcessCrashed _) -> Recoverable+    (InterruptedBy _) -> Recoverable+    (ErrorInterrupt _) -> Recoverable+    ExitNormally -> NoRecovery+    (ExitNormallyWith _) -> NoRecovery+    (ExitUnhandledError _) -> NoRecovery+    (ExitProcessCancelled _) -> NoRecovery+    (ExitOtherProcessNotRunning _) -> NoRecovery  -- | This value indicates whether a process exited in way consistent with -- the planned behaviour or not.@@ -551,21 +587,36 @@     -- @since 0.13.2     NormalExitRequested       :: Interrupt 'Recoverable+    -- | Extension of 'ExitNormally' with a custom reason+    --+    -- @since 0.30.0+    NormalExitRequestedWith+      :: forall a . (Typeable a, Show a, NFData a) => a -> Interrupt 'Recoverable     -- | A process that should be running was not running.     OtherProcessNotRunning       :: ProcessId -> Interrupt 'Recoverable     -- | A 'Recoverable' timeout has occurred.     TimeoutInterrupt       :: String -> Interrupt 'Recoverable-    -- | A linked process is down+    -- | A linked process is down, see 'Link' for a discussion on linking.     LinkedProcessCrashed       :: ProcessId -> Interrupt 'Recoverable     -- | An exit reason that has an error message and is 'Recoverable'.     ErrorInterrupt       :: String -> Interrupt 'Recoverable+    -- | An interrupt with a custom message.+    --+    -- @since 0.30.0+    InterruptedBy+      :: forall a . (Typeable a, Show a, NFData a) => a -> Interrupt 'Recoverable     -- | A process function returned or exited without any error.     ExitNormally       :: Interrupt 'NoRecovery+    -- | A process function returned or exited without any error, and with a custom message+    --+    -- @since 0.30.0+    ExitNormallyWith+      :: forall a . (Typeable a, Show a, NFData a) => a -> Interrupt 'NoRecovery     -- | An error causes the process to exit immediately.     -- For example an unexpected runtime exception was thrown, i.e. an exception     -- derived from 'Control.Exception.Safe.SomeException'@@ -574,7 +625,10 @@       :: Text -> Interrupt 'NoRecovery     -- | A process shall exit immediately, without any cleanup was cancelled (e.g. killed, in 'Async.cancel')     ExitProcessCancelled-      :: Interrupt 'NoRecovery+      :: Maybe ProcessId -> Interrupt 'NoRecovery+    -- | A process that is vital to the crashed process was not running+    ExitOtherProcessNotRunning+      :: ProcessId -> Interrupt 'NoRecovery   deriving Typeable  -- | Return either 'ExitNormally' or 'interruptToExit' from a 'Recoverable' 'Interrupt';@@ -582,81 +636,106 @@ -- If the 'Interrupt' is 'NormalExitRequested' then return 'ExitNormally' interruptToExit :: Interrupt 'Recoverable -> Interrupt 'NoRecovery interruptToExit NormalExitRequested = ExitNormally+interruptToExit (NormalExitRequestedWith x) = (ExitNormallyWith x) interruptToExit x = ExitUnhandledError (pack (show x))  instance Show (Interrupt x) where   showsPrec d =-    showParen (d >= 9)-      . (\case-          NormalExitRequested        -> showString "interrupt: A normal exit was requested"-          OtherProcessNotRunning p    -> showString "interrupt: Another process is not running: " . showsPrec 10 p-          TimeoutInterrupt reason -> showString "interrupt: A timeout occured: " . showString reason-          LinkedProcessCrashed m ->-            showString "interrupt: A linked process " . showsPrec 10 m . showString " crashed"-          ErrorInterrupt reason   -> showString "interrupt: An error occured: " . showString reason-          ExitNormally          -> showString "exit: Process finished successfully"-          ExitUnhandledError w ->-            showString "exit: Unhandled " . showString (unpack w)-          ExitProcessCancelled -> showString "exit: The process was cancelled"-        )+    showParen (d >= 10) .+    (\case+       NormalExitRequested -> showString "interrupt: A normal exit was requested"+       NormalExitRequestedWith p -> showString "interrupt: A normal exit was requested: " . showsPrec 10 p+       OtherProcessNotRunning p -> showString "interrupt: Another process is not running: " . showsPrec 10 p+       TimeoutInterrupt reason -> showString "interrupt: A timeout occured: " . showString reason+       LinkedProcessCrashed m -> showString "interrupt: A linked process " . showsPrec 10 m . showString " crashed"+       InterruptedBy reason -> showString "interrupt: " . showsPrec 10 reason+       ErrorInterrupt reason -> showString "interrupt: An error occured: " . showString reason+       ExitNormally -> showString "exit: Process finished successfully"+       ExitNormallyWith reason -> showString "exit: Process finished successfully: " . showsPrec 10 reason+       ExitUnhandledError w -> showString "exit: Unhandled " . showString (unpack w)+       ExitProcessCancelled Nothing -> showString "exit: The process was cancelled by a runtime exception"+       ExitProcessCancelled (Just origin) -> showString "exit: The process was cancelled by: " . shows origin+       ExitOtherProcessNotRunning p -> showString "exit: Another process is not running: " . showsPrec 10 p)  instance Exc.Exception (Interrupt 'Recoverable) instance Exc.Exception (Interrupt 'NoRecovery )  instance NFData (Interrupt x) where-  rnf NormalExitRequested               = rnf ()-  rnf (OtherProcessNotRunning    !l)     = rnf l+  rnf NormalExitRequested             = rnf ()+  rnf (NormalExitRequestedWith   !l)  = rnf l+  rnf (OtherProcessNotRunning    !l)  = rnf l   rnf (TimeoutInterrupt       !l)     = rnf l-  rnf (LinkedProcessCrashed !l)     = rnf l+  rnf (LinkedProcessCrashed !l)       = rnf l   rnf (ErrorInterrupt         !l)     = rnf l-  rnf ExitNormally                  = rnf ()-  rnf (ExitUnhandledError !l) = rnf l-  rnf ExitProcessCancelled                        = rnf ()+  rnf (InterruptedBy         !l)    = rnf l+  rnf ExitNormally                    = rnf ()+  rnf (ExitNormallyWith !l)           = rnf l+  rnf (ExitUnhandledError !l)         = rnf l+  rnf (ExitProcessCancelled  !o)      = rnf o+  rnf (ExitOtherProcessNotRunning !l) = rnf l  instance Ord (Interrupt x) where-  compare NormalExitRequested          NormalExitRequested         = EQ-  compare NormalExitRequested          _                           = LT-  compare _                        NormalExitRequested             = GT-  compare (OtherProcessNotRunning l)    (OtherProcessNotRunning r) = compare l r-  compare (OtherProcessNotRunning _)    _                          = LT-  compare _                        (OtherProcessNotRunning    _)   = GT-  compare (TimeoutInterrupt l)       (TimeoutInterrupt r)          = compare l r-  compare (TimeoutInterrupt _) _                                   = LT-  compare _                        (TimeoutInterrupt _)            = GT-  compare (LinkedProcessCrashed l) (LinkedProcessCrashed r)        = compare l r-  compare (LinkedProcessCrashed _) _                               = LT-  compare _                        (LinkedProcessCrashed _)        = GT-  compare (ErrorInterrupt l)         (ErrorInterrupt         r)    = compare l r-  compare ExitNormally             ExitNormally                    = EQ-  compare ExitNormally             _                               = LT-  compare _                        ExitNormally                    = GT-  compare (ExitUnhandledError l) (ExitUnhandledError r)            = compare l r-  compare (ExitUnhandledError _ ) _                                = LT-  compare _                         (ExitUnhandledError _)         = GT-  compare ExitProcessCancelled  ExitProcessCancelled               = EQ+  compare NormalExitRequested          NormalExitRequested               = EQ+  compare NormalExitRequested          _                                 = LT+  compare _                        NormalExitRequested                   = GT+  compare (NormalExitRequestedWith _)    (NormalExitRequestedWith _)     = EQ+  compare (NormalExitRequestedWith _)    _                               = LT+  compare _                        (NormalExitRequestedWith    _)        = GT+  compare (OtherProcessNotRunning l)    (OtherProcessNotRunning r)       = compare l r+  compare (OtherProcessNotRunning _)    _                                = LT+  compare _                        (OtherProcessNotRunning    _)         = GT+  compare (TimeoutInterrupt l)       (TimeoutInterrupt r)                = compare l r+  compare (TimeoutInterrupt _) _                                         = LT+  compare _                        (TimeoutInterrupt _)                  = GT+  compare (LinkedProcessCrashed l) (LinkedProcessCrashed r)              = compare l r+  compare (LinkedProcessCrashed _) _                                     = LT+  compare _                        (LinkedProcessCrashed _)              = GT+  compare (InterruptedBy _) (InterruptedBy _)                        = EQ+  compare (InterruptedBy _) _                                          = LT+  compare _                        (InterruptedBy _)                   = GT+  compare (ErrorInterrupt l)         (ErrorInterrupt         r)          = compare l r+  compare ExitNormally             ExitNormally                          = EQ+  compare ExitNormally             _                                     = LT+  compare _                        ExitNormally                          = GT+  compare (ExitNormallyWith _) (ExitNormallyWith _)                      = EQ+  compare (ExitNormallyWith _ ) _                                        = LT+  compare _                         (ExitNormallyWith _)                 = GT+  compare (ExitUnhandledError l) (ExitUnhandledError r)                  = compare l r+  compare (ExitUnhandledError _ ) _                                      = LT+  compare _                         (ExitUnhandledError _)               = GT+  compare (ExitOtherProcessNotRunning l)  (ExitOtherProcessNotRunning r) = compare l r+  compare (ExitOtherProcessNotRunning _ ) _                              = LT+  compare _                         (ExitOtherProcessNotRunning _)       = GT+  compare (ExitProcessCancelled l)  (ExitProcessCancelled r)             = compare l r  instance Eq (Interrupt x) where-  (==) NormalExitRequested          NormalExitRequested          = True-  (==) (OtherProcessNotRunning l)    (OtherProcessNotRunning r)    = (==) l r-  (==) ExitNormally             ExitNormally             = True-  (==) (TimeoutInterrupt l)       (TimeoutInterrupt r)       = l == r+  (==) NormalExitRequested NormalExitRequested = True+  (==) (OtherProcessNotRunning l) (OtherProcessNotRunning r) = (==) l r+  (==) ExitNormally ExitNormally = True+  (==) (TimeoutInterrupt l) (TimeoutInterrupt r) = l == r   (==) (LinkedProcessCrashed l) (LinkedProcessCrashed r) = l == r-  (==) (ErrorInterrupt         l) (ErrorInterrupt         r) = (==) l r+  (==) (ErrorInterrupt l) (ErrorInterrupt r) = (==) l r   (==) (ExitUnhandledError l) (ExitUnhandledError r) = (==) l r-  (==) ExitProcessCancelled ExitProcessCancelled = True-  (==) _      _      = False+  (==) (ExitOtherProcessNotRunning l) (ExitOtherProcessNotRunning r) = (==) l r+  (==) (ExitProcessCancelled l) (ExitProcessCancelled r) = l == r+  (==) _ _ = False  -- | A predicate for linked process __crashes__. isProcessDownInterrupt :: Maybe ProcessId -> Interrupt r -> Bool-isProcessDownInterrupt mOtherProcess = \case-  NormalExitRequested         -> False-  OtherProcessNotRunning    _  -> False-  TimeoutInterrupt       _  -> False-  LinkedProcessCrashed p  -> maybe True (== p) mOtherProcess-  ErrorInterrupt         _  -> False-  ExitNormally            -> False-  ExitUnhandledError _ -> False-  ExitProcessCancelled -> False+isProcessDownInterrupt mOtherProcess =+  \case+    NormalExitRequested -> False+    NormalExitRequestedWith _ -> False+    OtherProcessNotRunning _ -> False+    TimeoutInterrupt _ -> False+    LinkedProcessCrashed p -> maybe True (== p) mOtherProcess+    InterruptedBy _ -> False+    ErrorInterrupt _ -> False+    ExitNormally -> False+    ExitNormallyWith _ -> False+    ExitUnhandledError _ -> False+    ExitProcessCancelled _ -> False+    ExitOtherProcessNotRunning _ -> False  -- | 'Interrupt's which are 'Recoverable'. type RecoverableInterrupt = Interrupt 'Recoverable@@ -669,7 +748,7 @@ -- This constrains the effect list to look like this: -- @[e1 ... eN, 'Interrupts', 'Process' [e(N+1) .. e(N+k)], e(N+1) .. e(N+k)]@ ----- It contrains @e@ beyond 'HasSafeProcesses' to encompass 'Interrupts'.+-- It constrains @e@ beyond 'HasSafeProcesses' to encompass 'Interrupts'. -- -- @since 0.27.1 type HasProcesses e inner = (HasSafeProcesses e inner, Member Interrupts e)@@ -683,7 +762,7 @@ -- This constrains the effect list to look like this: -- @[e1 ... eN, 'Process' [e(N+1) .. e(N+k)], e(N+1) .. e(N+k)]@ ----- It contrains @e@ to support the (only) 'Process' effect.+-- It constrains @e@ to support the (only) 'Process' effect. -- -- This is more relaxed that 'HasProcesses' since it does not require 'Interrupts'. --@@ -792,17 +871,21 @@  -- | Partition a 'SomeExitReason' back into either a 'NoRecovery' -- or a 'Recoverable' 'Interrupt'-fromSomeExitReason-  :: SomeExitReason -> Either (Interrupt 'NoRecovery) (Interrupt 'Recoverable)-fromSomeExitReason (SomeExitReason e) = case e of-  recoverable@NormalExitRequested          -> Right recoverable-  recoverable@(OtherProcessNotRunning    _) -> Right recoverable-  recoverable@(TimeoutInterrupt       _) -> Right recoverable-  recoverable@(LinkedProcessCrashed _) -> Right recoverable-  recoverable@(ErrorInterrupt         _) -> Right recoverable-  noRecovery@ExitNormally              -> Left noRecovery-  noRecovery@(ExitUnhandledError _) -> Left noRecovery-  noRecovery@ExitProcessCancelled -> Left noRecovery+fromSomeExitReason :: SomeExitReason -> Either (Interrupt 'NoRecovery) (Interrupt 'Recoverable)+fromSomeExitReason (SomeExitReason e) =+  case e of+    recoverable@NormalExitRequested -> Right recoverable+    recoverable@(NormalExitRequestedWith _) -> Right recoverable+    recoverable@(OtherProcessNotRunning _) -> Right recoverable+    recoverable@(TimeoutInterrupt _) -> Right recoverable+    recoverable@(LinkedProcessCrashed _) -> Right recoverable+    recoverable@(InterruptedBy _) -> Right recoverable+    recoverable@(ErrorInterrupt _) -> Right recoverable+    noRecovery@ExitNormally -> Left noRecovery+    noRecovery@(ExitNormallyWith _) -> Left noRecovery+    noRecovery@(ExitUnhandledError _) -> Left noRecovery+    noRecovery@(ExitProcessCancelled _) -> Left noRecovery+    noRecovery@(ExitOtherProcessNotRunning _) -> Left noRecovery  -- | Print a 'Interrupt' to 'Just' a formatted 'String' when 'isCrash' -- is 'True'.@@ -823,7 +906,7 @@ -- | Log the 'Interrupt's logProcessExit   :: forall e x . (Member Logs e, HasCallStack) => Interrupt x -> Eff e ()-logProcessExit (toCrashReason -> Just ex) = withFrozenCallStack (logError ex)+logProcessExit (toCrashReason -> Just ex) = withFrozenCallStack (logWarning ex) logProcessExit ex = withFrozenCallStack (logDebug (fromString (show ex)))  @@ -880,12 +963,25 @@   => Eff r () yieldProcess = executeAndResumeOrThrow YieldProcess ++-- | Simply block until the time in the 'Timeout' has passed.+--+-- @since 0.30.0+delay+  :: forall r q+   . ( HasProcesses r q+     , HasCallStack+     )+  => Timeout+  -> Eff r ()+delay = executeAndResumeOrThrow . Delay+ -- | Send a message to a process addressed by the 'ProcessId'. -- See 'SendMessage'. -- -- The message will be reduced to normal form ('rnf') by/in the caller process. sendMessage-  :: forall r q o+  :: forall o r q    . ( HasProcesses r q      , HasCallStack      , Typeable o@@ -958,6 +1054,8 @@  -- | Start a new process, and immediately link to it. --+-- See 'Link' for a discussion on linking.+-- -- @since 0.12.0 spawnLink   :: forall r q@@ -1199,7 +1297,7 @@ data ProcessDown =   ProcessDown     { downReference :: !MonitorReference-    , downReason    :: !SomeExitReason+    , downReason    :: !(Interrupt 'NoRecovery)     , downProcess   :: !ProcessId     }   deriving (Typeable, Generic, Eq, Ord)@@ -1251,6 +1349,8 @@ -- if one of the processes crashes (i.e. 'isCrash' returns 'True'), the other -- is shutdown with the 'Interrupt' 'LinkedProcessCrashed'. --+-- See 'Link' for a discussion on linking.+-- -- @since 0.12.0 linkProcess   :: forall r q@@ -1260,6 +1360,8 @@ linkProcess = executeAndResumeOrThrow . Link . force  -- | Unlink the calling process from the other process.+--+-- See 'Link' for a discussion on linking. -- -- @since 0.12.0 unlinkProcess
src/Control/Eff/Concurrent/Process/ForkIOScheduler.hs view
@@ -21,7 +21,7 @@   ) where -import           Control.Concurrent             ( yield )+import           Control.Concurrent             ( yield, threadDelay, forkIO, killThread ) import qualified Control.Concurrent.Async      as Async import           Control.Concurrent.Async       ( Async(..) ) import           Control.Concurrent.STM        as STM@@ -38,7 +38,7 @@ import           Control.Exception.Safe        as Safe import           Control.Lens import           Control.Monad                  ( void-                                                , when+                                                , when, unless                                                 ) import           Control.Monad.Trans.Control    ( MonadBaseControl(..)                                                 , control@@ -57,7 +57,6 @@ import qualified Data.Text                     as T import           GHC.Stack import           System.Timeout-import           Text.Printf  -- * Process Types @@ -128,46 +127,74 @@   traverse_ logDebug pm   logDebug $ "nextMonitorIndex: " <> nm +-- | Allocate a new 'MonitorReference'+nextMonitorReference :: ProcessId -> SchedulerState -> STM MonitorReference+nextMonitorReference target schedulerState = do+  aNewMonitorIndex <- readTVar (schedulerState ^. nextMonitorIndex)+  modifyTVar' (schedulerState ^. nextMonitorIndex) (+ 1)+  return (MonitorReference aNewMonitorIndex target)+ -- | Add monitor: If the process is dead, enqueue a 'ProcessDown' message into the -- owners message queue addMonitoring-  :: HasCallStack => ProcessId -> ProcessId -> SchedulerState -> STM MonitorReference-addMonitoring target owner schedulerState = do-  aNewMonitorIndex <- readTVar (schedulerState ^. nextMonitorIndex)-  modifyTVar' (schedulerState ^. nextMonitorIndex) (+ 1)-  let monitorRef = MonitorReference aNewMonitorIndex target-  when (target /= owner) $ do-    pt <- readTVar (schedulerState ^. processTable)-    if Map.member target pt-      then modifyTVar' (schedulerState ^. processMonitors)-                       (Set.insert (monitorRef, owner))-      else-        let processDownMessage =-              ProcessDown monitorRef (SomeExitReason (OtherProcessNotRunning target)) target-        in  enqueueMessageOtherProcess owner-                                       (toStrictDynamic processDownMessage)-                                       schedulerState-  return monitorRef+  :: HasCallStack => MonitorReference -> ProcessId -> SchedulerState -> STM Int+addMonitoring monitorRef@(MonitorReference _ target) owner schedulerState =+  if target == owner then pure 0+    else+      do+        pt <- readTVar (schedulerState ^. processTable)+        case Map.lookup target pt of+          Just targetProcessInfo ->+            do (_, targetState) <- readTVar (targetProcessInfo ^. processState)+               check (   targetState == ProcessShuttingDown+                      || targetState == ProcessBusyReceiving+                      || targetState == ProcessIdle)+               if targetState /= ProcessShuttingDown then do+                insertMonitoringReference >> pure 1+               else+                processAlreadyDead >> pure 2+          Nothing ->+            processAlreadyDead >> pure 3 -removeMonitoring :: HasCallStack => MonitorReference -> SchedulerState -> STM ()-removeMonitoring monitorRef schedulerState = modifyTVar'-  (schedulerState ^. processMonitors)-  (Set.filter (\(ref, _) -> ref /= monitorRef))+  where+    insertMonitoringReference =+      modifyTVar' (schedulerState ^. processMonitors)+                  (Set.insert (monitorRef, owner)) +    processAlreadyDead = do+      let processDownMessage =+            ProcessDown monitorRef (ExitOtherProcessNotRunning target) target+      wasEnqueued <- enqueueMessageOtherProcess owner+                                     (toStrictDynamic processDownMessage)+                                     schedulerState+      check wasEnqueued+ triggerAndRemoveMonitor-  :: ProcessId -> SomeExitReason -> SchedulerState -> STM ()+  :: ProcessId -> Interrupt 'NoRecovery -> SchedulerState -> STM [ProcessId] triggerAndRemoveMonitor downPid reason schedulerState = do   monRefs <- readTVar (schedulerState ^. processMonitors)-  traverse_ go monRefs+  catMaybes <$> traverse go (toList monRefs)  where-  go (mr, owner) = when-    (monitoredProcess mr == downPid)-    (do-      let processDownMessage = ProcessDown mr reason downPid-      enqueueMessageOtherProcess owner (toStrictDynamic processDownMessage) schedulerState-      removeMonitoring mr schedulerState-    )+  go (mr, owner) = do+    if monitoredProcess mr == downPid+     then do+        let processDownMessage = ProcessDown mr reason downPid+        wasEnqueued <-+          enqueueMessageOtherProcess+            owner+            (toStrictDynamic processDownMessage)+            schedulerState+        removeMonitoring mr schedulerState+        pure $ if wasEnqueued then Nothing else Just owner+     else+      pure Nothing +removeMonitoring :: MonitorReference -> SchedulerState -> STM ()+removeMonitoring monitorRef schedulerState = modifyTVar'+  (schedulerState ^. processMonitors)+  (Set.filter (\(ref, _) -> ref /= monitorRef))++ -- * Process Implementation instance Show ProcessInfo where   show p = "process info: " ++ show (p ^. processId)@@ -283,7 +310,7 @@   -> Eff SafeEffects (Interrupt 'NoRecovery)   -> Eff BaseEffects (Interrupt 'NoRecovery) handleProcess myProcessInfo actionToRun = fix-  (handle_relay' singleStep (\er _nextRef -> return er))+  (handle_relay' singleStep (\er _nextRef -> setMyProcessState ProcessShuttingDown >> return er))   actionToRun   0  where@@ -304,9 +331,9 @@ -- setMyProcessState st = do --  oldSt <- lift (atomically (readTVar myProcessStateVar <* setMyProcessStateSTM st)) --  logDebug ("state change: "<> show oldSt <> " -> " <> show st)-  setMyProcessStateSTM = modifyTVar myProcessStateVar . set _2+  setMyProcessStateSTM   = modifyTVar myProcessStateVar . set _2   setMyProcessDetailsSTM = modifyTVar myProcessStateVar . set _1-  myMessageQVar        = myProcessInfo ^. messageQ+  myMessageQVar          = myProcessInfo ^. messageQ   kontinueWith     :: forall s v a      . HasCallStack@@ -384,6 +411,7 @@     SelfPid                  -> diskontinue shutdownRequest     MakeReference            -> diskontinue shutdownRequest     YieldProcess             -> diskontinue shutdownRequest+    Delay           _        -> diskontinue shutdownRequest     Shutdown        r        -> diskontinue r     GetProcessState _        -> diskontinue shutdownRequest     UpdateProcessDetails _   -> diskontinue shutdownRequest@@ -413,6 +441,7 @@       SelfPid                  -> kontinue (Interrupted interruptRequest)       MakeReference            -> kontinue (Interrupted interruptRequest)       YieldProcess             -> kontinue (Interrupted interruptRequest)+      Delay           _        -> kontinue (Interrupted interruptRequest)       Shutdown        r        -> diskontinue r       GetProcessState _        -> kontinue (Interrupted interruptRequest)       UpdateProcessDetails _   -> kontinue (Interrupted interruptRequest)@@ -430,7 +459,7 @@     -> Eff BaseEffects a   interpretRequest kontinue diskontinue nextRef = \case     SendMessage toPid msg ->-      interpretSend toPid msg >>= kontinue nextRef . ResumeWith+      void (interpretSend toPid msg) >>= kontinue nextRef . ResumeWith     SendInterrupt toPid msg -> if toPid == myPid       then kontinue nextRef (Interrupted msg)       else@@ -453,11 +482,13 @@     ReceiveSelectedMessage f -> do       recvRes <- interpretReceive f       either diskontinue (kontinue nextRef) recvRes+    Shutdown r    -> diskontinue r     FlushMessages -> interpretFlush >>= kontinue nextRef     SelfPid       -> kontinue nextRef (ResumeWith myPid)     MakeReference -> kontinue (nextRef + 1) (ResumeWith nextRef)     YieldProcess  -> kontinue nextRef (ResumeWith ())-    Shutdown r    -> diskontinue r+    Delay t ->+      interpretDelay t >>= either diskontinue (kontinue nextRef)     GetProcessState toPid ->       interpretGetProcessState toPid >>= kontinue nextRef . ResumeWith     UpdateProcessDetails d ->@@ -472,7 +503,9 @@     interpretMonitor !target = do       setMyProcessState ProcessBusyMonitoring       schedulerState <- getSchedulerState-      lift (atomically (addMonitoring target myPid schedulerState))+      monitoringReference <- lift (atomically (nextMonitorReference target schedulerState))+      void $ lift (atomically (addMonitoring monitoringReference myPid schedulerState))+      return monitoringReference     interpretDemonitor !ref = do       setMyProcessState ProcessBusyMonitoring       schedulerState <- getSchedulerState@@ -539,6 +572,31 @@         myMessageQ <- readTVar myMessageQVar         modifyTVar' myMessageQVar (incomingMessages .~ Seq.Empty)         return (ResumeWith (toList (myMessageQ ^. incomingMessages)))+    interpretDelay+      :: Timeout+      -> Eff BaseEffects (Either (Interrupt 'NoRecovery) (ResumeProcess ()))+    interpretDelay (TimeoutMicros t) = do+      setMyProcessState ProcessBusySleeping+      lift $ do+        timeoutTVar <- newTVarIO False+        newDelayThreadId <- forkIO $ do+          atomically $ writeTVar timeoutTVar False+          threadDelay t+          atomically $ writeTVar timeoutTVar True+        (elapsed, res) <- atomically $ do+          myMessageQ <- readTVar myMessageQVar+          case myMessageQ ^. shutdownRequests of+            Nothing -> do+              done <- readTVar timeoutTVar+              unless done retry+              return (True, Right (ResumeWith ()))+            Just shutdownRequest -> do+              modifyTVar' myMessageQVar (shutdownRequests .~ Nothing)+              case fromSomeExitReason shutdownRequest of+                Left  sr -> return (False, Left sr)+                Right ir -> return (False, Right (Interrupted ir))+        unless elapsed (killThread newDelayThreadId)+        return res     interpretReceive       :: MessageSelector b       -> Eff BaseEffects (Either (Interrupt 'NoRecovery) (ResumeProcess b))@@ -623,15 +681,12 @@   logAppendProcInfo pid =     let addProcessId = over           lmProcessId-          (maybe (Just (T.pack (printf "% 9s" (show title ++ show pid)))) Just)+          (maybe (Just (T.pack (show title ++ show pid))) Just)     in  censorLogs @(Lift IO) addProcessId   triggerProcessLinksAndMonitors-    :: ProcessId -> Interrupt e -> TVar (Set ProcessId) -> Eff BaseEffects ()+    :: ProcessId -> Interrupt 'NoRecovery -> TVar (Set ProcessId) -> Eff BaseEffects ()   triggerProcessLinksAndMonitors !pid !reason !linkSetVar = do     schedulerState <- getSchedulerState-    lift $ atomically $ triggerAndRemoveMonitor pid-                                                (SomeExitReason reason)-                                                schedulerState     let exitSeverity = toExitSeverity reason         sendIt !linkedPid = do           let msg = SomeExitReason (LinkedProcessCrashed pid)@@ -656,6 +711,7 @@                           else                              return (Right (Right linkedPid))                         else return (Left linkedPid)+    downMessageSendResults <- lift . atomically $ triggerAndRemoveMonitor pid reason schedulerState     linkedPids <- lift       (atomically         (do@@ -666,14 +722,19 @@       )     res <- traverse sendIt (toList linkedPids)     traverse_-      (logDebug . either-        (("linked process no found: " <>) . T.pack . show)+      (either+        (logNotice . ("linked process not found: " <>) . T.pack . show)         (either-              (("linked process crashed, interrupting linked process: " <>) . T.pack . show)-              (("linked process exited peacefully, not sending shutdown to linked process: " <>) . T.pack . show)+              (logWarning . ("process crashed, interrupting linked process: " <>) . T.pack . show)+              (logDebug . ("linked process exited peacefully, not sending shutdown to linked process: " <>) . T.pack . show)         )       )       res+    when (not (null downMessageSendResults)) $+      logWarning+        (  "failed to enqueue monitor down messages for: "+        <> T.pack(show downMessageSendResults)+        )   doForkProc     :: ProcessInfo     -> SchedulerState@@ -718,13 +779,23 @@     )    where     exitReasonFromException exc = case Safe.fromException exc of-      Just Async.AsyncCancelled -> ExitProcessCancelled+      Just Async.AsyncCancelled -> ExitProcessCancelled Nothing       Nothing -> ExitUnhandledError (  "runtime exception: "                                     <> T.pack (prettyCallStack callStack)                                     <> " "                                     <> T.pack (Safe.displayException exc)                                     )     logExitAndTriggerLinksAndMonitors reason pid = do+      (_, currentState) <-+        lift (atomically+          (readTVar (procInfo ^. processState)+            <* modifyTVar' (procInfo ^. processState) (_2 .~ ProcessShuttingDown)))+      when (currentState /= ProcessShuttingDown)+        (logNotice ("aborted in state: "+          <> T.pack (show currentState)+          <> " by: "+          <> T.pack (show reason)+          ))       triggerProcessLinksAndMonitors pid reason (procInfo ^. processLinks)       logProcessExit reason       return reason@@ -734,13 +805,13 @@ getSchedulerState = ask  enqueueMessageOtherProcess-  :: HasCallStack => ProcessId -> StrictDynamic -> SchedulerState -> STM ()+  :: HasCallStack => ProcessId -> StrictDynamic -> SchedulerState -> STM Bool enqueueMessageOtherProcess toPid msg schedulerState =   view (at toPid) <$> readTVar (schedulerState ^. processTable) >>= maybe-    (return ())+    (return False)     (\toProcessTable -> do       modifyTVar' (toProcessTable ^. messageQ) (incomingMessages %~ (:|> msg))-      return ()+      return True     )  enqueueShutdownRequest
src/Control/Eff/Concurrent/Process/SingleThreadedScheduler.hs view
@@ -21,6 +21,7 @@ import           Control.Concurrent             ( yield ) import           Control.Eff import           Control.Eff.Extend+import           Control.Eff.Concurrent.Misc import           Control.Eff.Concurrent.Process import           Control.Eff.Log import           Control.Eff.LogWriter.IO@@ -42,8 +43,8 @@ import           Data.Foldable import           Data.Monoid import qualified Control.Monad.State.Strict    as State-import Data.Function (fix)-import Data.Dynamic (dynTypeRep)+import           Data.Function                  ( fix)+import           Data.Dynamic                   ( dynTypeRep )  -- ----------------------------------------------------------------------------- --  STS and ProcessInfo@@ -62,8 +63,9 @@       (d >= 10)       (appEndo          (Endo (showChar ' ' . shows pTitle . showString ": ") <>-          foldMap (Endo . shows . dynTypeRep . unwrapStrictDynamic) (toList pQ) <>-          Endo (shows pDetails)))+          foldMap (Endo . showSTypeRep . dynTypeRep . unwrapStrictDynamic) (toList pQ) <>+          Endo (shows pDetails))+      )  makeLenses ''ProcessInfo @@ -166,14 +168,14 @@       pt <- use msgQs       if Map.member target pt         then monitors %= Set.insert (mref, owner)-        else let pdown = ProcessDown mref (SomeExitReason (OtherProcessNotRunning target)) target+        else let pdown = ProcessDown mref (ExitOtherProcessNotRunning target) target               in State.modify' (enqueueMsg owner (toStrictDynamic pdown))     return mref  removeMonitoring :: MonitorReference -> STS m r -> STS m r removeMonitoring mref = monitors %~ Set.filter (\(ref, _) -> ref /= mref) -triggerAndRemoveMonitor :: ProcessId -> SomeExitReason -> STS m r -> STS m r+triggerAndRemoveMonitor :: ProcessId -> Interrupt 'NoRecovery -> STS m r -> STS m r triggerAndRemoveMonitor downPid reason = State.execState $ do   monRefs <- use monitors   traverse_ go monRefs@@ -294,7 +296,10 @@   OnFlushMessages :: (ResumeProcess [StrictDynamic] -> Eff r (OnYield r a))                   -> OnYield r a   OnYield :: (ResumeProcess () -> Eff r (OnYield r a))-         -> OnYield r a+          -> OnYield r a+  OnDelay :: Timeout+          -> (ResumeProcess () -> Eff r (OnYield r a))+          -> OnYield r a   OnSelf :: (ResumeProcess ProcessId -> Eff r (OnYield r a))          -> OnYield r a   OnSpawn :: Bool@@ -346,9 +351,10 @@   show = \case     OnFlushMessages _          -> "OnFlushMessages"     OnYield         _          -> "OnYield"+    OnDelay t _                -> "OnDelay " ++ show t     OnSelf          _          -> "OnSelf"-    OnSpawn False t _ _        -> "OnSpawn" ++ show t-    OnSpawn True  t _ _        -> "OnSpawn (link)" ++ show t+    OnSpawn False t _ _        -> "OnSpawn " ++ show t+    OnSpawn True  t _ _        -> "OnSpawn (link) " ++ show t     OnDone     _               -> "OnDone"     OnShutdown e               -> "OnShutdown " ++ show e     OnInterrupt e _            -> "OnInterrupt " ++ show e@@ -378,6 +384,7 @@        -> Eff r (OnYield r v)   cont k q FlushMessages                = return (OnFlushMessages (k . qApp q))   cont k q YieldProcess                 = return (OnYield (k . qApp q))+  cont k q (Delay t)                    = return (OnDelay t (k . qApp q))   cont k q SelfPid                      = return (OnSelf (k . qApp q))   cont k q (Spawn     t e             ) = return (OnSpawn False t e (k . qApp q))   cont k q (SpawnLink t e             ) = return (OnSpawn True t e (k . qApp q))@@ -426,7 +433,12 @@             handleProcess               (dropMsgQ                  pid-                 (triggerAndRemoveMonitor pid (either SomeExitReason (const (SomeExitReason ExitNormally)) res) stsNew))+                 (triggerAndRemoveMonitor+                  pid+                  (either+                    id+                    (const ExitNormally) res)+                    stsNew))               nextTargets    in case processState of         OnDone r -> handleExit (Right r)@@ -449,6 +461,7 @@                         OnSendShutdown _ _ _tk -> return (OnShutdown sr)                         OnFlushMessages _tk -> return (OnShutdown sr)                         OnYield _tk -> return (OnShutdown sr)+                        OnDelay _t _tk -> return (OnShutdown sr)                         OnSelf _tk -> return (OnShutdown sr)                         OnSend _ _ _tk -> return (OnShutdown sr)                         OnRecv _ _tk -> return (OnShutdown sr)@@ -503,6 +516,12 @@           let (msgs, newSts) = flushMsgs pid sts           nextK <- kontinue newSts k msgs           handleProcess newSts (rest :|> (nextK, pid))+        OnDelay t k ->+          if t <= 0 then do+               nextK <- kontinue sts k ()+               handleProcess sts (rest :|> (nextK, pid))+          else+               handleProcess sts (rest :|> (OnDelay (t - 1) k, pid))         recv@(OnRecv messageSelector k) ->           case receiveMsg pid messageSelector sts of             Nothing -> do@@ -556,6 +575,7 @@                 OnSendInterrupt _ _ tk -> tk (Interrupted sr)                 OnSendShutdown _ _ tk -> tk (Interrupted sr)                 OnFlushMessages tk -> tk (Interrupted sr)+                OnDelay _ tk -> tk (Interrupted sr)                 OnYield tk -> tk (Interrupted sr)                 OnSelf tk -> tk (Interrupted sr)                 OnSend _ _ tk -> tk (Interrupted sr)
src/Control/Eff/Concurrent/Process/Timer.hs view
@@ -1,35 +1,36 @@--- | Functions for timeouts when receiving messages.+-- | Functions for receive timeouts and delayed messages sending. ----- NOTE: If you use a single threaded scheduler, these functions will not work--- as expected. (This is an open TODO)+-- Based on the 'delay' function. -- -- @since 0.12.0 module Control.Eff.Concurrent.Process.Timer-  ( Timeout(TimeoutMicros, fromTimeoutMicros)-  , TimerReference()+  ( TimerReference()   , TimerElapsed(fromTimerElapsed)   , sendAfter   , startTimer+  , sendAfterWithTitle+  , startTimerWithTitle   , cancelTimer   , selectTimerElapsed   , receiveAfter   , receiveSelectedAfter   , receiveSelectedWithMonitorAfter+  , receiveAfterWithTitle+  , receiveSelectedAfterWithTitle+  , receiveSelectedWithMonitorAfterWithTitle   )-   -- , receiveSelectedAfter, receiveAnyAfter, sendMessageAfter)+ where  import           Control.Eff.Concurrent.Process-import           Control.Concurrent+import           Control.Eff.Concurrent.Misc import           Control.Eff import           Control.DeepSeq-import           Control.Monad.IO.Class import           Data.Typeable import           Data.Text as T import           Control.Applicative import           GHC.Stack - -- | Wait for a message of the given type for the given time. When no message -- arrives in time, return 'Nothing'. This is based on -- 'receiveSelectedAfter'.@@ -37,8 +38,7 @@ -- @since 0.12.0 receiveAfter   :: forall a r q-   . ( Lifted IO q-     , HasCallStack+   . ( HasCallStack      , HasProcesses r q      , Typeable a      , NFData a@@ -56,16 +56,23 @@ -- @since 0.12.0 receiveSelectedAfter   :: forall a r q-   . ( Lifted IO q-     , HasCallStack+   . ( HasCallStack      , HasProcesses r q      , Show a+     , Typeable a      )   => MessageSelector a   -> Timeout   -> Eff r (Either TimerElapsed a) receiveSelectedAfter sel t = do-  timerRef <- startTimer t+  let timerTitle =+        MkProcessTitle+          ("receive-timer-"+          <> pack (showSTypeable @a "")+          <> "-"+          <> pack (show t)+          )+  timerRef <- startTimerWithTitle timerTitle t   res      <- receiveSelectedMessage     (Left <$> selectTimerElapsed timerRef <|> Right <$> sel)   cancelTimer timerRef@@ -76,17 +83,87 @@ -- @since 0.22.0 receiveSelectedWithMonitorAfter   :: forall a r q-   . ( Lifted IO q-     , HasCallStack+   . ( HasCallStack      , HasProcesses r q      , Show a+     , Typeable a      )   => ProcessId   -> MessageSelector a   -> Timeout   -> Eff r (Either (Either ProcessDown TimerElapsed) a)-receiveSelectedWithMonitorAfter pid sel t = do-  timerRef <- startTimer t+receiveSelectedWithMonitorAfter pid sel t =+  let timerTitle =+        MkProcessTitle+          ("receive-timer-"+          <> pack (showSTypeable @a "")+          <> "-monitoring-"+          <> pack (show pid)+          <> "-"+          <> pack (show t)+          )+  in receiveSelectedWithMonitorAfterWithTitle pid sel t timerTitle+++-- | Wait for a message of the given type for the given time. When no message+-- arrives in time, return 'Nothing'. This is based on+-- 'receiveSelectedAfterWithTitle'.+--+-- @since 0.12.0+receiveAfterWithTitle+  :: forall a r q+   . ( HasCallStack+     , HasProcesses r q+     , Typeable a+     , NFData a+     , Show a+     )+  => Timeout+  -> ProcessTitle+  -> Eff r (Maybe a)+receiveAfterWithTitle t timerTitle =+  either (const Nothing) Just <$> receiveSelectedAfterWithTitle (selectMessage @a) t timerTitle++-- | Wait for a message of the given type for the given time. When no message+-- arrives in time, return 'Left' 'TimerElapsed'. This is based on+-- 'selectTimerElapsed' and 'startTimerWithTitle'.+--+-- @since 0.12.0+receiveSelectedAfterWithTitle+  :: forall a r q+   . ( HasCallStack+     , HasProcesses r q+     , Show a+     , Typeable a+     )+  => MessageSelector a+  -> Timeout+  -> ProcessTitle+  -> Eff r (Either TimerElapsed a)+receiveSelectedAfterWithTitle sel t timerTitle = do+  timerRef <- startTimerWithTitle timerTitle t+  res      <- receiveSelectedMessage+    (Left <$> selectTimerElapsed timerRef <|> Right <$> sel)+  cancelTimer timerRef+  return res++-- | Like 'receiveWithMonitorWithTitle' combined with 'receiveSelectedAfterWithTitle'.+--+-- @since 0.30.0+receiveSelectedWithMonitorAfterWithTitle+  :: forall a r q+   . ( HasCallStack+     , HasProcesses r q+     , Show a+     , Typeable a+     )+  => ProcessId+  -> MessageSelector a+  -> Timeout+  -> ProcessTitle+  -> Eff r (Either (Either ProcessDown TimerElapsed) a)+receiveSelectedWithMonitorAfterWithTitle pid sel t timerTitle = do+  timerRef <- startTimerWithTitle timerTitle t   res      <- withMonitor pid $ \pidMon -> do                 receiveSelectedMessage                   (   Left . Left  <$> selectProcessDown pidMon@@ -104,17 +181,6 @@ selectTimerElapsed timerRef =   filterMessage (\(TimerElapsed timerRefIn) -> timerRef == timerRefIn) ---- | A number of micro seconds.------ @since 0.12.0-newtype Timeout = TimeoutMicros {fromTimeoutMicros :: Int}-  deriving (NFData, Ord,Eq, Num, Integral, Real, Enum, Typeable)--instance Show Timeout where-  showsPrec d (TimeoutMicros t) =-    showParen (d >= 10) (showString "timeout: " . shows t . showString " µs")- -- | The reference to a timer started by 'startTimer', required to stop -- a timer via 'cancelTimer'. --@@ -135,9 +201,7 @@ instance Show TimerElapsed where   showsPrec d (TimerElapsed t) =     showParen (d >= 10) (shows t . showString " elapsed")----- @since 0.12.0 - -- | Send a message to a given process after waiting. The message is created by -- applying the function parameter to the 'TimerReference', such that the -- message can directly refer to the timer.@@ -145,8 +209,7 @@ -- @since 0.12.0 sendAfter   :: forall r q message-   . ( Lifted IO q-     , HasCallStack+   . ( HasCallStack      , HasProcesses r q      , Typeable message      , NFData message@@ -155,30 +218,74 @@   -> Timeout   -> (TimerReference -> message)   -> Eff r TimerReference-sendAfter pid (TimeoutMicros us) mkMsg =+sendAfter pid t mkMsg =+  sendAfterWithTitle+    (MkProcessTitle ("send-after-timer-" <> T.pack (show t) <> "-" <> T.pack (showSTypeable @message "") <> "-" <> T.pack (show pid)))+    pid+    t+    mkMsg++-- | Like 'sendAfter' but with a user provided name for the timer process.+--+-- @since 0.30.0+sendAfterWithTitle+  :: forall r q message+   . ( HasCallStack+     , HasProcesses r q+     , Typeable message+     , NFData message+     )+  => ProcessTitle+  -> ProcessId+  -> Timeout+  -> (TimerReference -> message)+  -> Eff r TimerReference+sendAfterWithTitle title pid t mkMsg =   TimerReference <$>-  spawn-    (MkProcessTitle ("after_" <> T.pack (show us) <> "us_to_" <> T.pack (show pid)))-    ((if us == 0-        then yieldProcess-        else liftIO (threadDelay us)) >>-     self >>=-     (sendMessage pid . force . mkMsg . TimerReference))+  (spawn+    title+    (delay t+    >>  self+    >>= (sendMessage pid . force . mkMsg . TimerReference)))+ -- | Start a new timer, after the time has elapsed, 'TimerElapsed' is sent to -- calling process. The message also contains the 'TimerReference' returned by -- this function. Use 'cancelTimer' to cancel the timer. Use -- 'selectTimerElapsed' to receive the message using 'receiveSelectedMessage'. -- To receive messages with guarded with a timeout see 'receiveAfter'. --+-- This calls 'sendAfterWithTitle' under the hood with 'TimerElapsed' as+-- message.+--+-- @since 0.30.0+startTimerWithTitle+  :: forall r q+   . ( HasCallStack+     , HasProcesses r q+     )+  => ProcessTitle+  -> Timeout+  -> Eff r TimerReference -- TODO add a parameter to the TimerReference+startTimerWithTitle title t = do+  p <- self+  sendAfterWithTitle title p t TimerElapsed++-- | Start a new timer, after the time has elapsed, 'TimerElapsed' is sent to+-- calling process. The message also contains the 'TimerReference' returned by+-- this function. Use 'cancelTimer' to cancel the timer. Use+-- 'selectTimerElapsed' to receive the message using 'receiveSelectedMessage'.+-- To receive messages with guarded with a timeout see 'receiveAfter'.+--+-- Calls 'sendAfter' under the hood.+-- -- @since 0.12.0 startTimer   :: forall r q-   . ( Lifted IO q-     , HasCallStack+   . ( HasCallStack      , HasProcesses r q      )   => Timeout-  -> Eff r TimerReference+  -> Eff r TimerReference -- TODO add a parameter to the TimerReference startTimer t = do   p <- self   sendAfter p t TimerElapsed@@ -188,8 +295,7 @@ -- @since 0.12.0 cancelTimer   :: forall r q-   . ( Lifted IO q-     , HasCallStack+   . ( HasCallStack      , HasProcesses r q      )   => TimerReference
+ src/Control/Eff/Concurrent/Protocol/Broker.hs view
@@ -0,0 +1,645 @@+{-# LANGUAGE UndecidableInstances #-}+-- | A process broker spawns and monitors child processes.+--+-- The child processes are mapped to symbolic identifier values: Child-IDs.+--+-- This is the barest, most minimal version of a broker. Children+-- can be started, but not restarted.+--+-- Children can efficiently be looked-up by an id-value, and when+-- the broker is shutdown, all children will be shutdown these+-- are actually all the features of this broker implementation.+--+-- Also, this minimalist broker only knows how to spawn a+-- single kind of child process.+--+-- When a broker spawns a new child process, it expects the+-- child process to return a 'ProcessId'. The broker will+-- 'monitor' the child process.+--+-- This is in stark contrast to how Erlang/OTP handles things;+-- In the OTP Supervisor, the child has to link to the parent.+-- This allows the child spec to be more flexible in that no+-- @pid@ has to be passed from the child start function to the+-- broker process, and also, a child may break free from+-- the broker by unlinking.+--+-- Now while this seems nice at first, this might actually+-- cause surprising results, since it is usually expected that+-- stopping a broker also stops the children, or that a child+-- exit shows up in the logging originating from the former+-- broker.+--+-- The approach here is to allow any child to link to the+-- broker to realize when the broker was violently killed,+-- and otherwise give the child no chance to unlink itself from+-- its broker.+--+-- This module is far simpler than the Erlang/OTP counter part,+-- of a @simple_one_for_one@ supervisor.+--+-- The future of this broker might not be a-lot more than+-- it currently is. The ability to restart processes might be+-- implemented outside of this broker module.+--+-- One way to do that is to implement the restart logic in+-- a separate module, since the child-id can be reused when a child+-- exits.+--+-- @since 0.23.0+module Control.Eff.Concurrent.Protocol.Broker+  ( startLink+  , statefulChild+  , stopBroker+  , isBrokerAlive+  , monitorBroker+  , getDiagnosticInfo+  , spawnChild+  , spawnOrLookup+  , lookupChild+  , callById+  , castById+  , stopChild+  , ChildNotFound(..)+  , Broker()+  , Pdu(StartC, StopC, LookupC, GetDiagnosticInfo)+  , ChildId+  , Stateful.StartArgument(MkBrokerConfig)+  , brokerConfigChildStopTimeout+  , SpawnErr(AlreadyStarted)+  , ChildEvent(OnChildSpawned, OnChildDown, OnBrokerShuttingDown)+  ) where++import Control.DeepSeq (NFData(rnf))+import Control.Eff as Eff+import Control.Eff.Concurrent.Protocol+import Control.Eff.Concurrent.Protocol.Client+import Control.Eff.Concurrent.Protocol.Wrapper+import Control.Eff.Concurrent.Protocol.Observer+import qualified Control.Eff.Concurrent.Protocol.EffectfulServer as Effectful+import qualified Control.Eff.Concurrent.Protocol.StatefulServer as Stateful+import Control.Eff.Concurrent.Protocol.Broker.InternalState+import Control.Eff.Concurrent.Process+import Control.Eff.Concurrent.Process.Timer+import Control.Eff.Extend (raise)+import Control.Eff.Log+import Control.Eff.State.Strict as Eff+import Control.Lens hiding ((.=), use)+import Data.Default+import Data.Dynamic+import Data.Foldable+import Data.Kind+import qualified Data.Map as Map+import Data.Text (Text, pack)+import Data.Type.Pretty+import GHC.Generics (Generic)+import GHC.Stack+import Control.Applicative ((<|>))++-- * Broker Server API++-- ** Functions++-- | Start and link a new broker process with the given 'SpawnFun'unction.+--+-- To spawn new child processes use 'spawnChild'.+--+-- @since 0.23.0+startLink+  :: forall p e+  . ( HasCallStack+    , LogIo (Processes e)+    , TangibleBroker p+    , Stateful.Server (Broker p) (Processes e)+    )+  => Stateful.StartArgument (Broker p) (Processes e)+  -> Eff (Processes e) (Endpoint (Broker p))+startLink = Stateful.startLink++-- | A smart constructor for 'MkBrokerConfig' that makes it easy to start a 'Stateful.Server' instance.+--+-- The user needs to instantiate @'ChildId' p@.+--+-- @since 0.30.0+statefulChild+  :: forall p e+  . ( HasCallStack+    , LogIo (Processes e)+    , TangibleBroker (Stateful.Stateful p)+    , Stateful.Server (Broker (Stateful.Stateful p)) (Processes e)+    )+  => Timeout+  -> (ChildId p -> Stateful.StartArgument p (Processes e))+  -> Stateful.StartArgument (Broker (Stateful.Stateful p)) (Processes e)+statefulChild t f = MkBrokerConfig t (Stateful.Init . f)++-- | Stop the broker and shutdown all processes.+--+-- Block until the broker has finished.+--+-- @since 0.23.0+stopBroker+  :: ( HasCallStack+     , HasProcesses e q+     , Member Logs e+     , Lifted IO e+     , TangibleBroker p+     )+  => Endpoint (Broker p)+  -> Eff e ()+stopBroker ep = do+  logInfo ("stopping broker: " <> pack (show ep))+  mr <- monitor (_fromEndpoint ep)+  sendInterrupt (_fromEndpoint ep) NormalExitRequested+  r <- receiveSelectedMessage (selectProcessDown mr)+  logInfo ("broker stopped: " <> pack (show ep) <> " " <> pack (show r))++-- | Check if a broker process is still alive.+--+-- @since 0.23.0+isBrokerAlive+  :: forall p q0 e .+     ( HasCallStack+     , Member Logs e+     , Typeable p+     , HasProcesses e q0+     )+  => Endpoint (Broker p)+  -> Eff e Bool+isBrokerAlive x = isProcessAlive (_fromEndpoint x)++-- | Monitor a broker process.+--+-- @since 0.23.0+monitorBroker+  :: forall p q0 e .+     ( HasCallStack+     , Member Logs e+     , HasProcesses e q0+     , TangibleBroker p+     )+  => Endpoint (Broker p)+  -> Eff e MonitorReference+monitorBroker x = monitor (_fromEndpoint x)++-- | Start and monitor a new child process using the 'SpawnFun' passed+-- to 'startLink'.+--+-- @since 0.23.0+spawnChild+  :: forall p q0 e .+     ( HasCallStack+     , Member Logs e+     , HasProcesses e q0+     , TangibleBroker p+     , Typeable (Effectful.ServerPdu p)+     )+  => Endpoint (Broker p)+  -> ChildId p+  -> Eff e (Either (SpawnErr p) (Endpoint (Effectful.ServerPdu p)))+spawnChild ep cId = call ep (StartC cId)++-- | Start and monitor a new child process using the 'SpawnFun' passed+-- to 'startLink'.+--+-- Call 'spawnChild' and unpack the 'Either' result,+-- ignoring the 'AlreadyStarted' error.+--+-- @since 0.29.2+spawnOrLookup+  :: forall p q0 e .+     ( HasCallStack+     , Member Logs e+     , HasProcesses e q0+     , TangibleBroker p+     , Typeable (Effectful.ServerPdu p)+     )+  => Endpoint (Broker p)+  -> ChildId p+  -> Eff e (Endpoint (Effectful.ServerPdu p))+spawnOrLookup supEp cId =+  do res <- spawnChild supEp cId+     pure $+      case res of+        Left (AlreadyStarted _ ep) -> ep+        Right ep -> ep++-- | Lookup the given child-id and return the output value of the 'SpawnFun'+--   if the client process exists.+--+-- @since 0.23.0+lookupChild ::+    forall p e q0 .+     ( HasCallStack+     , Member Logs e+     , HasProcesses e q0+     , TangibleBroker p+     , Typeable (Effectful.ServerPdu p)+     )+  => Endpoint (Broker p)+  -> ChildId p+  -> Eff e (Maybe (Endpoint (Effectful.ServerPdu p)))+lookupChild ep cId = call ep (LookupC @p cId)++-- | Stop a child process, and block until the child has exited.+--+-- Return 'True' if a process with that ID was found, 'False' if no process+-- with the given ID was running.+--+-- @since 0.23.0+stopChild ::+    forall p e q0 .+     ( HasCallStack+     , Member Logs e+     , HasProcesses e q0+     , TangibleBroker p+     )+  => Endpoint (Broker p)+  -> ChildId p+  -> Eff e Bool+stopChild ep cId = call ep (StopC @p cId (TimeoutMicros 4000000))++callById ::+    forall destination protocol result e q0 .+     ( HasCallStack+     , Member Logs e+     , HasProcesses e q0+     , Lifted IO e+     , Lifted IO q0+     , TangibleBroker protocol+     , TangiblePdu destination ( 'Synchronous result)+     , TangiblePdu protocol ( 'Synchronous result)+     , Embeds (Effectful.ServerPdu destination) protocol+     , Ord (ChildId destination)+     , Tangible (ChildId destination)+     , Typeable (Effectful.ServerPdu destination)+     , Tangible result+     , NFData (Pdu protocol ( 'Synchronous result))+     , NFData (Pdu (Effectful.ServerPdu destination) ( 'Synchronous result))+     , Show (Pdu (Effectful.ServerPdu destination) ( 'Synchronous result))+     )+  => Endpoint (Broker destination)+  -> ChildId destination+  -> Pdu protocol ( 'Synchronous result)+  -> Timeout+  -> Eff e result+callById broker cId pdu tMax =+  lookupChild broker cId+   >>=+    maybe+      (do logError ("callById failed for: " <> pack (show pdu))+          interrupt (InterruptedBy (ChildNotFound cId broker))+      )+      (\cEp -> callWithTimeout cEp pdu tMax)++data ChildNotFound child where+  ChildNotFound :: ChildId child -> Endpoint (Broker child) -> ChildNotFound child+  deriving (Typeable)++instance (Show (ChildId child), Typeable child) => Show (ChildNotFound child) where+  showsPrec d (ChildNotFound cId broker)+    = showParen (d >= 10) ( showString "child not found: "+                           . showsPrec 10 cId+                           . showString " at: "+                           . showsPrec 10 broker+                           )++instance NFData (ChildId c) => NFData (ChildNotFound c) where+  rnf (ChildNotFound cId broker) = rnf cId `seq` rnf broker `seq` ()++castById ::+    forall destination protocol e q0 .+     ( HasCallStack+     , Member Logs e+     , HasProcesses e q0+     , TangibleBroker protocol+     , TangiblePdu destination 'Asynchronous+     , TangiblePdu protocol 'Asynchronous+     )+  => Endpoint (Broker destination)+  -> ChildId destination+  -> Pdu protocol 'Asynchronous+  -> Eff e ()+castById = error "TODO"+++-- | Return a 'Text' describing the current state of the broker.+--+-- @since 0.23.0+getDiagnosticInfo+  :: forall p e q0 .+     ( HasCallStack+     , HasProcesses e q0+     , TangibleBroker p+     )+  => Endpoint (Broker p)+  -> Eff e Text+getDiagnosticInfo s = call s (GetDiagnosticInfo @p)++-- ** Types++-- | The index type of 'Server' supervisors.+--+-- A @'Broker' p@ manages the life cycle of the processes, running the @'Server' p@+-- methods of that specific type.+--+-- The broker maps an identifier value of type @'ChildId' p@ to an @'Endpoint' p@.+--+-- @since 0.24.0+data Broker (p :: Type) deriving Typeable++instance Typeable p => HasPdu (Broker p) where+  type instance EmbeddedPduList (Broker p) = '[ObserverRegistry (ChildEvent p)]+  -- | The 'Pdu' instance contains methods to start, stop and lookup a child+  -- process, as well as a diagnostic callback.+  --+  -- @since 0.23.0+  data instance  Pdu (Broker p) r where+          StartC :: ChildId p -> Pdu (Broker p) ('Synchronous (Either (SpawnErr p) (Endpoint (Effectful.ServerPdu p))))+          StopC :: ChildId p -> Timeout -> Pdu (Broker p) ('Synchronous Bool)+          LookupC :: ChildId p -> Pdu (Broker p) ('Synchronous (Maybe (Endpoint (Effectful.ServerPdu p))))+          GetDiagnosticInfo :: Pdu (Broker p) ('Synchronous Text)+          ChildEventObserverRegistry :: Pdu (ObserverRegistry (ChildEvent p)) r -> Pdu (Broker p) r+      deriving Typeable++instance (Typeable p, Show (ChildId p)) => Show (Pdu (Broker p) r) where+  showsPrec d (StartC c) = showParen (d >= 10) (showString "StartC " . showsPrec 10 c)+  showsPrec d (StopC c t) = showParen (d >= 10) (showString "StopC " . showsPrec 10 c . showChar ' ' . showsPrec 10 t)+  showsPrec d (LookupC c) = showParen (d >= 10) (showString "LookupC " . showsPrec 10 c)+  showsPrec _ GetDiagnosticInfo = showString "GetDiagnosticInfo"+  showsPrec d (ChildEventObserverRegistry c) = showParen (d >= 10) (showString "ChildEventObserverRegistry " . showsPrec 10 c)++instance (NFData (ChildId p)) => NFData (Pdu (Broker p) r) where+  rnf (StartC ci) = rnf ci+  rnf (StopC ci t) = rnf ci `seq` rnf t+  rnf (LookupC ci) = rnf ci+  rnf GetDiagnosticInfo = ()+  rnf (ChildEventObserverRegistry x) = rnf x++instance Typeable p => HasPduPrism (Broker p) (ObserverRegistry (ChildEvent p)) where+ embedPdu = ChildEventObserverRegistry+ fromPdu (ChildEventObserverRegistry x) = Just x+ fromPdu _ = Nothing++type instance ToPretty (Broker p) = "broker" <:> ToPretty p++-- | The event type to indicate that a child was started or stopped.+--+-- The need for this type originated for the watchdog functionality introduced+-- in 0.30.0.+-- The watch dog shall restart a crashed child, and in order to do so, it+-- must somehow monitor the child.+-- Since no order is specified in which processes get the 'ProcessDown' events,+-- a watchdog cannot monitor a child and restart it immediately, because it might+-- have received the process down event before the broker.+-- So instead the watchdog can simply use the broker events, and monitor only the+-- broker process.+--+-- @since 0.30.0+data ChildEvent p where+  OnChildSpawned :: Endpoint (Broker p) -> ChildId p -> Endpoint (Effectful.ServerPdu p) -> ChildEvent p+  OnChildDown :: Endpoint (Broker p) -> ChildId p -> Endpoint (Effectful.ServerPdu p) -> Interrupt 'NoRecovery -> ChildEvent p+  OnBrokerShuttingDown :: Endpoint (Broker p) -> ChildEvent p -- ^ The broker is shutting down and will soon begin stopping/killing its children+  deriving (Typeable, Generic)++instance (NFData (ChildId p)) => NFData (ChildEvent p)++instance (Typeable p, Typeable (Effectful.ServerPdu p), Show (ChildId p)) => Show (ChildEvent p) where+  showsPrec d x =+    case x of+     OnChildSpawned s i e ->+        showParen (d >= 10)+          (shows s . showString ": child-spawned: " . shows i . showChar ' ' . shows e)+     OnChildDown s i e r ->+        showParen (d >= 10)+          (shows s . showString ": child-down: " . shows i . showChar ' ' . shows e . showChar ' ' . showsPrec 10 r)+     OnBrokerShuttingDown s ->+        shows s . showString ": shutting down"++-- | The type of value used to index running 'Server' processes managed by a 'Broker'.+--+-- Note, that the type you provide must be 'Tangible'.+--+-- @since 0.24.0+type family ChildId p++type instance ChildId (Stateful.Stateful p) = ChildId p++-- | Constraints on the parameters to 'Broker'.+--+-- @since 0.24.0+type TangibleBroker p =+  ( Tangible (ChildId p)+  , Ord (ChildId p)+  , Typeable p+  )+++instance+  ( LogIo q+  , TangibleBroker p+  , Tangible (ChildId p)+  , Typeable (Effectful.ServerPdu p)+  , Effectful.Server p (Processes q)+  , HasProcesses (Effectful.ServerEffects p (Processes q)) q+  ) => Stateful.Server (Broker p) (Processes q) where++  -- | Options that control the 'Broker p' process.+  --+  -- This contains:+  --+  -- * a 'SpawnFun'+  -- * the 'Timeout' after requesting a normal child exit before brutally killing the child.+  --+  -- @since 0.24.0+  data instance StartArgument (Broker p) (Processes q) = MkBrokerConfig+    {+      brokerConfigChildStopTimeout :: Timeout+    , brokerConfigStartFun :: ChildId p -> Effectful.Init p (Processes q)+    }++  data instance Model (Broker p) =+    BrokerModel { _children :: Children (ChildId p) p+                , _childEventObserver :: ObserverRegistry (ChildEvent p)+                }+      deriving Typeable++  setup _ _cfg = pure (BrokerModel def emptyObserverRegistry, ())+  update _ _brokerConfig (Stateful.OnCast req) =+    case  req of+      ChildEventObserverRegistry x ->+        Stateful.zoomModel @(Broker p) childEventObserverLens (observerRegistryHandlePdu x)++  update me brokerConfig (Stateful.OnCall rt req) =+    case req of+      ChildEventObserverRegistry x ->+        logEmergency ("unreachable: " <> pack (show x))++      GetDiagnosticInfo -> zoomToChildren @p $ do+        p <- (pack . show <$> getChildren @(ChildId p) @p)+        sendReply rt p++      LookupC i -> zoomToChildren @p $ do+        p <- fmap (view childEndpoint) <$> lookupChildById @(ChildId p) @p i+        sendReply rt p++      StopC i t -> zoomToChildren @p $ do+        mExisting <- lookupAndRemoveChildById @(ChildId p) @p i+        case mExisting of+          Nothing -> sendReply rt False+          Just existingChild -> do+            reason <- stopOrKillChild i existingChild t+            sendReply rt True+            Stateful.zoomModel @(Broker p)+              childEventObserverLens+              (observerRegistryNotify @(ChildEvent p) (OnChildDown me i (existingChild^.childEndpoint) reason))++      StartC i -> do+        mExisting <- zoomToChildren @p $ lookupChildById @(ChildId p) @p i+        case mExisting of+          Nothing -> do+            childEp <- raise (raise (Effectful.startLink (brokerConfigStartFun brokerConfig i)))+            let childPid = _fromEndpoint childEp+            cMon <- monitor childPid+            zoomToChildren @p $ putChild i (MkChild @p childEp cMon)+            sendReply rt (Right childEp)+            Stateful.zoomModel @(Broker p)+              childEventObserverLens+              (observerRegistryNotify @(ChildEvent p) (OnChildSpawned me i childEp))+          Just existingChild ->+            sendReply rt (Left (AlreadyStarted i (existingChild ^. childEndpoint)))++  update me _brokerConfig (Stateful.OnDown pd) = do+      wasObserver <- Stateful.zoomModel @(Broker p)+        childEventObserverLens+        (observerRegistryRemoveProcess @(ChildEvent p) (downProcess pd))+      if wasObserver+       then logInfo ("observer process died: " <> pack (show pd))+       else do+        oldEntry <- zoomToChildren @p $ lookupAndRemoveChildByMonitor @(ChildId p) @p (downReference pd)+        case oldEntry of+          Nothing -> logWarning ("unexpected: " <> pack (show pd))+          Just (i, c) -> do+            logInfo (  pack (show pd)+                    <> " for child "+                    <> pack (show i)+                    <> " => "+                    <> pack (show (c^.childEndpoint))+                    )+            Stateful.zoomModel @(Broker p)+                childEventObserverLens+                (observerRegistryNotify @(ChildEvent p) (OnChildDown me i (c^.childEndpoint) (downReason pd)))+  update me brokerConfig (Stateful.OnInterrupt e) =+    case e of+      NormalExitRequested -> do+        logDebug ("broker stopping: " <> pack (show e))+        Stateful.zoomModel @(Broker p)+            childEventObserverLens+            (observerRegistryNotify @(ChildEvent p) (OnBrokerShuttingDown me))+        stopAllChildren @p me (brokerConfigChildStopTimeout brokerConfig)+        exitNormally+      LinkedProcessCrashed linked ->+        logNotice (pack (show linked))+      _ -> do+        logWarning ("broker interrupted: " <> pack (show e))+        Stateful.zoomModel @(Broker p)+            childEventObserverLens+            (observerRegistryNotify @(ChildEvent p) (OnBrokerShuttingDown me))+        stopAllChildren @p me (brokerConfigChildStopTimeout brokerConfig)+        exitBecause (interruptToExit e)++  update _ _brokerConfig o = logWarning ("unexpected: " <> pack (show o))+++zoomToChildren :: forall p c e+  . Member (Stateful.ModelState (Broker p)) e+  => Eff (State (Children (ChildId p) p) ': e) c+  -> Eff e c+zoomToChildren = Stateful.zoomModel @(Broker p) childrenLens++childrenLens :: Lens' (Stateful.Model (Broker p)) (Children (ChildId p) p)+childrenLens = lens _children (\(BrokerModel _ o) c -> BrokerModel c o)++childEventObserverLens :: Lens' (Stateful.Model (Broker p)) (ObserverRegistry (ChildEvent p))+childEventObserverLens = lens _childEventObserver (\(BrokerModel o _) c -> BrokerModel o c)++-- | Runtime-Errors occurring when spawning child-processes.+--+-- @since 0.23.0+data SpawnErr p = AlreadyStarted (ChildId p) (Endpoint (Effectful.ServerPdu p))+  deriving (Typeable, Generic)++deriving instance Eq (ChildId p) => Eq (SpawnErr p)+deriving instance Ord (ChildId p) => Ord (SpawnErr p)+deriving instance (Typeable (Effectful.ServerPdu p), Show (ChildId p)) => Show (SpawnErr p)++instance NFData (ChildId p) => NFData (SpawnErr p)++-- Internal Functions++stopOrKillChild+  :: forall p e q0 .+     ( HasCallStack+     , HasProcesses e q0+     , Lifted IO e+     , Lifted IO q0+     , Member Logs e+     , Member (Stateful.ModelState (Broker p)) e+     , TangibleBroker p+     , Typeable (Effectful.ServerPdu p)+     )+  => ChildId p+  -> Child p+  -> Timeout+  -> Eff e (Interrupt 'NoRecovery)+stopOrKillChild cId c stopTimeout =+      do+        broker <- asEndpoint @(Broker p) <$> self+        t <- startTimerWithTitle+                (MkProcessTitle ("child-exit-timer-" <> pack (show broker) <> "-" <> pack (show cId)))+                stopTimeout+        sendInterrupt (_fromEndpoint (c^.childEndpoint)) NormalExitRequested+        r1 <- receiveSelectedMessage (   Right <$> selectProcessDown (c^.childMonitoring)+                                     <|> Left  <$> selectTimerElapsed t )+        demonitor (c^.childMonitoring)+        unlinkProcess (_fromEndpoint (c^.childEndpoint))+        case r1 of+          Left timerElapsed -> do+            logWarning (pack (show timerElapsed) <> ": child "<> pack (show cId) <>" => " <> pack(show (c^.childEndpoint)) <>" did not shutdown in time")+            let reason = interruptToExit (TimeoutInterrupt+                                           ("child did not shut down in time and was terminated by the "+                                             ++ show broker))+            sendShutdown (_fromEndpoint (c^.childEndpoint)) reason+            return reason+          Right downMsg -> do+            logInfo ("child "<> pack (show cId) <>" => " <> pack(show (c^.childEndpoint)) <>" terminated: " <> pack (show (downReason downMsg)))+            return (downReason downMsg)++stopAllChildren+  :: forall p e q0 .+     ( HasCallStack+     , HasProcesses e q0+     , Lifted IO e+     , Lifted IO q0+     , Member Logs e+     , Member (Stateful.ModelState (Broker p)) e+     , TangibleBroker p+     , Typeable (Effectful.ServerPdu p)+     )+  => Endpoint (Broker p) -> Timeout -> Eff e ()+stopAllChildren me stopTimeout =+  zoomToChildren @p+    (removeAllChildren @(ChildId p) @p)+  >>= pure . Map.assocs+  >>= traverse_ killAndNotify+  where+    killAndNotify (cId, c) = do+      reason <- provideInterrupts (stopOrKillChild cId c stopTimeout) >>= either crash return+      Stateful.zoomModel @(Broker p)+          childEventObserverLens+          (observerRegistryNotify @(ChildEvent p) (OnChildDown me cId (c^.childEndpoint) reason))++      where+        crash e = do+          logError (pack (show e) <> " while stopping child: " <> pack (show cId) <> " " <> pack (show c))+          return (interruptToExit e)
+ src/Control/Eff/Concurrent/Protocol/Broker/InternalState.hs view
@@ -0,0 +1,117 @@+module Control.Eff.Concurrent.Protocol.Broker.InternalState+ ( Child(MkChild)+ , childMonitoring+ , childEndpoint+ , putChild+ , Children()+ , removeAllChildren+ , getChildren+ , lookupChildById+ , lookupAndRemoveChildById+ , lookupAndRemoveChildByMonitor+ )+ where++import Control.DeepSeq+import Control.Eff as Eff+import Control.Eff.Concurrent.Process+import Control.Eff.Concurrent.Protocol+import Control.Eff.Concurrent.Protocol.EffectfulServer+import Control.Eff.State.Strict as Eff+import Control.Lens hiding ((.=), use)+import Data.Default+import Data.Dynamic+import Data.Map (Map)+import GHC.Generics (Generic)+++data Child p = MkChild+  { _childEndpoint :: Endpoint (ServerPdu p)+  , _childMonitoring :: MonitorReference+  }+  deriving (Generic, Typeable, Eq, Ord)++instance NFData (Child o)++instance Typeable (ServerPdu p) => Show (Child p) where+  showsPrec d c = showParen (d>=10)+    (showString "process broker entry: " . shows (_childEndpoint c)  . showChar ' ' . shows (_childMonitoring c) )++makeLenses ''Child++-- | Internal state.+data Children i p = MkChildren+  { _childrenById :: Map i (Child p)+  , _childrenByMonitor :: Map MonitorReference (i, Child p)+  } deriving (Show, Generic, Typeable)++instance Default (Children i p) where+  def = MkChildren def def++instance (NFData i) => NFData (Children i p)++makeLenses ''Children++-- | State accessor+getChildren+  ::  (Ord i, Member (State (Children i o)) e)+  => Eff e (Children i o)+getChildren = Eff.get++putChild+  :: (Ord i, Member (State (Children i o)) e)+  => i+  -> Child o+  -> Eff e ()+putChild cId c = modify ( (childrenById . at cId .~ Just c)+                        . (childrenByMonitor . at (_childMonitoring c) .~ Just (cId, c))+                        )++lookupChildById+  :: (Ord i, Member (State (Children i o)) e)+  => i+  -> Eff e (Maybe (Child o))+lookupChildById i = view (childrenById . at i) <$> get++lookupChildByMonitor+  :: (Ord i, Member (State (Children i o)) e)+  => MonitorReference+  -> Eff e (Maybe (i, Child o))+lookupChildByMonitor m = view (childrenByMonitor . at m) <$> get++lookupAndRemoveChildById+  :: forall i o e. (Ord i, Member (State (Children i o)) e)+  => i+  -> Eff e (Maybe (Child o))+lookupAndRemoveChildById i =+  traverse go =<< lookupChildById i+  where+    go c = pure c <* removeChild i c++removeChild+  :: forall i o e. (Ord i, Member (State (Children i o)) e)+  => i+  -> Child o+  -> Eff e ()+removeChild i c = do+  modify @(Children i o) ( (childrenById . at i .~ Nothing)+                         . (childrenByMonitor . at (_childMonitoring c) .~ Nothing)+                         )++lookupAndRemoveChildByMonitor+  :: forall i o e. (Ord i, Member (State (Children i o)) e)+  => MonitorReference+  -> Eff e (Maybe (i, Child o))+lookupAndRemoveChildByMonitor r = do+  traverse go =<< lookupChildByMonitor r+  where+    go (i, c) = pure (i, c) <* removeChild i c++removeAllChildren+  :: forall i o e. (Ord i, Member (State (Children i o)) e)+  => Eff e (Map i (Child o))+removeAllChildren = do+  cm <- view childrenById <$> getChildren @i+  modify @(Children i o) (childrenById .~ mempty)+  modify @(Children i o) (childrenByMonitor .~ mempty)+  return cm
src/Control/Eff/Concurrent/Protocol/Client.hs view
@@ -26,6 +26,7 @@ import           Control.Eff.Concurrent.Process.Timer import           Control.Eff.Log import           Data.Typeable                  ( Typeable )+import           Data.Text (pack) import           GHC.Stack  @@ -104,8 +105,6 @@      , TangiblePdu protocol ( 'Synchronous result)      , Tangible result      , Member Logs r-     , Lifted IO q-     , Lifted IO r      , HasCallStack      , Embeds destination protocol      )@@ -125,7 +124,10 @@             extractResult (Reply origin' result) =               if origin == origin' then Just result else Nothing         in selectMessageWith extractResult-  resultOrError <- receiveSelectedWithMonitorAfter pidInternal selectResult timeOut+  let timerTitle = MkProcessTitle ( "call-timer-" <> pack (show serverP)+                                  <> "-" <> pack (show origin)+                                  <> "-" <> pack (show timeOut))+  resultOrError <- receiveSelectedWithMonitorAfterWithTitle pidInternal selectResult timeOut timerTitle   let onTimeout timerRef = do         let msg = "call timed out after "                   ++ show timeOut ++ " to server: "
src/Control/Eff/Concurrent/Protocol/EffectfulServer.hs view
@@ -63,8 +63,8 @@   -- Usually you should rely on the default implementation   serverTitle :: Init a e -> ProcessTitle -  default serverTitle :: Typeable (ServerPdu a) => Init a e -> ProcessTitle-  serverTitle _ = fromString $ showSTypeable @(ServerPdu a) "-server"+  default serverTitle :: Typeable a => Init a e -> ProcessTitle+  serverTitle _ = fromString $ showSTypeable @a ""    -- | Process the effects of the implementation   runEffects :: Endpoint (ServerPdu a) -> Init a e -> Eff (ServerEffects a e) x -> Eff e x@@ -125,13 +125,10 @@   => Init a (Processes q) -> Eff (Processes q) () protocolServerLoop a = do   myEp <- asEndpoint @(ServerPdu a) <$> self-  let myEpTxt = T.pack . show $ myEp-  censorLogs (lmAddEp myEpTxt) $ do-    logDebug ("starting")-    runEffects  myEp a (receiveSelectedLoop sel (mainLoop myEp))-    return ()+  logDebug ("starting")+  runEffects  myEp a (receiveSelectedLoop sel (mainLoop myEp))+  return ()   where-    lmAddEp myEp = lmProcessId ?~ myEp     sel :: MessageSelector (Event (ServerPdu a))     sel =  onRequest <$> selectMessage @(Request (ServerPdu a))        <|> OnDown    <$> selectMessage @ProcessDown@@ -158,12 +155,19 @@   -- use 'toEmbeddedReplyTarget' to convert a 'ReplyTarget' safely to the embedded protocol.   --   -- @since 0.24.1-  OnCall :: forall a r. (Tangible r, TangiblePdu a ('Synchronous r)) => ReplyTarget a r -> Pdu a ('Synchronous r) -> Event a-  OnCast :: forall a. TangiblePdu a 'Asynchronous => Pdu a 'Asynchronous -> Event a-  OnInterrupt  :: (Interrupt 'Recoverable) -> Event a-  OnDown  :: ProcessDown -> Event a-  OnTimeOut  :: TimerElapsed -> Event a-  OnMessage  :: StrictDynamic -> Event a+  OnCall+    :: forall a r. (Tangible r, TangiblePdu a ('Synchronous r))+    => ReplyTarget a r+    -> Pdu a ('Synchronous r)+    -> Event a+  OnCast+    :: forall a. TangiblePdu a 'Asynchronous+    => Pdu a 'Asynchronous+    -> Event a+  OnInterrupt :: Interrupt 'Recoverable -> Event a+  OnDown :: ProcessDown -> Event a+  OnTimeOut :: TimerElapsed -> Event a+  OnMessage :: StrictDynamic -> Event a   deriving Typeable  instance Show (Event a) where
src/Control/Eff/Concurrent/Protocol/Observer.hs view
@@ -75,7 +75,7 @@  instance Typeable event => Show (Observer event) where   showsPrec d (MkObserver (Arg x (MkObservationSink _ m))) =-    showParen (d>=10) (showSTypeable @event . showString "-observer: " . shows x . showChar ' ' . shows m )+    showParen (d>=10) (showString "observer: " . showSTypeable @event . showString " ". showsPrec 10 x . showChar ' ' . showsPrec 10 m )  type instance ToPretty (Observer event) =   PrettyParens ("observing" <:> ToPretty event)@@ -90,7 +90,7 @@  instance Show event => Show (Pdu (Observer event) r) where   showsPrec d (Observed event) =-    showParen (d >= 10) (showString "observered: " . shows event)+    showParen (d >= 10) (showString "observered: " . showsPrec 10 event)  -- | The Information necessary to wrap an 'Observed' event to a process specific -- message, e.g. the embedded 'Observer' 'Pdu' instance, and the 'MonitorReference' of
src/Control/Eff/Concurrent/Protocol/Observer/Queue.hs view
@@ -2,7 +2,7 @@ -- | A small process to capture and _share_ observation's by enqueueing them into an STM 'TBQueue'. module Control.Eff.Concurrent.Protocol.Observer.Queue   ( ObservationQueue(..)-  , ObservationQueueReader+  , Reader   , observe   , await   , tryRead@@ -12,13 +12,14 @@  import           Control.Concurrent.STM import           Control.Eff+import           Control.Eff.Concurrent.Misc import           Control.Eff.Concurrent.Protocol import           Control.Eff.Concurrent.Protocol.Observer import           Control.Eff.Concurrent.Protocol.StatefulServer import           Control.Eff.Concurrent.Process import           Control.Eff.ExceptionExtra     ( ) import           Control.Eff.Log-import           Control.Eff.Reader.Strict+import qualified Control.Eff.Reader.Strict     as Eff import           Control.Exception.Safe        as Safe import           Control.Lens import           Control.Monad.IO.Class@@ -26,16 +27,17 @@ import qualified Data.Text                     as T import           Data.Typeable import           GHC.Stack+import Data.Default (Default)  -- | Contains a 'TBQueue' capturing observations. -- See 'observe'. newtype ObservationQueue a = ObservationQueue (TBQueue a)  -- | A 'Reader' for an 'ObservationQueue'.-type ObservationQueueReader a = Reader (ObservationQueue a)+type Reader a = Eff.Reader (ObservationQueue a)  logPrefix :: forall event proxy . (HasCallStack, Typeable event) => proxy event -> T.Text-logPrefix px = "observation queue: " <> T.pack (show (typeRep px))+logPrefix _px = "observation queue: " <> T.pack (showSTypeable @event "")  -- | Read queued observations captured and enqueued in the shared 'ObservationQueue' by 'observe'. --@@ -45,7 +47,7 @@ -- @since 0.28.0 await   :: forall event r-   . ( Member (ObservationQueueReader event) r+   . ( Member (Reader event) r      , HasCallStack      , MonadIO (Eff r)      , Typeable event@@ -53,7 +55,7 @@      )   => Eff r event await = do-  ObservationQueue q <- ask @(ObservationQueue event)+  ObservationQueue q <- Eff.ask @(ObservationQueue event)   liftIO (atomically (readTBQueue q))  -- | Read queued observations captured and enqueued in the shared 'ObservationQueue' by 'observe'.@@ -64,7 +66,7 @@ -- @since 0.28.0 tryRead   :: forall event r-   . ( Member (ObservationQueueReader event) r+   . ( Member (Reader event) r      , HasCallStack      , MonadIO (Eff r)      , Typeable event@@ -72,7 +74,7 @@      )   => Eff r (Maybe event) tryRead = do-  ObservationQueue q <- ask @(ObservationQueue event)+  ObservationQueue q <- Eff.ask @(ObservationQueue event)   liftIO (atomically (tryReadTBQueue q))  -- | Read at once all currently queued observations captured and enqueued@@ -84,7 +86,7 @@ -- @since 0.28.0 flush   :: forall event r-   . ( Member (ObservationQueueReader event) r+   . ( Member (Reader event) r      , HasCallStack      , MonadIO (Eff r)      , Typeable event@@ -92,7 +94,7 @@      )   => Eff r [event] flush = do-  ObservationQueue q <- ask @(ObservationQueue event)+  ObservationQueue q <- Eff.ask @(ObservationQueue event)   liftIO (atomically (flushTBQueue q))  -- | Listen to, and capture observations in an 'ObservationQueue'.@@ -113,7 +115,7 @@ -- -- foo = --   do---     observed <- start SomeObservable+--     observed <- startLink SomeObservable --     OQ.observe 100 observed $ do --       ... --       cast observed DoSomething@@ -123,11 +125,14 @@ -- -- @since 0.28.0 observe-  :: forall event eventSource e q len b+  :: forall event eventSource e q len b h   . ( HasCallStack     , HasProcesses e q-    , LogIo q-    , LogIo e+    , LogsTo h e+    , LogsTo h q+    , LogsTo h (Processes q)+    , Lifted IO e+    , Lifted IO q     , IsObservable eventSource event     , Integral len     , Server (ObservationQueue event) (Processes q)@@ -135,7 +140,7 @@     )   => len   -> Endpoint eventSource-  -> Eff (ObservationQueueReader event ': e) b+  -> Eff (Reader event ': e) b   -> Eff e b observe queueLimit eventSource e =   withObservationQueue queueLimit (withWriter @event eventSource e)@@ -152,12 +157,12 @@      , Member Interrupts e      )   => len-  -> Eff (ObservationQueueReader event ': e) b+  -> Eff (Reader event ': e) b   -> Eff e b withObservationQueue queueLimit e = do   q   <- lift (newTBQueueIO (fromIntegral queueLimit))   res <- handleInterrupts (return . Left)-                          (Right <$> runReader (ObservationQueue q) e)+                          (Right <$> Eff.runReader (ObservationQueue q) e)   rest <- lift (atomically (flushTBQueue q))   unless     (null rest)@@ -185,7 +190,7 @@   => ObservationQueue event   -> Eff r (Endpoint (Observer event)) spawnWriter q =-  start @_ @r @q @h (MkObservationQueue q)+  startLink @_ @r @q @h (MkObservationQueue q)  -- | Spawn a process that can be used as an 'Observer' that enqueues the observations into an --   'ObservationQueue'. See 'withObservationQueue'.@@ -196,19 +201,21 @@ -- -- @since 0.28.0 withWriter-  :: forall event eventSource e q b+  :: forall event eventSource e q b h   . ( HasCallStack     , HasProcesses e q-    , LogIo q+    , Lifted IO q+    , LogsTo h (Processes q)+    , Member Logs q     , IsObservable eventSource event-    , Member (ObservationQueueReader event) e+    , Member (Reader event) e     , Tangible (Pdu eventSource 'Asynchronous)     )   => Endpoint eventSource   -> Eff e b   -> Eff e b withWriter eventSource e = do-  q <- ask @(ObservationQueue event)+  q <- Eff.ask @(ObservationQueue event)   w <- spawnWriter @event q   registerObserver @event eventSource w   res <- e@@ -222,6 +229,8 @@    data instance StartArgument (ObservationQueue event) (Processes q) =      MkObservationQueue (ObservationQueue event)++  newtype instance Model (ObservationQueue event) = MkObservationQueueModel () deriving Default    update _ (MkObservationQueue (ObservationQueue q)) =     \case
src/Control/Eff/Concurrent/Protocol/StatefulServer.hs view
@@ -5,8 +5,8 @@   ( Server(..)   , Stateful   , Effectful.Init(..)-  , start   , startLink+  , start   , ModelState   , modifyModel   , getAndModifyModel@@ -15,29 +15,38 @@   , putModel   , getAndPutModel   , useModel+  , preuseModel   , zoomModel+  , logModel   , SettingsReader   , askSettings   , viewSettings+  , mapEffects+  , coerceEffects   -- * Re-exports   , Effectful.Event(..)   )   where  import Control.Eff-import Control.Eff.Extend ()+import Control.Eff.Concurrent.Misc import Control.Eff.Concurrent.Process import Control.Eff.Concurrent.Protocol import qualified Control.Eff.Concurrent.Protocol.EffectfulServer as Effectful+import Control.Eff.Extend () import Control.Eff.Log import Control.Eff.Reader.Strict import Control.Eff.State.Strict import Control.Lens+import Data.Coerce import Data.Default import Data.Kind+import Data.String (fromString)+import Data.Text (Text, pack) import Data.Typeable import GHC.Stack (HasCallStack)-+import Control.Eff.Extend (raise)+import Data.Monoid (First) -- | A type class for server loops. -- -- This class serves as interface for other mechanisms, for example /process supervision/@@ -68,12 +77,21 @@   -- via the 'ModelState' effect.   -- The /model/ of a server loop is changed through incoming 'Event's.   -- It is initially calculated by 'setup'.-  type Model a :: Type-  type Model a = ()+  data family Model a :: Type+   -- | Type of read-only state.   type Settings a :: Type   type Settings a = () +  -- | Return a new 'ProcessTitle' for the stateful process,+  -- while it is running.+  --+  -- @since 0.30.0+  title :: StartArgument a q -> ProcessTitle++  default title :: Typeable a => StartArgument a q -> ProcessTitle+  title _ = fromString $ showSTypeable @a ""+   -- | Return an initial 'Model' and 'Settings'   setup ::        Endpoint (Protocol a)@@ -113,11 +131,12 @@    onEvent selfEndpoint (Init sa) = update selfEndpoint sa +  serverTitle (Init startArg) = title startArg  -- | Execute the server loop. -- -- @since 0.24.0-start+startLink   :: forall a r q h   . ( HasCallStack     , Typeable a@@ -127,23 +146,23 @@     , HasProcesses r q     )   => StartArgument a (Processes q) -> Eff r (Endpoint (Protocol a))-start = Effectful.start . Init+startLink = Effectful.startLink . Init --- | Execute the server loop.+-- | Execute the server loop. Please use 'startLink' if you can. -- -- @since 0.24.0-startLink+start   :: forall a r q h   . ( HasCallStack     , Typeable a-    , LogsTo h (Processes q)     , Effectful.Server (Stateful a) (Processes q)     , Server a (Processes q)+    , HandleLogWriter h+    , LogsTo h (Processes q)     , HasProcesses r q     )   => StartArgument a (Processes q) -> Eff r (Endpoint (Protocol a))-startLink = Effectful.startLink . Init-+start = Effectful.start . Init  -- | The 'Eff'ect type of mutable 'Model' in a 'Server' instance. --@@ -180,6 +199,12 @@ useModel :: forall a b e . Member (ModelState a) e => Getting b (Model a) b -> Eff e b useModel l = view l <$> getModel @a +-- | Return a element selected by a 'Lens' of the 'Model' of a 'Server'.+--+-- @since 0.30.0+preuseModel :: forall a b e . Member (ModelState a) e => Getting (First b) (Model a) b -> Eff e (Maybe b)+preuseModel l = preview l <$> getModel @a+ -- | Overwrite the 'Model' of a 'Server'. -- -- @since 0.24.0@@ -202,6 +227,18 @@   modifyModel @a (l .~ m1)   return c +-- | Log the 'Model' of a 'Server' using 'logDebug'.+--+-- @since 0.30.0+logModel+  :: forall m e q. ( Show (Model m)+                   , Member Logs e+                   , HasProcesses e q+                   , Member (ModelState m) e)+  => Text -> Eff e ()+logModel x =+  getModel @m >>= logDebug . (x <>) . pack . show+ -- | The 'Eff'ect type of readonly 'Settings' in a 'Server' instance. -- -- @since 0.24.0@@ -218,3 +255,57 @@ -- @since 0.24.0 viewSettings :: forall a b e . Member (SettingsReader a) e =>  Getting b (Settings a) b -> Eff e b viewSettings l = view l <$> askSettings @a++-- | Map 'ModelState' and 'SettingsReader' effects.+-- Use this to embed 'update' from another 'Server' instance.+--+-- @since 0.30.0+mapEffects+  :: forall inner outer a e.+     (Settings outer -> Settings inner) -- ^ A function to get the /inner/ settings out of the /outer/ settings+  -> Lens' (Model outer) (Model inner)  -- ^ A 'Lens' to get and set the /inner/ model inside the /outer/ model+  -> Eff (ModelState inner : SettingsReader inner : e) a+  -> Eff (ModelState outer : SettingsReader outer : e) a+mapEffects innerSettings innerStateLens innerEff =+  do st0 <- getModel @outer+     s0 <- askSettings @outer+     (res, st1) <-+      raise+        (raise+          (runReader+            @(Settings inner)+            (innerSettings s0)+            (runState+              @(Model inner)+              (st0 ^. innerStateLens)+              innerEff)))+     modifyModel @outer (innerStateLens .~ st1)+     return res+++-- | Coerce 'Coercible' 'ModelState' and 'SettingsReader' effects.+-- Use this to embed 'update' from a /similar/ 'Server' instance.+--+-- @since 0.30.0+coerceEffects+  :: forall inner outer a e.+     ( Coercible (Model inner) (Model outer)+     , Coercible (Model outer) (Model inner)+     , Coercible (Settings outer) (Settings inner)+     )+  => Eff (ModelState inner : SettingsReader inner : e) a+  -> Eff (ModelState outer : SettingsReader outer : e) a+coerceEffects innerEff =+  do st0 <- getModel @outer+     s0 <- askSettings @outer+     (res, st1) <-+      raise+        (raise+          (runReader+            @(Settings inner)+            (coerce s0)+            (runState+              (coerce @(Model outer) st0)+              innerEff)))+     putModel @outer (coerce st1)+     return res
− src/Control/Eff/Concurrent/Protocol/Supervisor.hs
@@ -1,457 +0,0 @@-{-# LANGUAGE UndecidableInstances #-}--- | A process supervisor spawns and monitors child processes.------ The child processes are mapped to symbolic identifier values: Child-IDs.------ This is the barest, most minimal version of a supervisor. Children--- can be started, but not restarted.------ Children can efficiently be looked-up by an id-value, and when--- the supervisor is shutdown, all children will be shutdown these--- are actually all the features of this supervisor implementation.------ Also, this minimalist supervisor only knows how to spawn a--- single kind of child process.------ When a supervisor spawns a new child process, it expects the--- child process to return a 'ProcessId'. The supervisor will--- 'monitor' the child process.------ This is in stark contrast to how Erlang/OTP handles things;--- In the OTP Supervisor, the child has to link to the parent.--- This allows the child spec to be more flexible in that no--- @pid@ has to be passed from the child start function to the--- supervisor process, and also, a child may break free from--- the supervisor by unlinking.------ Now while this seems nice at first, this might actually--- cause surprising results, since it is usually expected that--- stopping a supervisor also stops the children, or that a child--- exit shows up in the logging originating from the former--- supervisor.------ The approach here is to allow any child to link to the--- supervisor to realize when the supervisor was violently killed,--- and otherwise give the child no chance to unlink itself from--- its supervisor.------ This module is far simpler than the Erlang/OTP counter part,--- of a @simple_one_for_one@ supervisor.------ The future of this supervisor might not be a-lot more than--- it currently is. The ability to restart processes might be--- implemented outside of this supervisor module.------ One way to do that is to implement the restart logic in--- a separate module, since the child-id can be reused when a child--- exits.------ @since 0.23.0-module Control.Eff.Concurrent.Protocol.Supervisor-  ( startSupervisor-  , stopSupervisor-  , isSupervisorAlive-  , monitorSupervisor-  , getDiagnosticInfo-  , spawnChild-  , spawnOrLookup-  , lookupChild-  , stopChild-  , Sup()-  , ChildId-  , Stateful.StartArgument(MkSupConfig)-  , supConfigChildStopTimeout-  , SpawnErr(AlreadyStarted)-  ) where--import Control.DeepSeq (NFData(rnf))-import Control.Eff as Eff-import Control.Eff.Concurrent.Protocol-import Control.Eff.Concurrent.Protocol.Client-import Control.Eff.Concurrent.Protocol.Wrapper-import qualified Control.Eff.Concurrent.Protocol.EffectfulServer as Effectful-import qualified Control.Eff.Concurrent.Protocol.StatefulServer as Stateful-import Control.Eff.Concurrent.Protocol.Supervisor.InternalState-import Control.Eff.Concurrent.Process-import Control.Eff.Concurrent.Process.Timer-import Control.Eff.Extend (raise)-import Control.Eff.Log-import Control.Eff.State.Strict as Eff-import Control.Lens hiding ((.=), use)-import Data.Default-import Data.Dynamic-import Data.Foldable-import Data.Kind-import qualified Data.Map as Map-import Data.Text (Text, pack)-import Data.Type.Pretty-import GHC.Generics (Generic)-import GHC.Stack-import Control.Applicative ((<|>))---- * Supervisor Server API----- ** Functions---- | Start and link a new supervisor process with the given 'SpawnFun'unction.------ To spawn new child processes use 'spawnChild'.------ @since 0.23.0-startSupervisor-  :: forall p e-  . ( HasCallStack-    , LogIo (Processes e)-    , TangibleSup p-    , Stateful.Server (Sup p) (Processes e)-    )-  => Stateful.StartArgument (Sup p) (Processes e)-  -> Eff (Processes e) (Endpoint (Sup p))-startSupervisor = Stateful.start---- | Stop the supervisor and shutdown all processes.------ Block until the supervisor has finished.------ @since 0.23.0-stopSupervisor-  :: ( HasCallStack-     , HasProcesses e q-     , Member Logs e-     , Lifted IO e-     , TangibleSup p-     )-  => Endpoint (Sup p)-  -> Eff e ()-stopSupervisor ep = do-  logInfo ("stopping supervisor: " <> pack (show ep))-  mr <- monitor (_fromEndpoint ep)-  sendInterrupt (_fromEndpoint ep) NormalExitRequested-  r <- receiveSelectedMessage (selectProcessDown mr)-  logInfo ("supervisor stopped: " <> pack (show ep) <> " " <> pack (show r))---- | Check if a supervisor process is still alive.------ @since 0.23.0-isSupervisorAlive-  :: forall p q0 e .-     ( HasCallStack-     , Member Logs e-     , Typeable p-     , HasProcesses e q0-     )-  => Endpoint (Sup p)-  -> Eff e Bool-isSupervisorAlive x = isProcessAlive (_fromEndpoint x)---- | Monitor a supervisor process.------ @since 0.23.0-monitorSupervisor-  :: forall p q0 e .-     ( HasCallStack-     , Member Logs e-     , HasProcesses e q0-     , TangibleSup p-     )-  => Endpoint (Sup p)-  -> Eff e MonitorReference-monitorSupervisor x = monitor (_fromEndpoint x)---- | Start and monitor a new child process using the 'SpawnFun' passed--- to 'startSupervisor'.------ @since 0.23.0-spawnChild-  :: forall p q0 e .-     ( HasCallStack-     , Member Logs e-     , HasProcesses e q0-     , TangibleSup p-     , Typeable (Effectful.ServerPdu p)-     )-  => Endpoint (Sup p)-  -> ChildId p-  -> Eff e (Either (SpawnErr p) (Endpoint (Effectful.ServerPdu p)))-spawnChild ep cId = call ep (StartC cId)---- | Start and monitor a new child process using the 'SpawnFun' passed--- to 'startSupervisor'.------ Call 'spawnChild' and unpack the 'Either' result,--- ignoring the 'AlreadyStarted' error.------ @since 0.29.2-spawnOrLookup-  :: forall p q0 e .-     ( HasCallStack-     , Member Logs e-     , HasProcesses e q0-     , TangibleSup p-     , Typeable (Effectful.ServerPdu p)-     )-  => Endpoint (Sup p)-  -> ChildId p-  -> Eff e (Endpoint (Effectful.ServerPdu p))-spawnOrLookup supEp cId =-  do res <- spawnChild supEp cId-     pure $-      case res of-        Left (AlreadyStarted _ ep) -> ep-        Right ep -> ep---- | Lookup the given child-id and return the output value of the 'SpawnFun'---   if the client process exists.------ @since 0.23.0-lookupChild ::-    forall p e q0 .-     ( HasCallStack-     , Member Logs e-     , HasProcesses e q0-     , TangibleSup p-     , Typeable (Effectful.ServerPdu p)-     )-  => Endpoint (Sup p)-  -> ChildId p-  -> Eff e (Maybe (Endpoint (Effectful.ServerPdu p)))-lookupChild ep cId = call ep (LookupC @p cId)---- | Stop a child process, and block until the child has exited.------ Return 'True' if a process with that ID was found, 'False' if no process--- with the given ID was running.------ @since 0.23.0-stopChild ::-    forall p e q0 .-     ( HasCallStack-     , Member Logs e-     , HasProcesses e q0-     , TangibleSup p-     )-  => Endpoint (Sup p)-  -> ChildId p-  -> Eff e Bool-stopChild ep cId = call ep (StopC @p cId (TimeoutMicros 4000000))---- | Return a 'Text' describing the current state of the supervisor.------ @since 0.23.0-getDiagnosticInfo-  :: forall p e q0 .-     ( HasCallStack-     , HasProcesses e q0-     , TangibleSup p-     )-  => Endpoint (Sup p)-  -> Eff e Text-getDiagnosticInfo s = call s (GetDiagnosticInfo @p)---- ** Types---- | The index type of 'Server' supervisors.------ A @'Sup' p@ manages the life cycle of the processes, running the @'Server' p@--- methods of that specific type.------ The supervisor maps an identifier value of type @'ChildId' p@ to an @'Endpoint' p@.------ @since 0.24.0-data Sup (p :: Type) deriving Typeable--instance Typeable p => HasPdu (Sup p) where-  -- | The 'Pdu' instance contains methods to start, stop and lookup a child-  -- process, as well as a diagnostic callback.-  ---  -- @since 0.23.0-  data instance  Pdu (Sup p) r where-          StartC :: ChildId p -> Pdu (Sup p) ('Synchronous (Either (SpawnErr p) (Endpoint (Effectful.ServerPdu p))))-          StopC :: ChildId p -> Timeout -> Pdu (Sup p) ('Synchronous Bool)-          LookupC :: ChildId p -> Pdu (Sup p) ('Synchronous (Maybe (Endpoint (Effectful.ServerPdu p))))-          GetDiagnosticInfo :: Pdu (Sup p) ('Synchronous Text)-      deriving Typeable--instance (Show (ChildId p)) => Show (Pdu (Sup p) ('Synchronous r)) where-  showsPrec d (StartC c) = showParen (d >= 10) (showString "StartC " . showsPrec 10 c)-  showsPrec d (StopC c t) = showParen (d >= 10) (showString "StopC " . showsPrec 10 c . showChar ' ' . showsPrec 10 t)-  showsPrec d (LookupC c) = showParen (d >= 10) (showString "LookupC " . showsPrec 10 c)-  showsPrec _ GetDiagnosticInfo = showString "GetDiagnosticInfo"--instance (NFData (ChildId p)) => NFData (Pdu (Sup p) ('Synchronous r)) where-  rnf (StartC ci) = rnf ci-  rnf (StopC ci t) = rnf ci `seq` rnf t-  rnf (LookupC ci) = rnf ci-  rnf GetDiagnosticInfo = ()--type instance ToPretty (Sup p) = "supervisor" <:> ToPretty p---- | The type of value used to index running 'Server' processes managed by a 'Sup'.------ Note, that the type you provide must be 'Tangible'.------ @since 0.24.0-type family ChildId p---- | Constraints on the parameters to 'Sup'.------ @since 0.24.0-type TangibleSup p =-  ( Tangible (ChildId p)-  , Ord (ChildId p)-  , Typeable p-  )---instance-  ( LogIo q-  , TangibleSup p-  , Tangible (ChildId p)-  , Typeable (Effectful.ServerPdu p)-  , Effectful.Server p (Processes q)-  , HasProcesses (Effectful.ServerEffects p (Processes q)) q-  ) => Stateful.Server (Sup p) (Processes q) where--  -- | Options that control the 'Sup p' process.-  ---  -- This contains:-  ---  -- * a 'SpawnFun'-  -- * the 'Timeout' after requesting a normal child exit before brutally killing the child.-  ---  -- @since 0.24.0-  data StartArgument (Sup p) (Processes q) = MkSupConfig-    {-      -- , supConfigChildRestartPolicy :: ChildRestartPolicy-      -- , supConfigResilience :: Resilience-      supConfigChildStopTimeout :: Timeout-    , supConfigStartFun :: ChildId p -> Effectful.Init p (Processes q)-    }--  type Model (Sup p) = Children (ChildId p) p--  setup _ _cfg = pure (def, ())-  update _ supConfig (Stateful.OnCall rt req) =-    case req of-      GetDiagnosticInfo ->  do-        p <- (pack . show <$> getChildren @(ChildId p) @p)-        sendReply rt p--      LookupC i -> do-        p <- fmap _childEndpoint <$> lookupChildById @(ChildId p) @p i-        sendReply rt p--      StopC i t -> do-        mExisting <- lookupAndRemoveChildById @(ChildId p) @p i-        case mExisting of-          Nothing -> sendReply rt False-          Just existingChild -> do-            stopOrKillChild i existingChild t-            sendReply rt True--      StartC i -> do-        mExisting <- lookupChildById @(ChildId p) @p i-        case mExisting of-          Nothing -> do-            childEp <- raise (raise (Effectful.start (supConfigStartFun supConfig i)))-            let childPid = _fromEndpoint childEp-            cMon <- monitor childPid-            putChild i (MkChild @p childEp cMon)-            sendReply rt (Right childEp)-          Just existingChild ->-            sendReply rt (Left (AlreadyStarted i (existingChild ^. childEndpoint)))--  update _ _supConfig (Stateful.OnDown pd) = do-      oldEntry <- lookupAndRemoveChildByMonitor @(ChildId p) @p (downReference pd)-      case oldEntry of-        Nothing -> logWarning ("unexpected: " <> pack (show pd))-        Just (i, c) -> do-          logInfo (  pack (show pd)-                  <> " for child "-                  <> pack (show i)-                  <> " => "-                  <> pack (show (_childEndpoint c))-                  )-  update _ supConfig (Stateful.OnInterrupt e) = do-      let (logSev, exitReason) =-            case e of-              NormalExitRequested ->-                (debugSeverity, ExitNormally)-              _ ->-                (warningSeverity, interruptToExit (ErrorInterrupt ("supervisor interrupted: " <> show e)))-      stopAllChildren @p (supConfigChildStopTimeout supConfig)-      logWithSeverity logSev ("supervisor stopping: " <> pack (show e))-      exitBecause exitReason--  update _ _supConfig o = logWarning ("unexpected: " <> pack (show o))----- | Runtime-Errors occurring when spawning child-processes.------ @since 0.23.0-data SpawnErr p = AlreadyStarted (ChildId p) (Endpoint (Effectful.ServerPdu p))-  deriving (Typeable, Generic)--deriving instance Eq (ChildId p) => Eq (SpawnErr p)-deriving instance Ord (ChildId p) => Ord (SpawnErr p)-deriving instance (Typeable (Effectful.ServerPdu p), Show (ChildId p)) => Show (SpawnErr p)--instance NFData (ChildId p) => NFData (SpawnErr p)---- Internal Functions--stopOrKillChild-  :: forall p e q0 .-     ( HasCallStack-     , HasProcesses e q0-     , Lifted IO e-     , Lifted IO q0-     , Member Logs e-     , Member (State (Children (ChildId p) p)) e-     , TangibleSup p-     , Typeable (Effectful.ServerPdu p)-     )-  => ChildId p-  -> Child p-  -> Timeout-  -> Eff e ()-stopOrKillChild cId c stopTimeout =-      do-        sup <- asEndpoint @(Sup p) <$> self-        t <- startTimer stopTimeout-        sendInterrupt (_fromEndpoint (c^.childEndpoint)) NormalExitRequested-        r1 <- receiveSelectedMessage (   Right <$> selectProcessDown (c^.childMonitoring)-                                     <|> Left  <$> selectTimerElapsed t )-        demonitor (c^.childMonitoring)-        unlinkProcess (_fromEndpoint (c^.childEndpoint))-        case r1 of-          Left timerElapsed -> do-            logWarning (pack (show timerElapsed) <> ": child "<> pack (show cId) <>" => " <> pack(show (c^.childEndpoint)) <>" did not shutdown in time")-            sendShutdown-              (_fromEndpoint (c^.childEndpoint))-              (interruptToExit-                (TimeoutInterrupt-                  ("child did not shut down in time and was terminated by the "-                    ++ show sup)))-          Right downMsg ->-            logInfo ("child "<> pack (show cId) <>" => " <> pack(show (c^.childEndpoint)) <>" terminated: " <> pack (show (downReason downMsg)))--stopAllChildren-  :: forall p e q0 .-     ( HasCallStack-     , HasProcesses e q0-     , Lifted IO e-     , Lifted IO q0-     , Member Logs e-     , Member (State (Children (ChildId p) p)) e-     , TangibleSup p-     , Typeable (Effectful.ServerPdu p)-     )-  => Timeout -> Eff e ()-stopAllChildren stopTimeout = removeAllChildren @(ChildId p) @p >>= pure . Map.assocs >>= traverse_ xxx-  where-    xxx (cId, c) = provideInterrupts (stopOrKillChild cId c stopTimeout) >>= either crash return-      where-        crash e = do-          logError (pack (show e) <> " while stopping child: " <> pack (show cId) <> " " <> pack (show c))
− src/Control/Eff/Concurrent/Protocol/Supervisor/InternalState.hs
@@ -1,105 +0,0 @@-module Control.Eff.Concurrent.Protocol.Supervisor.InternalState where--import Control.DeepSeq-import Control.Eff as Eff-import Control.Eff.Concurrent.Process-import Control.Eff.Concurrent.Protocol-import Control.Eff.Concurrent.Protocol.EffectfulServer-import Control.Eff.State.Strict as Eff-import Control.Lens hiding ((.=), use)-import Data.Default-import Data.Dynamic-import Data.Map (Map)-import GHC.Generics (Generic)---data Child p = MkChild-  { _childEndpoint :: Endpoint (ServerPdu p)-  , _childMonitoring :: MonitorReference-  }-  deriving (Generic, Typeable, Eq, Ord)--instance NFData (Child o)--instance Typeable (ServerPdu p) => Show (Child p) where-  showsPrec d c = showParen (d>=10)-    (showString "supervised process: " . shows (_childEndpoint c)  . showChar ' ' . shows (_childMonitoring c) )--makeLenses ''Child---- | Internal state.-data Children i p = MkChildren-  { _childrenById :: Map i (Child p)-  , _childrenByMonitor :: Map MonitorReference (i, Child p)-  } deriving (Show, Generic, Typeable)--instance Default (Children i p) where-  def = MkChildren def def--instance (NFData i) => NFData (Children i p)--makeLenses ''Children---- | State accessor-getChildren-  ::  (Ord i, Member (State (Children i o)) e)-  => Eff e (Children i o)-getChildren = Eff.get--putChild-  :: (Ord i, Member (State (Children i o)) e)-  => i-  -> Child o-  -> Eff e ()-putChild cId c = modify ( (childrenById . at cId .~ Just c)-                        . (childrenByMonitor . at (_childMonitoring c) .~ Just (cId, c))-                        )--lookupChildById-  :: (Ord i, Member (State (Children i o)) e)-  => i-  -> Eff e (Maybe (Child o))-lookupChildById i = view (childrenById . at i) <$> get--lookupChildByMonitor-  :: (Ord i, Member (State (Children i o)) e)-  => MonitorReference-  -> Eff e (Maybe (i, Child o))-lookupChildByMonitor m = view (childrenByMonitor . at m) <$> get--lookupAndRemoveChildById-  :: forall i o e. (Ord i, Member (State (Children i o)) e)-  => i-  -> Eff e (Maybe (Child o))-lookupAndRemoveChildById i =-  traverse go =<< lookupChildById i-  where-    go c = pure c <* removeChild i c--removeChild-  :: forall i o e. (Ord i, Member (State (Children i o)) e)-  => i-  -> Child o-  -> Eff e ()-removeChild i c = do-  modify @(Children i o) ( (childrenById . at i .~ Nothing)-                         . (childrenByMonitor . at (_childMonitoring c) .~ Nothing)-                         )--lookupAndRemoveChildByMonitor-  :: forall i o e. (Ord i, Member (State (Children i o)) e)-  => MonitorReference-  -> Eff e (Maybe (i, Child o))-lookupAndRemoveChildByMonitor r = do-  traverse go =<< lookupChildByMonitor r-  where-    go (i, c) = pure (i, c) <* removeChild i c--removeAllChildren-  :: forall i o e. (Ord i, Member (State (Children i o)) e)-  => Eff e (Map i (Child o))-removeAllChildren = do-  cm <- view childrenById <$> getChildren @i-  modify @(Children i o) (childrenById .~ mempty)-  modify @(Children i o) (childrenByMonitor .~ mempty)-  return cm
+ src/Control/Eff/Concurrent/Protocol/Watchdog.hs view
@@ -0,0 +1,616 @@+{-# LANGUAGE UndecidableInstances #-}+-- | Monitor a process and act when it is unresponsive.+--+-- Behaviour of the watchdog:+--+-- When a child crashes:+-- * if the allowed maximum number crashes per time span has been reached for the process,+-- ** cancel all other timers+-- ** don't start the child again+-- ** if this is a /permanent/ watchdog crash the watchdog+-- * otherwise+-- ** tell the broker to start the child+-- ** record a crash and start a timer to remove the record later+-- ** monitor the child+--+-- When a child crash timer elapses:+-- * remove the crash record+--+-- @since 0.30.0+module Control.Eff.Concurrent.Protocol.Watchdog+  ( startLink+  , Watchdog+  , attachTemporary+  , attachPermanent+  , getCrashReports+  , CrashRate(..)+  , crashCount+  , crashTimeSpan+  , crashesPerSeconds+  , CrashCount+  , CrashTimeSpan+  , ChildWatch(..)+  , parent+  , crashes+  , ExonerationTimer(..)+  , CrashReport(..)+  , crashTime+  , crashReason+  , exonerationTimerReference+  ) where++import Control.DeepSeq+import Control.Eff (Eff, Member, lift, Lifted)+import Control.Eff.Concurrent.Misc+import Control.Eff.Concurrent.Process+import Control.Eff.Concurrent.Process.Timer+import Control.Eff.Concurrent.Protocol+import Control.Eff.Concurrent.Protocol.Client+import Control.Eff.Concurrent.Protocol.Wrapper+import qualified Control.Eff.Concurrent.Protocol.Observer as Observer+import Control.Eff.Concurrent.Protocol.Observer (Observer)+import qualified Control.Eff.Concurrent.Protocol.Broker as Broker+import Control.Eff.Concurrent.Protocol.Broker (Broker)+import qualified Control.Eff.Concurrent.Protocol.StatefulServer as Stateful+import qualified Control.Eff.Concurrent.Protocol.EffectfulServer as Effectful+import Control.Lens+import Control.Eff.Log+import Data.Set (Set)+import Data.Typeable+import qualified Data.Set as Set+import Data.Map (Map)+import qualified Data.Map as Map+import Data.Time.Clock+import Data.Kind (Type)+import Data.Default+import Data.Text (pack)+import GHC.Stack (HasCallStack)+import Data.Maybe (isJust)+import Data.Foldable (traverse_, forM_)+import Control.Monad (when)+++-- | The phantom for watchdog processes, that watch the given type of servers+--+-- This type is used for the 'Effectful.Server' and 'HasPdu' instances.+--+-- @since 0.30.0+data Watchdog (child :: Type) deriving Typeable++-- | Start and link a new watchdog process.+--+-- The watchdog process will register itself to the 'Broker.ChildEvent's and+-- restart crashed children.+--+-- @since 0.30.0+startLink+  :: forall child e q h+  . ( HasCallStack+    , Typeable child+    , LogsTo h (Processes q)+    , Member Logs q+    , HasProcesses e q+    , Tangible (Broker.ChildId child)+    , Ord (Broker.ChildId child)+    , HasPdu (Effectful.ServerPdu child)+    , Lifted IO q+    )+  => CrashRate -> Eff e (Endpoint (Watchdog child))+startLink = Stateful.startLink @(Watchdog child) . StartWatchDog++-- | Restart children of the given broker.+--+-- When the broker exits, ignore the children of that broker.+--+-- @since 0.30.0+attachTemporary+  :: forall child q e h+  . ( HasCallStack+    , LogsTo h e+    , Typeable child+    , HasPdu (Effectful.ServerPdu child)+    , Tangible (Broker.ChildId child)+    , Ord (Broker.ChildId child)+    , HasProcesses e q+    )+  => Endpoint (Watchdog child) -> Endpoint (Broker child) -> Eff e ()+attachTemporary wd broker =+  callWithTimeout wd (Attach broker False) (TimeoutMicros 1_000_000)++-- | Restart children of the given broker.+--+-- When the broker exits, the watchdog process will exit, too.+--+-- @since 0.30.0+attachPermanent+  :: forall child q e h+  . ( HasCallStack+    , LogsTo h e+    , Typeable child+    , HasPdu (Effectful.ServerPdu child)+    , Tangible (Broker.ChildId child)+    , Ord (Broker.ChildId child)+    , HasProcesses e q+    )+  => Endpoint (Watchdog child) -> Endpoint (Broker child) -> Eff e ()+attachPermanent wd broker =+  callWithTimeout wd (Attach broker True) (TimeoutMicros 1_000_000)++-- | Return a list of 'CrashReport's.+--+-- Useful for diagnostics+--+-- @since 0.30.0+getCrashReports+  :: forall child q e h+  . ( HasCallStack+    , LogsTo h e+    , Typeable child+    , HasPdu (Effectful.ServerPdu child)+    , Tangible (Broker.ChildId child)+    , Ord (Broker.ChildId child)+    , HasProcesses e q+    , Lifted IO q+    , Lifted IO e+    , Member Logs e+    )+  => Endpoint (Watchdog child) -> Eff e (Map (Broker.ChildId child) (ChildWatch child))+getCrashReports wd = callWithTimeout wd GetCrashReports (TimeoutMicros 5_000_000)++instance Typeable child => HasPdu (Watchdog child) where+  type instance EmbeddedPduList (Watchdog child) = '[Observer (Broker.ChildEvent child)]+  data Pdu (Watchdog child) r where+    Attach :: Endpoint (Broker child) -> Bool -> Pdu (Watchdog child) ('Synchronous ())+    GetCrashReports :: Pdu (Watchdog child) ('Synchronous (Map (Broker.ChildId child) (ChildWatch child)))+    OnChildEvent :: Broker.ChildEvent child -> Pdu (Watchdog child) 'Asynchronous+      deriving Typeable++instance Typeable child => HasPduPrism (Watchdog child) (Observer (Broker.ChildEvent child)) where+  embedPdu (Observer.Observed e) = OnChildEvent e+  fromPdu (OnChildEvent x) = Just (Observer.Observed x)+  fromPdu _ = Nothing++instance (NFData (Broker.ChildId child)) => NFData (Pdu (Watchdog child) r) where+  rnf (Attach e b) = rnf e `seq` rnf b `seq` ()+  rnf GetCrashReports = ()+  rnf (OnChildEvent o) = rnf o++instance+  ( Show (Broker.ChildId child)+  , Typeable child+  , Typeable (Effectful.ServerPdu child)+  )+  => Show (Pdu (Watchdog child) r) where+  showsPrec d (Attach e False) = showParen (d>=10) (showString "attach-temporary: " . shows e)+  showsPrec d (Attach e True) = showParen (d>=10) (showString "attach-permanent: " . shows e)+  showsPrec _ GetCrashReports = showString "get-crash-reports"+  showsPrec d (OnChildEvent o) = showParen (d>=10) (showString "on-child-event: " . showsPrec 10 o)++-- ------------------ Broker Watches++data BrokerWatch =+  MkBrokerWatch { _brokerMonitor ::  MonitorReference, _isPermanent :: Bool }+  deriving (Typeable)++instance Show BrokerWatch where+  showsPrec d (MkBrokerWatch mon False) = showParen (d>=10) (showString "temporary-broker: " . showsPrec 10 mon)+  showsPrec d (MkBrokerWatch mon True) = showParen (d>=10) (showString "permanent-broker: " . showsPrec 10 mon)++brokerMonitor :: Lens' BrokerWatch MonitorReference+brokerMonitor = lens _brokerMonitor (\(MkBrokerWatch _ x) m -> MkBrokerWatch m x)++isPermanent :: Lens' BrokerWatch Bool+isPermanent = lens _isPermanent (\(MkBrokerWatch x _) m -> MkBrokerWatch x m)++-- --- Server Definition++instance+  ( Typeable child+  , HasPdu (Effectful.ServerPdu child)+  , Tangible (Broker.ChildId child)+  , Ord (Broker.ChildId child)+  , Eq (Broker.ChildId child)+  , Lifted IO e+  , Member Logs e+  ) => Stateful.Server (Watchdog child) (Processes e) where++  data instance StartArgument (Watchdog child) (Processes e) =+    StartWatchDog { _crashRate :: CrashRate+                  }+      deriving Typeable++  data instance Model (Watchdog child) =+    WatchdogModel { _brokers :: Map (Endpoint (Broker child)) BrokerWatch+                  , _watched :: Map (Broker.ChildId child) (ChildWatch child)+                  }++  update me startArg =+    \case+      Effectful.OnCall rt (Attach broker permanent) -> do+        logDebug (  "attaching "+                 <> if permanent then "permanently" else "temporarily"+                 <> " to: " <> pack (show broker)+                 )+        oldMonitor <- Stateful.preuseModel @(Watchdog child) (brokers . at broker . _Just . brokerMonitor)+        newMonitor <- maybe (monitor (broker^.fromEndpoint)) return oldMonitor+        case oldMonitor of+          Nothing -> do+            logDebug ("start observing: " <> pack (show broker))+            Observer.registerObserver @(Broker.ChildEvent child) broker me+          Just _ ->+            logDebug ("already observing " <> pack (show broker))+        let newBrokerWatch = MkBrokerWatch newMonitor permanent+        Stateful.modifyModel (brokers . at broker ?~ newBrokerWatch)+        Observer.registerObserver @(Broker.ChildEvent child) broker me+        sendReply rt ()++      Effectful.OnCall rt GetCrashReports ->+        Stateful.useModel @(Watchdog child) watched >>= sendReply rt++      Effectful.OnCast (OnChildEvent e) ->+        case e of+          down@(Broker.OnBrokerShuttingDown broker) -> do+            logInfo ("received: " <> pack (show down))+            removeBroker me broker++          spawned@(Broker.OnChildSpawned broker _ _) -> do+            logInfo ("received: " <> pack (show spawned))+            currentModel <- Stateful.getModel @(Watchdog child)+            when (not (Set.member broker (currentModel ^. brokers . to Map.keysSet)))+             (logWarning ("received child event for unknown broker: " <> pack (show spawned)))++          down@(Broker.OnChildDown broker cId _ ExitNormally) -> do+            logInfo ("received: " <> pack (show down))+            currentModel <- Stateful.getModel @(Watchdog child)+            if not (Set.member broker (currentModel ^. brokers . to Map.keysSet))+             then logWarning ("received child event for unknown broker: " <> pack (show down))+             else removeAndCleanChild @child cId++          down@(Broker.OnChildDown broker cId _ reason) -> do+            logInfo ("received: " <> pack (show down))+            currentModel <- Stateful.getModel @(Watchdog child)+            if not (Set.member broker (currentModel ^. brokers . to Map.keysSet))+             then+              logWarning ("received child event for unknown broker: " <> pack (show down))+             else do+              let recentCrashes = countRecentCrashes broker cId currentModel+                  rate = startArg ^. crashRate+                  maxCrashCount = rate ^. crashCount+              if recentCrashes < maxCrashCount then do+                logNotice ("restarting (" <> pack (show recentCrashes) <> "/" <> pack (show maxCrashCount) <> "): "+                            <> pack (show cId) <> " of " <> pack (show broker))+                res <- Broker.spawnChild broker cId+                logNotice ("restarted: " <> pack (show cId) <> " of "+                            <> pack (show broker) <> ": " <> pack (show res))+                crash <- startExonerationTimer @child cId reason (rate ^. crashTimeSpan)+                if isJust (currentModel ^? childWatchesById cId)+                  then do+                    logDebug ("recording crash for child: " <> pack (show cId) <> " of " <> pack (show broker))+                    Stateful.modifyModel (watched @child . at cId . _Just . crashes %~ Set.insert crash)+                  else do+                    logDebug ("recording crash for new child: " <> pack (show cId) <> " of " <> pack (show broker))+                    Stateful.modifyModel (watched @child . at cId .~ Just (MkChildWatch broker (Set.singleton crash)))+              else do+                logWarning ("restart rate exceeded: " <> pack (show rate)+                            <> ", for child: " <> pack (show cId)+                            <> " of " <> pack (show broker))+                removeAndCleanChild @child cId+                forM_ (currentModel ^? brokers . at broker . _Just) $ \bw ->+                  if  bw ^. isPermanent then do+                    logError ("a child of a permanent broker crashed too often, interrupting: " <> pack (show broker))+                    let r =  ExitUnhandledError "restart frequency exceeded"+                    demonitor (bw ^. brokerMonitor)+                    sendShutdown (broker ^. fromEndpoint) r+                    exitBecause r+                    -- TODO shutdown all other permanent brokers!+                  else+                    logError ("a child of a temporary broker crashed too often: " <> pack (show broker))++      Effectful.OnDown pd@(ProcessDown _mref _ pid) -> do+        logDebug ("received " <> pack (show pd))+        let broker = asEndpoint pid+        removeBroker @child me broker++      Effectful.OnTimeOut t -> do+        logError ("received: " <> pack (show t))++      Effectful.OnMessage (fromStrictDynamic -> Just (MkExonerationTimer cId ref :: ExonerationTimer (Broker.ChildId child))) -> do+        logInfo ("exonerating: " <> pack (show cId))+        Stateful.modifyModel+          (watched @child . at cId . _Just . crashes %~ Set.filter (\c -> c^.exonerationTimerReference /= ref))++      Effectful.OnMessage t -> do+        logError ("received: " <> pack (show t))++      Effectful.OnInterrupt reason -> do+        logError ("received: " <> pack (show reason))++-- ------------------ Start Argument++crashRate :: Lens' (Stateful.StartArgument (Watchdog child) (Processes e)) CrashRate+crashRate = lens _crashRate (\m x -> m {_crashRate = x})++-- ----------------- Crash Rate++-- | The limit of crashes (see 'CrashCount') per time span (see 'CrashTimeSpan') that justifies restarting+-- child processes.+--+-- Used as parameter for 'startLink'.+--+-- Use 'crashesPerSeconds' to construct a value.+--+-- This governs how long the 'ExonerationTimer' runs before cleaning up a 'CrashReport' in a 'ChildWatch'.+--+-- @since 0.30.0+data CrashRate =+  CrashesPerSeconds { _crashCount :: CrashCount+                    , _crashTimeSpan :: CrashTimeSpan+                    }+  deriving (Typeable, Eq, Ord)++-- | The default is three crashes in 30 seconds.+--+-- @since 0.30.0+instance Default CrashRate where+  def = 3 `crashesPerSeconds` 30++instance Show CrashRate where+  showsPrec d (CrashesPerSeconds count time) =+    showParen (d>=7) (shows count . showString " crashes/" . shows time . showString " seconds")++instance NFData CrashRate where+  rnf (CrashesPerSeconds c t) = c `seq` t `seq` ()++-- | Number of crashes in 'CrashRate'.+--+-- @since 0.30.0+type CrashCount = Int++-- | Time span in which crashes are counted in 'CrashRate'.+--+-- @since 0.30.0+type CrashTimeSpan = Int++-- | A smart constructor for 'CrashRate'.+--+-- The first parameter is the number of crashes allowed per number of seconds (second parameter)+-- until the watchdog should give up restarting a child.+--+-- @since 0.30.0+crashesPerSeconds :: CrashCount -> CrashTimeSpan -> CrashRate+crashesPerSeconds = CrashesPerSeconds++-- | A lens for '_crashCount'.+--+-- @since 0.30.0+crashCount :: Lens' CrashRate CrashCount+crashCount = lens _crashCount (\(CrashesPerSeconds _ time) count -> CrashesPerSeconds count time )++-- | A lens for '_crashTimeSpan'.+--+-- @since 0.30.0+crashTimeSpan :: Lens' CrashRate CrashTimeSpan+crashTimeSpan = lens _crashTimeSpan (\(CrashesPerSeconds count _) time -> CrashesPerSeconds count time)++-- ------------------ Crash++-- | An internal data structure that records a single crash of a child of an attached 'Broker'.+--+-- See 'attachPermanent' and 'attachTemporary'.+--+-- @since 0.30.0+data CrashReport a =+  MkCrashReport { _exonerationTimerReference :: TimerReference+                  -- ^ After a crash, an 'ExonerationTimer' according to the 'CrashRate' of the 'Watchdog'+                  -- is started, this is the reference+                , _crashTime :: UTCTime+                  -- ^ Recorded time of the crash+                , _crashReason :: Interrupt 'NoRecovery+                  -- ^ Recorded crash reason+                }+  deriving (Eq, Ord, Typeable)++instance (Show a, Typeable a) => Show (CrashReport a) where+  showsPrec d c =+    showParen (d>=10)+      ( showString "crash report: "+      . showString " time: " . showsPrec 10 (c^.crashTime)+      . showString " reason: " . showsPrec 10 (c^.crashReason)+      . showString " " . showsPrec 10 (c^.exonerationTimerReference)+      )++instance NFData (CrashReport a) where+  rnf (MkCrashReport !a !b !c) = rnf a `seq` rnf b `seq` rnf c `seq` ()++-- | Lens for '_crashTime'+--+-- @since 0.30.0+crashTime :: Lens' (CrashReport a) UTCTime+crashTime = lens _crashTime (\c t -> c { _crashTime = t})++-- | Lens for '_crashReason'+--+-- @since 0.30.0+crashReason :: Lens' (CrashReport a) (Interrupt 'NoRecovery)+crashReason = lens _crashReason (\c t -> c { _crashReason = t})++-- | Lens for '_exonerationTimerReference'+--+-- @since 0.30.0+exonerationTimerReference :: Lens' (CrashReport a) TimerReference+exonerationTimerReference = lens _exonerationTimerReference (\c t -> c { _exonerationTimerReference = t})++startExonerationTimer :: forall child a q e .+     (HasProcesses e q, Lifted IO q, Lifted IO e, Show a, NFData a, Typeable a, Typeable child)+     => a -> Interrupt 'NoRecovery -> CrashTimeSpan -> Eff e (CrashReport a)+startExonerationTimer cId r t = do+  let title = MkProcessTitle ("ExonerationTimer<" <> pack (showSTypeable @child ">") <> pack (show cId))+  me <- self+  ref <- sendAfterWithTitle title me (TimeoutMicros (t * 1_000_000)) (MkExonerationTimer cId)+  now <- lift getCurrentTime+  return (MkCrashReport ref now r)++-- | The timer started based on the 'CrashRate' '_crashTimeSpan' when a 'CrashReport' is recorded.+--+-- After this timer elapses, the 'Watchdog' server will remove the 'CrashReport' from the 'ChildWatch' of+-- that child.+--+-- @since 0.30.0+data ExonerationTimer a =  MkExonerationTimer !a !TimerReference+  deriving (Eq, Ord, Typeable)++instance NFData a => NFData (ExonerationTimer a) where+  rnf (MkExonerationTimer !x !r) = rnf r `seq` rnf x `seq` ()++instance Show a => Show (ExonerationTimer a) where+  showsPrec d (MkExonerationTimer x r) =+    showParen (d >= 10)+      ( showString "exonerate: " . showsPrec 10 x+      . showString " after: " .  showsPrec 10 r+      )++-- --------------------------- Child Watches++-- | An internal data structure that keeps the 'CrashReport's of a child of an attached 'Broker' monitored by a 'Watchdog'.+--+-- See 'attachPermanent' and 'attachTemporary', 'ExonerationTimer', 'CrashRate'.+--+-- @since 0.30.0+data ChildWatch child =+  MkChildWatch+    { _parent :: Endpoint (Broker child)+      -- ^ The attached 'Broker' that started the child+    , _crashes :: Set (CrashReport (Broker.ChildId child))+      -- ^ The crashes of the child. If the number of crashes+      -- surpasses the allowed number of crashes before the+      -- 'ExonerationTimer's clean them, the child is finally crashed.+    }+   deriving Typeable++instance NFData (ChildWatch child) where+  rnf (MkChildWatch p c) =+    rnf p `seq` rnf c `seq` ()++instance (Typeable child, Typeable (Broker.ChildId child), Show (Broker.ChildId child)) => Show (ChildWatch child) where+  showsPrec d (MkChildWatch p c) =+    showParen (d>=10) ( showString "child-watch: parent: "+                      . showsPrec 10 p+                      . showString " crashes: "+                      . foldr (.) id (showsPrec 10 <$> Set.toList c)+                      )++-- | A lens for '_parent'.+--+-- @since 0.30.0+parent :: Lens' (ChildWatch child) (Endpoint (Broker child))+parent = lens _parent (\m x -> m {_parent = x})++-- | A lens for '_crashes'+--+-- @since 0.30.0+crashes :: Lens' (ChildWatch child) (Set (CrashReport (Broker.ChildId child)))+crashes = lens _crashes (\m x -> m {_crashes = x})++-- ------------------ Model++instance Default (Stateful.Model (Watchdog child)) where+  def = WatchdogModel def Map.empty++instance ( Show (Broker.ChildId child)+         , Typeable (Broker.ChildId child)+         , Typeable child+         )+          => Show (Stateful.Model (Watchdog child))+  where+  showsPrec d  (WatchdogModel bs cs) =+    showParen (d>=10)+      (showString "watchdog model broker watches: "+      . showsPrec 10 bs+      . showString " watchdog model child watches: "+      . showsPrec 10 cs+      )++-- -------------------------- Model -> Child Watches++watched :: Lens' (Stateful.Model (Watchdog child)) (Map (Broker.ChildId child) (ChildWatch child))+watched = lens _watched (\m x -> m {_watched = x})++childWatches :: IndexedTraversal' (Broker.ChildId child) (Stateful.Model (Watchdog child)) (ChildWatch child)+childWatches = watched . itraversed++childWatchesById ::+     Eq (Broker.ChildId child)+  => Broker.ChildId child+  -> Traversal' (Stateful.Model (Watchdog child)) (ChildWatch child)+childWatchesById theCId = childWatches . ifiltered (\cId _ -> cId == theCId)++childWatchesByParenAndId ::+     Eq (Broker.ChildId child)+  => Endpoint (Broker child)+  -> Broker.ChildId child+  -> Traversal' (Stateful.Model (Watchdog child)) (ChildWatch child)+childWatchesByParenAndId theParent theCId =+  childWatches . ifiltered (\cId cw -> cw ^. parent == theParent && cId == theCId)++countRecentCrashes ::+     Eq (Broker.ChildId child)+  => Endpoint (Broker child)+  -> Broker.ChildId child+  -> Stateful.Model (Watchdog child)+  -> CrashCount+countRecentCrashes theParent theCId theModel =+ length (theModel ^.. childWatchesByParenAndId theParent theCId . crashes . folded)++-- --------------------------- Model -> Broker Watches++brokers :: Lens' (Stateful.Model (Watchdog child)) (Map (Endpoint (Broker child)) BrokerWatch)+brokers = lens _brokers (\m x -> m {_brokers = x})++-- -------------------------- Server Implementation Helpers++removeAndCleanChild ::+  forall child q e.+  ( HasProcesses e q+  , Typeable child+  , Typeable (Broker.ChildId child)+  , Ord (Broker.ChildId child)+  , Show (Broker.ChildId child)+  , Member (Stateful.ModelState (Watchdog child)) e+  , Member Logs e+  )+  => Broker.ChildId child+  -> Eff e ()+removeAndCleanChild cId = do+    oldModel <- Stateful.modifyAndGetModel (watched @child . at cId .~ Nothing)+    forMOf_ (childWatchesById cId) oldModel $ \w -> do+      logDebug ("removing client entry: " <> pack (show cId))+      forMOf_ (crashes . folded . exonerationTimerReference) w cancelTimer+      logDebug (pack (show w))++removeBroker ::+  forall child q e.+  ( HasProcesses e q+  , Typeable child+  , Tangible (Broker.ChildId child)+  , Typeable (Effectful.ServerPdu child)+  , Ord (Broker.ChildId child)+  , Show (Broker.ChildId child)+  , Member (Stateful.ModelState (Watchdog child)) e+  , Member Logs e+  )+  => Endpoint (Watchdog child)+  -> Endpoint (Broker child)+  -> Eff e ()+removeBroker me broker = do+    oldModel <- Stateful.getAndModifyModel @(Watchdog child)+                  ( (brokers . at broker .~ Nothing)+                  . (watched %~ Map.filter (\cw -> cw^.parent /= broker))+                  )+    forM_ (oldModel ^? brokers . at broker . _Just) $ \deadBroker ->  do+      logNotice ("dettaching: " <> pack (show deadBroker) <> " " <> pack (show broker))+      let forgottenChildren = oldModel ^.. watched . itraversed . filtered (\cw -> cw^.parent == broker)+      traverse_ (logNotice . ("forgetting: " <>) . pack . show) forgottenChildren+      Observer.forgetObserver @(Broker.ChildEvent child) broker me+      when (view isPermanent deadBroker) $ do+        logError ("permanent broker exited: " <> pack (show broker))+        exitBecause (ExitOtherProcessNotRunning (broker ^. fromEndpoint))
src/Control/Eff/Log.hs view
@@ -88,6 +88,9 @@   , logInfo'   , logDebug   , logDebug'+  , logCallStack+  , logMultiLine+  , logMultiLine'      -- ** Log Message Pre-Filtering #LogPredicate#     -- $LogPredicate
src/Control/Eff/Log/Handler.hs view
@@ -26,6 +26,9 @@   , logInfo'   , logDebug   , logDebug'+  , logCallStack+  , logMultiLine+  , logMultiLine'    -- ** Log Message Pre-Filtering #LogPredicate#   , includeLogMessages@@ -79,13 +82,15 @@                                                  ) import           Data.Default import           Data.Function                  ( fix )+import           Data.Hashable import           Data.Text                     as T import           GHC.Stack                      ( HasCallStack                                                 , callStack                                                 , withFrozenCallStack+                                                , prettyCallStack                                                 )--+import Data.Foldable                           ( traverse_ )+import Text.Printf                             ( printf )  -- | This effect sends 'LogMessage's and is a reader for a 'LogPredicate'. --@@ -458,6 +463,72 @@   -> Eff e () logDebug' = withFrozenCallStack (logWithSeverity' debugSeverity) +-- | Log the current 'callStack' using the given 'Severity'.+--+-- @since 0.30.0+logCallStack :: forall e . (HasCallStack, Member Logs e) => Severity -> Eff e ()+logCallStack =+  withFrozenCallStack $ \s ->+    let stackTraceLines = T.lines (pack (prettyCallStack callStack))+    in logMultiLine s stackTraceLines+++-- | Issue a log statement for each item in the list prefixed with a line number and a message hash.+--+-- When several concurrent processes issue log statements, multiline log statements are often+-- interleaved.+--+-- In order to make the logs easier to read, this function will count the items and calculate a unique+-- hash and prefix each message, so a user can grep to get all the lines of an interleaved,+-- multi-line log message.+--+-- @since 0.30.0+logMultiLine+  :: forall e+    . ( HasCallStack+    , Member Logs e+    )+    => Severity+    -> [Text]+    -> Eff e ()+logMultiLine =+  withFrozenCallStack $ \s messageLines -> do+    let msgHash = T.pack $ printf "multi-line message %06X" $ hash messageLines `mod` 0x1000000+        messageLinesWithLineNum =+          let messageLineCount = Prelude.length messageLines+              messageLineCountString = T.pack (show messageLineCount)+              messageLineCountStringLen = T.length messageLineCountString+              printLineNum i =+                let i' = T.pack (show i)+                    padding = messageLineCountStringLen - T.length i'+                in msgHash <> " line " <> T.replicate padding " " <> i' <> " of " <> messageLineCountString <> ":    "+          in Prelude.zipWith (<>) (printLineNum <$> [1 :: Int ..])  messageLines+    traverse_ (logWithSeverity s) messageLinesWithLineNum+++-- | Issue a log statement for each item in the list prefixed with a line number and a message hash.+--+-- When several concurrent processes issue log statements, multiline log statements are often+-- interleaved.+--+-- In order to make the logs easier to read, this function will count the items and calculate a unique+-- hash and prefix each message, so a user can grep to get all the lines of an interleaved,+-- multi-line log message.+--+-- This function takes a list of 'String's as opposed to 'logMultiLine'.+--+-- @since 0.30.0+logMultiLine'+  :: forall e+    . ( HasCallStack+    , Member Logs e+    )+    => Severity+    -> [String]+    -> Eff e ()+logMultiLine' s = logMultiLine s . fmap pack++ -- | Get the current 'Logs' filter/transformer function. -- -- See "Control.Eff.Log#LogPredicate"@@ -522,7 +593,7 @@  -- | Include 'LogMessage's that match a 'LogPredicate'. ----- @excludeLogMessages p@ allows log message to be logged if @p m@+-- @includeLogMessages p@ allows log message to be logged if @p m@ -- -- Although it is enough if the previous predicate holds. -- See 'excludeLogMessages' and 'modifyLogPredicate'.
src/Control/Eff/LogWriter/Console.hs view
@@ -11,6 +11,7 @@ import Control.Eff.LogWriter.IO import Data.Text import qualified Data.Text.IO                  as T+import qualified System.IO                     as IO  -- | Enable logging to @standard output@ using the 'consoleLogWriter', with some 'LogMessage' fields preset -- as in 'withIoLogging'.@@ -33,7 +34,9 @@   -> LogPredicate -- ^ The inital predicate for log messages, there are some pre-defined in "Control.Eff.Log.Message#PredefinedPredicates"   -> Eff (Logs : LogWriterReader (Lift IO) : e) a   -> Eff e a-withConsoleLogging = withIoLogging consoleLogWriter+withConsoleLogging a b c d = do+  lift (IO.hSetBuffering IO.stdout IO.LineBuffering)+  withIoLogging consoleLogWriter a b c d   -- | Enable logging to @standard output@ using the 'consoleLogWriter'.@@ -51,7 +54,9 @@ withConsoleLogWriter   :: (LogIo e)   => Eff e a -> Eff e a-withConsoleLogWriter = addLogWriter consoleLogWriter+withConsoleLogWriter e = do+  lift (IO.hSetBuffering IO.stdout IO.LineBuffering)+  addLogWriter consoleLogWriter e  -- | Write 'LogMessage's to standard output, formatted with 'printLogMessage'. --
src/Control/Eff/LogWriter/File.hs view
@@ -43,7 +43,7 @@   -> LogPredicate -- ^ The inital predicate for log messages, there are some pre-defined in "Control.Eff.Log.Message#PredefinedPredicates"   -> Eff (Logs : LogWriterReader (Lift IO) : e) a   -> Eff e a-withFileLogging fnIn a f p e =+withFileLogging fnIn a f p e = do   liftBaseOp (withOpenedLogFile fnIn) (\lw -> withIoLogging lw a f p e)  @@ -72,7 +72,7 @@     fnCanon <- canonicalizePath fnIn     createDirectoryIfMissing True (takeDirectory fnCanon)     h <- IO.openFile fnCanon IO.AppendMode-    IO.hSetBuffering h (IO.BlockBuffering (Just 1024))+    IO.hSetBuffering h IO.LineBuffering     return h   )   (\h -> Safe.try @IO @Catch.SomeException (IO.hFlush h) >> IO.hClose h)
stack.yaml view
@@ -63,3 +63,4 @@ # # Allow a newer minor version of GHC than the snapshot specifies # compiler-check: newer-minor+ghc-options: {"$locals": -ddump-to-file -ddump-hi}
+ test/BrokerTests.hs view
@@ -0,0 +1,367 @@+module BrokerTests+  ( test_Broker+  ) where++import Common+import Control.Eff.Concurrent.Protocol.EffectfulServer (Event(..))+import qualified Control.Eff.Concurrent.Protocol.StatefulServer as Stateful+import Control.Eff.Concurrent.Protocol.Broker as Broker+import qualified Control.Eff.Concurrent.Protocol.Observer.Queue as OQ+import Control.Lens++test_Broker :: HasCallStack => TestTree+test_Broker =+  setTravisTestOptions $+  testGroup+    "Broker"+    (let startTestBroker = startTestBrokerWith ExitWhenRequested (TimeoutMicros 500000)+         startTestBrokerWith e t = Broker.startLink (MkBrokerConfig t (Stateful.Init . TestServerArgs e))+         spawnTestChild broker i = Broker.spawnChild broker i >>= either (lift . assertFailure . show) pure+      in [ runTestCase "The broker starts and is shut down" $ do+             outerSelf <- self+             testWorker <-+               spawn "test-worker" $ do+                 broker <- startTestBroker+                 sendMessage outerSelf broker+                 () <- receiveMessage+                 sendMessage outerSelf ()+                 () <- receiveMessage+                 Broker.stopBroker broker+             unlinkProcess testWorker+             broker <- receiveMessage :: Eff Effects  (Endpoint (Broker.Broker (Stateful.Stateful TestProtocol)))+             brokerAliveAfter1 <- isBrokerAlive broker+             logInfo ("still alive 1: " <> pack (show brokerAliveAfter1))+             lift (brokerAliveAfter1 @=? True)+             sendMessage testWorker ()+             () <- receiveMessage+             brokerAliveAfter2 <- isBrokerAlive broker+             logInfo ("still alive 2: " <> pack (show brokerAliveAfter2))+             lift (brokerAliveAfter2 @=? True)+             sendMessage testWorker ()+             testWorkerMonitorRef <- monitor testWorker+             d1 <- receiveSelectedMessage (selectProcessDown testWorkerMonitorRef)+             logInfo ("got test worker down: " <> pack (show d1))+             testBrokerMonitorRef <- monitorBroker broker+             d2 <- receiveSelectedMessage (selectProcessDown testBrokerMonitorRef)+             logInfo ("got broker down: " <> pack (show d2))+             brokerAliveAfterOwnerExited <- isBrokerAlive broker+             logInfo ("still alive after owner exited: " <> pack (show brokerAliveAfterOwnerExited))+             lift (brokerAliveAfterOwnerExited @=? False)+         , testGroup+             "Diagnostics"+             [ runTestCase "When only time passes the diagnostics do not change" $ do+                 broker <- startTestBroker+                 info1 <- Broker.getDiagnosticInfo broker+                 lift (threadDelay 10000)+                 info2 <- Broker.getDiagnosticInfo broker+                 lift (assertEqual "diagnostics should not differ: " info1 info2)+             , runTestCase "When a child is started the diagnostics change" $ do+                 broker <- startTestBroker+                 info1 <- Broker.getDiagnosticInfo broker+                 logInfo ("got diagnostics: " <> info1)+                 let childId = 1+                 _child <- fromRight (error "failed to spawn child") <$> Broker.spawnChild broker childId+                 info2 <- Broker.getDiagnosticInfo broker+                 logInfo ("got diagnostics: " <> info2)+                 lift $ assertBool ("diagnostics should differ: " ++ show (info1, info2)) (info1 /= info2)+             ]+         , let childId = 1+            in testGroup+                 "Startup and shutdown"+                 [ runTestCase "When a broker is shut down, all children are shutdown" $ do+                     broker <- startTestBroker+                     child <- spawnTestChild broker childId+                     let childPid = _fromEndpoint child+                     brokerMon <- monitorBroker broker+                     childMon <- monitor childPid+                     isProcessAlive childPid >>= lift . assertBool "child process not running"+                     isBrokerAlive broker >>= lift . assertBool "broker process not running"+                     call child (TestGetStringLength "123") >>= lift . assertEqual "child not working" 3+                     stopBroker broker+                     d1@(ProcessDown mon1 er1 _) <-+                       fromMaybe (error "receive timeout 1") <$> receiveAfter (TimeoutMicros 1000000)+                     logInfo ("got process down: " <> pack (show d1))+                     d2@(ProcessDown mon2 er2 _) <-+                       fromMaybe (error "receive timeout 2") <$> receiveAfter (TimeoutMicros 1000000)+                     logInfo ("got process down: " <> pack (show d2))+                     case if mon1 == brokerMon && mon2 == childMon+                            then Right (er1, er2)+                            else if mon1 == childMon && mon2 == brokerMon+                                   then Right (er2, er1)+                                   else Left+                                          ("unexpected monitor down: first: " <> show (mon1, er1) <> ", and then: " <>+                                           show (mon2, er2) <>+                                           ", brokerMon: " <>+                                           show brokerMon <>+                                           ", childMon: " <>+                                           show childMon) of+                       Right (brokerER, childER) -> do+                         lift (assertEqual "bad broker exit reason" ExitNormally brokerER)+                         lift (assertEqual "bad child exit reason" ExitNormally childER)+                       Left x -> lift (assertFailure x)+                 , runTestCase+                     "When a broker is shut down, children that won't shutdown, are killed after some time" $ do+                     broker <- startTestBrokerWith IgnoreNormalExitRequest (TimeoutMicros 10000)+                     child <- spawnTestChild broker childId+                     let childPid = _fromEndpoint child+                     brokerMon <- monitorBroker broker+                     childMon <- monitor childPid+                     isProcessAlive childPid >>= lift . assertBool "child process not running"+                     isBrokerAlive broker >>= lift . assertBool "broker process not running"+                     call child (TestGetStringLength "123") >>= lift . assertEqual "child not working" 3+                     stopBroker broker+                     d1@(ProcessDown mon1 er1 _) <-+                       fromMaybe (error "receive timeout 1") <$> receiveAfter (TimeoutMicros 1000000)+                     logInfo ("got process down: " <> pack (show d1))+                     d2@(ProcessDown mon2 er2 _) <-+                       fromMaybe (error "receive timeout 2") <$> receiveAfter (TimeoutMicros 1000000)+                     logInfo ("got process down: " <> pack (show d2))+                     case if mon1 == brokerMon && mon2 == childMon+                            then Right er1+                            else if mon1 == childMon && mon2 == brokerMon+                                   then Right er2+                                   else Left+                                          ("unexpected monitor down: first: " <> show (mon1, er1) <> ", and then: " <>+                                           show (mon2, er2) <>+                                           ", brokerMon: " <>+                                           show brokerMon <>+                                           ", childMon: " <>+                                           show childMon) of+                       Right brokerER -> do+                         lift (assertEqual "bad broker exit reason" ExitNormally brokerER)+                       Left x -> lift (assertFailure x)+                 ]+         , let i = 123+            in testGroup+                 "Spawning and Using Children"+                 [ runTestCase+                     "When a broker is requested to start two children with the same id, an already started error is returned" $ do+                     broker <- startTestBroker+                     c <- spawnTestChild broker i+                     x <- Broker.spawnChild broker i+                     case x of+                       Left (AlreadyStarted i' c') ->+                         lift $ do+                           assertEqual "bad pid returned" c c'+                           assertEqual "bad child id returned" 123 i'+                       _ -> lift (assertFailure "AlreadyStarted expected!")+                 , runTestCase "When a child is started it can be lookup up" $ do+                     broker <- startTestBroker+                     c <- spawnTestChild broker i+                     c' <- Broker.lookupChild broker i >>= maybe (lift (assertFailure "child not found")) pure+                     lift (assertEqual "lookupChild returned wrong child" c c')+                 , runTestCase "When several children are started they can be lookup up and don't crash" $ do+                     broker <- startTestBroker+                     c <- spawnTestChild broker i+                     someOtherChild <- spawnTestChild broker (i + 1)+                     c' <- Broker.lookupChild broker i >>= maybe (lift (assertFailure "child not found")) pure+                     lift (assertEqual "lookupChild returned wrong child" c c')+                     childStillRunning <- isProcessAlive (_fromEndpoint c)+                     lift (assertBool "child not running" childStillRunning)+                     someOtherChildStillRunning <- isProcessAlive (_fromEndpoint someOtherChild)+                     lift (assertBool "someOtherChild not running" someOtherChildStillRunning)+                 , let startTestBrokerAndChild = do+                         broker <- startTestBroker+                         c <- spawnTestChild broker i+                         cm <- monitor (_fromEndpoint c)+                         return (broker, cm)+                    in testGroup+                         "Stopping children"+                         [ runTestCase "When a child is started it can be stopped" $ do+                             (broker, cm) <- startTestBrokerAndChild+                             Broker.stopChild broker i >>= lift . assertBool "child not found"+                             (ProcessDown _ r _) <- receiveSelectedMessage (selectProcessDown cm)+                             lift (assertEqual "bad exit reason" ExitNormally r)+                         , runTestCase+                             "When a child is stopped but doesn't exit voluntarily, it is kill after some time" $ do+                             broker <- startTestBrokerWith IgnoreNormalExitRequest (TimeoutMicros 5000)+                             c <- spawnTestChild broker i+                             cm <- monitor (_fromEndpoint c)+                             Broker.stopChild broker i >>= lift . assertBool "child not found"+                             (ProcessDown _ r _) <- receiveSelectedMessage (selectProcessDown cm)+                             case r of+                               ExitUnhandledError _ -> return ()+                               _ -> lift (assertFailure ("bad exit reason: " ++ show r))+                         , runTestCase "When a stopChild is called with an unallocated ID, False is returned" $ do+                             (broker, _) <- startTestBrokerAndChild+                             Broker.stopChild broker (i + 1) >>= lift . assertBool "child not found" . not+                         , runTestCase "When a child is stopped, lookup won't find it" $ do+                             (broker, _) <- startTestBrokerAndChild+                             Broker.stopChild broker i >>= lift . assertBool "child not found"+                             x <- Broker.lookupChild broker i+                             lift (assertEqual "lookup should not find a child" Nothing x)+                         , runTestCase "When a child is stopped, the child id can be reused" $ do+                             (broker, _) <- startTestBrokerAndChild+                             Broker.stopChild broker i >>= lift . assertBool "child not found"+                             Broker.spawnChild broker i >>= lift . assertBool "id could not be reused" . isRight+                         ]+                 , let startTestBrokerAndChild = do+                         broker <- startTestBroker+                         c <- spawnTestChild broker i+                         cm <- monitor (_fromEndpoint c)+                         return (broker, c, cm)+                    in testGroup+                         "Child exit handling"+                         [ runTestCase "When a child exits normally, lookupChild will not find it" $ do+                             (broker, c, cm) <- startTestBrokerAndChild+                             cast c (TestInterruptWith NormalExitRequested)+                             (ProcessDown _ r _) <- receiveSelectedMessage (selectProcessDown cm)+                             lift (assertEqual "bad exit reason" ExitNormally r)+                             x <- Broker.lookupChild broker i+                             lift (assertEqual "lookup should not find a child" Nothing x)+                         , runTestCase "When a child exits with an error, lookupChild will not find it" $ do+                             (broker, c, cm) <- startTestBrokerAndChild+                             cast c (TestInterruptWith (ErrorInterrupt "test error reason"))+                             (ProcessDown _ _ _) <- receiveSelectedMessage (selectProcessDown cm)+                             x <- Broker.lookupChild broker i+                             lift (assertEqual "lookup should not find a child" Nothing x)+                         , runTestCase "When a child is interrupted from another process and dies, lookupChild will not find it" $ do+                             (broker, c, cm) <- startTestBrokerAndChild+                             sendInterrupt (_fromEndpoint c) NormalExitRequested+                             (ProcessDown _ _ _) <- receiveSelectedMessage (selectProcessDown cm)+                             x <- Broker.lookupChild broker i+                             lift (assertEqual "lookup should not find a child" Nothing x)+                         , runTestCase "When a child is shutdown from another process and dies, lookupChild will not find it" $ do+                             (broker, c, cm) <- startTestBrokerAndChild+                             self >>= sendShutdown (_fromEndpoint c) . ExitProcessCancelled . Just+                             (ProcessDown _ _ _) <- receiveSelectedMessage (selectProcessDown cm)+                             x <- Broker.lookupChild broker i+                             lift (assertEqual "lookup should not find a child" Nothing x)+                         ]+                 , testGroup "broker events"+                     [ runTestCase "when a child starts the observer is notified" $ do+                         broker <- startTestBroker+                         OQ.observe @(Broker.ChildEvent (Stateful.Stateful TestProtocol)) (100 :: Int) broker $ do+                           c <- Broker.spawnChild @(Stateful.Stateful TestProtocol) broker i >>= either (lift . assertFailure . show) pure+                           e <- OQ.await @(Broker.ChildEvent (Stateful.Stateful TestProtocol))+                           case e of+                            Broker.OnChildSpawned broker' i' c' -> do+                              lift (assertEqual "wrong endpoint" broker broker')+                              lift (assertEqual "wrong child" c c')+                              lift (assertEqual "wrong child-id" i i')+                            _ ->+                              lift (assertFailure ("unexpected event: " ++ show e))+                    , runTestCase "when a child stops the observer is notified" $ do+                         broker <- startTestBroker+                         c <- Broker.spawnChild @(Stateful.Stateful TestProtocol) broker i >>= either (lift . assertFailure . show) pure+                         OQ.observe @(Broker.ChildEvent (Stateful.Stateful TestProtocol)) (100 :: Int) broker $ do+                           Broker.stopChild broker i >>= lift . assertBool "child not found"+                           OQ.await @(Broker.ChildEvent (Stateful.Stateful TestProtocol))+                            >>= \case+                                    Broker.OnChildDown broker' i' c' e -> do+                                      lift (assertEqual "wrong endpoint" broker broker')+                                      lift (assertEqual "wrong child" c c')+                                      lift (assertEqual "wrong child-id" i i')+                                      lift (assertEqual "wrong exit reason" ExitNormally e)+                                    e ->+                                      lift (assertFailure ("unexpected event: " ++ show e))++                    , runTestCase "when a child crashes the observer is notified" $ do+                         broker <- startTestBroker+                         OQ.observe @(Broker.ChildEvent (Stateful.Stateful TestProtocol)) (100 :: Int) broker $ do+                           c <- Broker.spawnChild @(Stateful.Stateful TestProtocol) broker i >>= either (lift . assertFailure . show) pure+                           let expectedError = ExitUnhandledError "test error"+                           sendShutdown (_fromEndpoint c) expectedError+                           OQ.await @(Broker.ChildEvent (Stateful.Stateful TestProtocol))+                            >>= \case+                                    Broker.OnChildSpawned broker' i' c' -> do+                                      lift (assertEqual "wrong endpoint" broker broker')+                                      lift (assertEqual "wrong child" c c')+                                      lift (assertEqual "wrong child-id" i i')+                                    e ->+                                      lift (assertFailure ("unexpected event: " ++ show e))+                           OQ.await @(Broker.ChildEvent (Stateful.Stateful TestProtocol))+                            >>= \case+                                    Broker.OnChildDown broker' i' c' e -> do+                                      lift (assertEqual "wrong endpoint" broker broker')+                                      lift (assertEqual "wrong child" c c')+                                      lift (assertEqual "wrong child-id" i i')+                                      lift (assertEqual "wrong exit reason" expectedError e)+                                    e ->+                                      lift (assertFailure ("unexpected event: " ++ show e))+                    , runTestCase "when a child does not stop when requested and is killed, the oberser is notified correspondingly" $ do+                         broker <- startTestBrokerWith IgnoreNormalExitRequest (TimeoutMicros 5000)+                         c <- spawnTestChild broker i+                         OQ.observe @(Broker.ChildEvent (Stateful.Stateful TestProtocol)) (100 :: Int) broker $ do+                           cm <- monitor (_fromEndpoint c)+                           Broker.stopChild broker i >>= lift . assertBool "child not found"+                           (ProcessDown _ expectedError _) <- receiveSelectedMessage (selectProcessDown cm)+                           OQ.await @(Broker.ChildEvent (Stateful.Stateful TestProtocol))+                            >>= \case+                                    Broker.OnChildDown broker' i' c' e -> do+                                      lift (assertEqual "wrong endpoint" broker broker')+                                      lift (assertEqual "wrong child" c c')+                                      lift (assertEqual "wrong child-id" i i')+                                      lift (assertEqual "wrong exit reason" expectedError e)+                                    e ->+                                      lift (assertFailure ("unexpected event: " ++ show e))++                    , runTestCase "when the broker stops, an OnBrokerShuttingDown is emitted before any child is stopped" $ do+                         broker <- startTestBroker+                         unlinkProcess (broker ^. fromEndpoint)+                         void (Broker.spawnChild @(Stateful.Stateful TestProtocol) broker i+                                >>= either (lift . assertFailure . show) pure)+                         OQ.observe @(Broker.ChildEvent (Stateful.Stateful TestProtocol)) (1000 :: Int) broker $ do+                           Broker.stopBroker broker+                           OQ.await @(Broker.ChildEvent (Stateful.Stateful TestProtocol))+                            >>= \case+                                    Broker.OnBrokerShuttingDown _ -> logNotice "received OnBrokerShuttingDown"+                                    e ->+                                      lift (assertFailure ("unexpected event: " ++ show e))+                           OQ.await @(Broker.ChildEvent (Stateful.Stateful TestProtocol))+                            >>= \case+                                  Broker.OnChildDown _ _ _ e -> lift (assertEqual "wrong exit reason" ExitNormally e)+                                  e ->+                                    lift (assertFailure ("unexpected event: " ++ show e))+                    ]+                 ]+         ])++data TestProtocol+  deriving (Typeable)++type instance ToPretty TestProtocol = PutStr "test"++instance HasPdu TestProtocol where+  data instance Pdu TestProtocol x where+    TestGetStringLength :: String -> Pdu TestProtocol ('Synchronous Int)+    TestInterruptWith :: Interrupt 'Recoverable -> Pdu TestProtocol 'Asynchronous+      deriving Typeable++instance NFData (Pdu TestProtocol x) where+  rnf (TestGetStringLength x) = rnf x+  rnf (TestInterruptWith x) = rnf x++instance Show (Pdu TestProtocol r) where+  show (TestGetStringLength s) = "TestGetStringLength " ++ show s+  show (TestInterruptWith s) = "TestInterruptWith " ++ show s++data TestProtocolServerMode+  = IgnoreNormalExitRequest+  | ExitWhenRequested+  deriving Eq++instance Stateful.Server TestProtocol Effects where+  newtype instance Model TestProtocol = TestProtocolModel () deriving Default+  update _me (TestServerArgs testMode tId) evt =+    case evt of+      OnCast (TestInterruptWith i) -> do+        logInfo (pack (show tId) <> ": stopping with: " <> pack (show i))+        interrupt i+      OnCall rt (TestGetStringLength str) -> do+        logInfo (pack (show tId) <> ": calculating length of: " <> pack str)+        sendReply rt (length str)+      OnInterrupt x -> do+        logNotice (pack (show tId) <> ": " <> pack (show x))+        if testMode == IgnoreNormalExitRequest+          then+            logNotice $ pack (show tId) <> ": ignoring normal exit request"+          else do+            logNotice $ pack (show tId) <> ": exitting normally"+            exitBecause (interruptToExit x)+      _ ->+        logDebug (pack (show tId) <> ": got some info: " <> pack (show evt))+  data instance StartArgument TestProtocol Effects = TestServerArgs TestProtocolServerMode Int++type instance ChildId TestProtocol = Int+
test/Common.hs view
@@ -1,30 +1,95 @@-module Common where+module Common+  ( module Common+  , module Test.Tasty+  , module Test.Tasty.HUnit+  , module Test.Tasty.Runners+  , module Control.Eff.Extend+  , module Control.Monad+  , module GHC.Stack+  , module Control.Concurrent+  , module Control.Concurrent.STM+  , module Control.DeepSeq+  , module Control.Eff+  , module Control.Eff.Concurrent+  , module Control.Eff.Concurrent.Misc+  , module Data.Default+  , module Data.Foldable+  , module Data.Typeable+  , module Data.Text+  , module Data.Either+  , module Data.Maybe+  , module Data.Type.Pretty+  )+where -import Control.Concurrent-import Control.Concurrent.STM-import Control.Eff-import Control.Eff.Concurrent hiding (Timeout)-import Control.Eff.Concurrent.Process.ForkIOScheduler as Scheduler-import Control.Eff.Extend-import Control.Monad (void)-import GHC.Stack-import Test.Tasty-import Test.Tasty.HUnit-import Test.Tasty.Runners+import           Control.Concurrent+import           Control.Concurrent.STM+import           Control.DeepSeq+import           Control.Eff+--import           Control.Eff.Log+import           Control.Eff.Concurrent+import           Control.Eff.Concurrent.Misc+import           Control.Eff.Concurrent.Process.ForkIOScheduler+                                               as Scheduler+import           Control.Eff.Extend+import           Control.Monad+import           Control.Lens+import           Data.Default+import           Data.Foldable+import           Data.Text                      ( Text+                                                , pack+                                                )+import qualified Data.Text                     as T+import           Data.Typeable           hiding ( cast )+import           GHC.Stack+import           Test.Tasty              hiding ( Timeout+                                                , defaultMain+                                                )+import qualified Test.Tasty                    as Tasty+import           Test.Tasty.HUnit+import           Test.Tasty.Runners+import           Data.Either                    ( fromRight+                                                , isLeft+                                                , isRight+                                                )+import           Data.Maybe                     ( fromMaybe )+import           Data.Type.Pretty+import qualified System.IO                     as IO  setTravisTestOptions :: TestTree -> TestTree-setTravisTestOptions = localOption (timeoutSeconds 60) . localOption (NumThreads 1)+setTravisTestOptions =+  localOption (timeoutSeconds 60) . localOption (NumThreads 1) -timeoutSeconds :: Integer -> Timeout-timeoutSeconds seconds = Timeout (seconds * 1000000) (show seconds ++ "s")+timeoutSeconds :: Integer -> Tasty.Timeout+timeoutSeconds seconds =+  Tasty.Timeout (seconds * 1000000) (show seconds ++ "s")  runTestCase :: TestName -> Eff Effects () -> TestTree-runTestCase msg =-  testCase msg .-  runLift . withTraceLogging "unit-tests" local0 allLogMessages . Scheduler.schedule . handleInterrupts onInt-  where-    onInt = lift . assertFailure . show+runTestCase msg et =+  testCase msg $ do+    IO.hSetBuffering IO.stdout IO.LineBuffering+    runLift+      $ withIoLogging (stdoutLogWriter renderMinimalisticWide) "unit-tests" local0 allLogMessages+      $ Scheduler.schedule+      $ handleInterrupts onInt+        et+  where onInt = lift . assertFailure . show +-- | Render a 'LogMessage' human readable, for console logging+renderMinimalisticWide :: LogMessageRenderer T.Text+renderMinimalisticWide l =+  T.unwords $ filter+    (not . T.null)+    [ let s = l ^. lmSeverity . to (T.pack . show)+      in s <> T.replicate (max 0 (15 - T.length s)) " "+    , let p = fromMaybe " no proc " (l ^. lmProcessId)+      in p <> T.replicate (max 0 (55 - T.length p)) " "+    , let msg = l^.lmMessage+      in  msg <> T.replicate (max 0 (100 - T.length msg)) " "+    -- , fromMaybe "" (renderLogMessageSrcLoc l)+    ]++ withTestLogC :: (e -> IO ()) -> (IO (e -> IO ()) -> TestTree) -> TestTree withTestLogC doSchedule k = k (return doSchedule) @@ -33,25 +98,30 @@   r <- send pa   case r of     Interrupted _ -> return ()-    _ -> untilInterrupted pa+    _             -> untilInterrupted pa -scheduleAndAssert ::-     forall r. (LogIo r)+scheduleAndAssert+  :: forall r+   . (LogIo r)   => IO (Eff (Processes r) () -> IO ())   -> ((String -> Bool -> Eff (Processes r) ()) -> Eff (Processes r) ())   -> IO ()-scheduleAndAssert schedulerFactory testCaseAction =-  withFrozenCallStack $ do-    resultVar <- newEmptyTMVarIO-    void-      (applySchedulerFactory-         schedulerFactory-         (testCaseAction (\title cond -> lift (atomically (putTMVar resultVar (title, cond))))))-    (title, result) <- atomically (takeTMVar resultVar)-    assertBool title result+scheduleAndAssert schedulerFactory testCaseAction = withFrozenCallStack $ do+  IO.hSetBuffering IO.stdout IO.LineBuffering+  resultVar <- newEmptyTMVarIO+  void+    (applySchedulerFactory+      schedulerFactory+      (testCaseAction+        (\title cond -> lift (atomically (putTMVar resultVar (title, cond))))+      )+    )+  (title, result) <- atomically (takeTMVar resultVar)+  assertBool title result -applySchedulerFactory ::-     forall r. (LogIo r)+applySchedulerFactory+  :: forall r+   . (LogIo r)   => IO (Eff (Processes r) () -> IO ())   -> Eff (Processes r) ()   -> IO ()@@ -59,8 +129,46 @@   scheduler <- factory   scheduler (procAction >> lift (threadDelay 20000)) -awaitProcessDown :: (HasCallStack, HasProcesses r q) => ProcessId -> Eff r ProcessDown+assertShutdown+  :: (Member Logs r, HasCallStack, HasProcesses r q, Lifted IO r)+  => ProcessId+  -> Interrupt 'NoRecovery+  -> Eff r ()+assertShutdown p r = do+  unlinkProcess p+  m <- monitor p+  sendShutdown p r+  logInfo+    (  "awaitProcessDown: "+    <> pack (show p)+    <> " "+    <> pack (show m)+    )+  logCallStack debugSeverity+  receiveSelectedMessage (selectProcessDown m)+    >>= lift . assertEqual "bad exit reason" r . downReason++awaitProcessDown+  :: (Member Logs r, HasCallStack, HasProcesses r q)+  => ProcessId+  -> Eff r ProcessDown awaitProcessDown p = do   m <- monitor p+  logInfo+    (  "awaitProcessDown: "+    <> pack (show p)+    <> " "+    <> pack (show m)+    )+  logCallStack debugSeverity   receiveSelectedMessage (selectProcessDown m)++awaitProcessDownAny+  :: (Member Logs r, HasCallStack, HasProcesses r q)+  => Eff r ProcessDown+awaitProcessDownAny = do+  logInfo "awaitProcessDownAny"+  logCallStack debugSeverity+  receiveMessage+ 
test/GenServerTests.hs view
@@ -4,17 +4,10 @@   ) where  import Common-import Control.DeepSeq-import Control.Eff-import Control.Eff.Concurrent-import Control.Eff.Concurrent.Protocol.Supervisor as Sup+import Control.Eff.Concurrent.Protocol.Broker as Broker import qualified Control.Eff.Concurrent.Protocol.EffectfulServer as E import qualified Control.Eff.Concurrent.Protocol.StatefulServer as S-import Data.Text as T-import Data.Type.Pretty import Data.Typeable (Typeable)-import Test.Tasty-import Test.Tasty.HUnit  -- ------------------------------ @@ -41,11 +34,12 @@  instance LogIo e => S.Server Small (Processes e) where   data StartArgument Small (Processes e) = MkSmall-  type Model Small = String+  newtype instance Model Small = SmallModel String deriving Default   update _me MkSmall x =     case x of       E.OnCall rt (SmallCall f) ->-       sendReply rt f+       do S.modifyModel (\(SmallModel y) -> SmallModel (y ++ ", " ++ show f))+          sendReply rt f       E.OnCast msg ->        logInfo' (show msg)       other ->@@ -84,26 +78,32 @@  instance LogIo e => S.Server Big (Processes e) where   data instance StartArgument Big (Processes e) = MkBig-  type Model Big = String+  newtype Model Big = BigModel String deriving Default   update me MkBig = \case     E.OnCall rt req ->           case req of             BigCall o -> do               logNotice ("BigCall " <> pack (show o))               sendReply rt o-            BigSmall x -> S.update (toEmbeddedEndpoint me) MkSmall (S.OnCall (toEmbeddedReplyTarget rt) x)+            BigSmall x ->+               S.coerceEffects+                  (S.update+                    (toEmbeddedEndpoint me)+                    MkSmall+                    (S.OnCall (toEmbeddedReplyTarget rt) x))     E.OnCast req ->         case req of-          BigCast o -> S.putModel @Big o-          BigSmall x -> S.update (toEmbeddedEndpoint me) MkSmall (S.OnCast x)+          BigCast o -> S.putModel (BigModel o)+          BigSmall x -> S.coerceEffects (S.update (toEmbeddedEndpoint me) MkSmall (S.OnCast x))     other ->       interrupt (ErrorInterrupt (show other))+ -- ----------------------------------------------------------------------------  test_genServer :: HasCallStack => TestTree test_genServer = setTravisTestOptions $ testGroup "Server" [   runTestCase "When a server is started it handles call Pdus without dieing" $ do-    big <- S.start MkBig+    big <- S.startLink MkBig     call big (BigCall True) >>=  lift . assertBool "invalid result 1"     isProcessAlive (_fromEndpoint big) >>= lift . assertBool "process dead"     call big (BigCall False) >>=  lift . assertBool "invalid result 2" . not
test/LoopTests.hs view
@@ -4,13 +4,7 @@     ) where -import           Control.DeepSeq-import           Control.Eff-import           Control.Eff.Concurrent import           Control.Eff.State.Strict-import           Control.Monad-import           Test.Tasty-import           Test.Tasty.HUnit import           Common import           Control.Eff.Concurrent.Process.SingleThreadedScheduler                                                as Scheduler
test/ObserverTests.hs view
@@ -4,18 +4,11 @@   ) where  import Common-import Control.DeepSeq-import Control.Eff-import Control.Eff.Concurrent import qualified Control.Eff.Concurrent.Protocol.EffectfulServer as S import qualified Control.Eff.Concurrent.Protocol.Observer.Queue as OQ import qualified Control.Eff.Concurrent.Protocol.StatefulServer as M import Control.Lens-import Control.Monad-import Data.Text as T import Data.Typeable (Typeable)-import Test.Tasty-import Test.Tasty.HUnit   test_observer :: HasCallStack => TestTree@@ -26,7 +19,7 @@ basicTests =   [runTestCase "when no observer is present, nothing crashes"       $ do-            testObservable <- S.start TestObservableServerInit+            testObservable <- S.startLink TestObservableServerInit             call testObservable (SendTestEvent "1")             cast testObservable StopTestObservable @@ -34,11 +27,11 @@    , runTestCase "observers receive only messages sent after registration"       $ do-            testObservable <- S.start TestObservableServerInit+            testObservable <- S.startLink TestObservableServerInit             call testObservable (SendTestEvent "1")             call testObservable (SendTestEvent "2")             call testObservable (SendTestEvent "3")-            testObserver <- M.start MkTestObserver+            testObserver <- M.startLink MkTestObserver             registerObserver @String testObservable testObserver             call testObservable (SendTestEvent "4")             call testObservable (SendTestEvent "5")@@ -48,11 +41,11 @@             void $ awaitProcessDown (testObservable ^. fromEndpoint)   , runTestCase "observers receive only messages sent before de-registration"       $ do-            testObservable <- S.start TestObservableServerInit+            testObservable <- S.startLink TestObservableServerInit             call testObservable (SendTestEvent "1")             call testObservable (SendTestEvent "2")             call testObservable (SendTestEvent "3")-            testObserver <- M.start MkTestObserver+            testObserver <- M.startLink MkTestObserver             registerObserver @String testObservable testObserver             call testObservable (SendTestEvent "4")             call testObservable (SendTestEvent "5")@@ -65,11 +58,11 @@             void $ awaitProcessDown (testObservable ^. fromEndpoint)   , runTestCase "observers receive only messages sent between registration and deregistration"       $ do-            testObservable <- S.start TestObservableServerInit+            testObservable <- S.startLink TestObservableServerInit             call testObservable (SendTestEvent "1")             call testObservable (SendTestEvent "2")             call testObservable (SendTestEvent "3")-            testObserver <- M.start MkTestObserver+            testObserver <- M.startLink MkTestObserver             registerObserver @String testObservable testObserver             call testObservable (SendTestEvent "4")             call testObservable (SendTestEvent "5")@@ -83,13 +76,13 @@             void $ awaitProcessDown (testObservable ^. fromEndpoint)   , runTestCase "all observers receive all messages sent between registration and deregistration"       $ do-            testObservable <- S.start TestObservableServerInit+            testObservable <- S.startLink TestObservableServerInit             call testObservable (SendTestEvent "1")             call testObservable (SendTestEvent "2")             call testObservable (SendTestEvent "3")-            testObserver1 <- M.start MkTestObserver-            testObserver2 <- M.start MkTestObserver-            testObserver3 <- M.start MkTestObserver+            testObserver1 <- M.startLink MkTestObserver+            testObserver2 <- M.startLink MkTestObserver+            testObserver3 <- M.startLink MkTestObserver             registerObserver @String testObservable testObserver1             call testObservable (SendTestEvent "4")             registerObserver @String testObservable testObserver2@@ -112,12 +105,12 @@             void $ awaitProcessDown (testObservable ^. fromEndpoint)   , runTestCase "when an observer exits, the messages are still deliviered to the others"       $ do-            testObservable <- S.start TestObservableServerInit+            testObservable <- S.startLink TestObservableServerInit             call testObservable (SendTestEvent "1")             call testObservable (SendTestEvent "2")             call testObservable (SendTestEvent "3")-            testObserver1 <- M.start MkTestObserver-            testObserver2 <- M.start MkTestObserver+            testObserver1 <- M.startLink MkTestObserver+            testObserver2 <- M.startLink MkTestObserver             registerObserver @String testObservable testObserver1             registerObserver @String testObservable testObserver2             call testObservable (SendTestEvent "4")@@ -129,13 +122,30 @@             lift (["4", "5", "6"] @=? es1)             cast testObservable StopTestObservable             void $ awaitProcessDown (testObservable ^. fromEndpoint)+  , runTestCase "evil observer monitoring"+      $ do+            testObservable <- S.startLink TestObservableServerInit+            call testObservable (SendTestEvent "1")+            call testObservable (SendTestEvent "2")+            call testObservable (SendTestEvent "3")+            testObserver1 <- M.startLink MkTestObserver+            testObserver2 <- M.startLink MkTestObserver+            registerObserver @String testObservable testObserver1+            sendShutdown (testObserver2^.fromEndpoint) ExitNormally+            void $ monitor (testObserver2^.fromEndpoint)+            void $ awaitProcessDownAny+            call testObservable (SendTestEvent "6")+            es1 <- call testObserver1 GetCapturedEvents+            lift (["6"] @=? es1)+            cast testObservable StopTestObservable+            void $ awaitProcessDown (testObservable ^. fromEndpoint)   , runTestCase "when an observer registers multiple times, it still gets the messages only once"       $ do-            testObservable <- S.start TestObservableServerInit+            testObservable <- S.startLink TestObservableServerInit             call testObservable (SendTestEvent "1")             call testObservable (SendTestEvent "2")             call testObservable (SendTestEvent "3")-            testObserver1 <- M.start MkTestObserver+            testObserver1 <- M.startLink MkTestObserver             registerObserver @String testObservable testObserver1             registerObserver @String testObservable testObserver1             call testObservable (SendTestEvent "4")@@ -147,11 +157,11 @@             void $ awaitProcessDown (testObservable ^. fromEndpoint)   , runTestCase "when an observer is forgotton multiple times, nothing bad happens"       $ do-            testObservable <- S.start TestObservableServerInit+            testObservable <- S.startLink TestObservableServerInit             call testObservable (SendTestEvent "1")             call testObservable (SendTestEvent "2")             call testObservable (SendTestEvent "3")-            testObserver1 <- M.start MkTestObserver+            testObserver1 <- M.startLink MkTestObserver             registerObserver @String testObservable testObserver1             call testObservable (SendTestEvent "4")             call testObservable (SendTestEvent "5")@@ -170,7 +180,7 @@   testGroup "observer-queue"   [ runTestCase "tryRead"       $ do-          testObservable <- S.start TestObservableServerInit+          testObservable <- S.startLink TestObservableServerInit           let len :: Int               len = 1           OQ.observe  @String len testObservable $ do@@ -182,7 +192,7 @@           void $ awaitProcessDown (testObservable ^. fromEndpoint)   , runTestCase "observe then read"       $ do-          testObservable <- S.start TestObservableServerInit+          testObservable <- S.startLink TestObservableServerInit           let len :: Int               len = 1           OQ.observe  @String len testObservable $ do@@ -197,7 +207,7 @@           void $ awaitProcessDown (testObservable ^. fromEndpoint)   , runTestCase "FIFO"       $ do-          testObservable <- S.start TestObservableServerInit+          testObservable <- S.startLink TestObservableServerInit           let len :: Int               len = 3           OQ.observe  @String len testObservable $ do@@ -212,7 +222,7 @@           void $ awaitProcessDown (testObservable ^. fromEndpoint)   , runTestCase "flush"       $ do-          testObservable <- S.start TestObservableServerInit+          testObservable <- S.startLink TestObservableServerInit           let len :: Int               len = 3           OQ.observe  @String len testObservable $ do@@ -226,7 +236,7 @@           void $ awaitProcessDown (testObservable ^. fromEndpoint)   , runTestCase "when the queue is full, new observations are dropped"       $ do-            testObservable <- S.start TestObservableServerInit+            testObservable <- S.startLink TestObservableServerInit             let len :: Int                 len = 2             OQ.observe  @String len testObservable $ do@@ -242,7 +252,7 @@             void $ awaitProcessDown (testObservable ^. fromEndpoint)   , runTestCase "flush after queue full"       $ do-          testObservable <- S.start TestObservableServerInit+          testObservable <- S.startLink TestObservableServerInit           let len :: Int               len = 3           OQ.observe  @String len testObservable $ do@@ -333,13 +343,12 @@  instance (LogIo r, HasProcesses r q) => M.Server TestObserver r where   data StartArgument TestObserver r = MkTestObserver-  type Model TestObserver = [String]-  setup _ MkTestObserver = pure ([], ())+  newtype instance Model TestObserver = TestObserverModel {fromTestObserverModel :: [String]} deriving Default   update _ MkTestObserver e =     case e of       M.OnCall rt GetCapturedEvents ->-        M.getAndPutModel @TestObserver [] >>= sendReply rt+        M.getAndPutModel (TestObserverModel []) >>= sendReply rt . fromTestObserverModel       M.OnCast (OnTestEvent (Observed x)) ->-        M.modifyModel @TestObserver (++ [x])+        M.modifyModel (\ (TestObserverModel o) -> TestObserverModel (o ++ [x]))       _ ->         logError ("unexpected: " <> pack (show e))
test/ProcessBehaviourTestCases.hs view
@@ -2,39 +2,21 @@  import           Common import           Control.Exception-import           Control.Concurrent-import           Control.Concurrent.STM-import           Control.Eff.Concurrent.Process-import           Control.Eff.Concurrent.Process.Timer-import           Control.Eff.Concurrent.Protocol-import           Control.Eff.Concurrent.Protocol.Wrapper-import           Control.Eff.Concurrent.Protocol.Client-import qualified Control.Eff.Concurrent.Protocol.CallbackServer as Callback+import qualified Control.Eff.Concurrent.Protocol.CallbackServer+                                               as Callback import           Control.Eff.Concurrent.Protocol.EffectfulServer import qualified Control.Eff.Concurrent.Process.ForkIOScheduler                                                as ForkIO import qualified Control.Eff.Concurrent.Process.SingleThreadedScheduler                                                as SingleThreaded-import           Control.Eff-import           Control.Eff.Extend-import           Control.Eff.Log-import           Control.Eff.LogWriter.Async-import           Control.Eff.LogWriter.Console-import           Control.Eff.LogWriter.IO-import           Control.Eff.Loop-import           Control.Monad import           Control.Applicative-import           Control.Lens (view)-import           Control.DeepSeq+import           Control.Lens                   ( view ) import           Data.List                      ( sort ) import           Data.Foldable                  ( traverse_ ) import           Data.Maybe-import           Data.Typeable-import           Data.Type.Pretty import           Data.Void-import           Test.Tasty-import           Test.Tasty.HUnit-import GHC.Generics (Generic)+import           GHC.Generics                   ( Generic )+import           Data.String                    ( fromString )   testInterruptReason :: Interrupt 'Recoverable@@ -43,23 +25,25 @@ test_forkIo :: TestTree test_forkIo = setTravisTestOptions $ withTestLogC   (\c ->-      runLift-    $ withLogging (filteringLogWriter (lmSeverityIsAtLeast errorSeverity) consoleLogWriter)-    $ withAsyncLogWriter (100 :: Int)-    $ ForkIO.schedule c)+    runLift+      $ withLogging+          (filteringLogWriter (lmSeverityIsAtLeast errorSeverity)+                              consoleLogWriter+          )+      $ withAsyncLogWriter (100 :: Int)+      $ ForkIO.schedule c+  )   (\factory -> testGroup "ForkIOScheduler" [allTests factory])   test_singleThreaded :: TestTree test_singleThreaded = setTravisTestOptions $ withTestLogC   (\e ->-    let runEff-          :: Eff LoggingAndIo a-          -> IO a-        runEff =-            runLift-          . withLogging-              (mkLogWriterIO (\m -> when (view lmSeverity m < errorSeverity) (printLogMessage m)))+    let runEff :: Eff LoggingAndIo a -> IO a+        runEff = runLift . withLogging+          (mkLogWriterIO+            (\m -> when (view lmSeverity m < errorSeverity) (printLogMessage m))+          )     in  void $ SingleThreaded.scheduleM runEff yield e   )   (\factory -> testGroup "SingleThreadedScheduler" [allTests factory])@@ -79,6 +63,7 @@     , exitTests schedulerFactory     , pingPongTests schedulerFactory     , yieldLoopTests schedulerFactory+    , delayTests schedulerFactory     , selectiveReceiveTests schedulerFactory     , linkingTests schedulerFactory     , monitoringTests schedulerFactory@@ -128,23 +113,18 @@   :: forall q    . (HasCallStack, LogIo q, Typeable q)   => Eff (Processes q) (Endpoint ReturnToSender)-returnToSenderServer =-  Callback.start @ReturnToSender-    $ Callback.onEvent-        (\evt ->-          case evt of-            OnCall rt msg ->-              case msg of-                StopReturnToSender -> interrupt testInterruptReason-                ReturnToSender fromP echoMsg -> do-                  sendMessage fromP echoMsg-                  yieldProcess-                  sendReply rt True-            OnInterrupt i ->-              interrupt i-            other -> interrupt (ErrorInterrupt (show other))-        )-        "return-to-sender"+returnToSenderServer = Callback.startLink @ReturnToSender $ Callback.onEvent+  (\evt -> case evt of+    OnCall rt msg -> case msg of+      StopReturnToSender           -> interrupt testInterruptReason+      ReturnToSender fromP echoMsg -> do+        sendMessage fromP echoMsg+        yieldProcess+        sendReply rt True+    OnInterrupt i -> interrupt i+    other         -> interrupt (ErrorInterrupt (show other))+  )+  "return-to-sender"  selectiveReceiveTests   :: forall r@@ -170,7 +150,7 @@           senderLoop destination =             traverse_ (sendMessage destination) [1 .. nMax] -        me          <- self+        me           <- self         receiverPid2 <- spawn "reciever loop" (receiverLoop me)         spawn_ "sender loop" (senderLoop receiverPid2)         ok <- receiveMessage @Bool@@ -182,16 +162,44 @@         ok  <- returnToSender srv "test"         ()  <- stopReturnToSender srv         lift (ok @? "selective receive failed")+    , testCase "when sending multiple messages, it is possible to receive them selectively in any order"+    $ applySchedulerFactory schedulerFactory+    $ do+        me <- self+        let messages :: [Int]+            messages = [1 .. 100]+        mRefs <- traverse+                  (\i ->+                    spawn (fromString ("sender-" ++ show i))+                          (yieldProcess >> sendMessage me i >> logInfo ("sent: " <> pack (show i)))+                    >>= monitor)+                  messages+        traverse_+                  (\i ->+                    receiveSelectedMessage (filterMessage (== i))+                    >>= logInfo . ("received: " <> ) . pack . show)+                  messages+        traverse_+                  (\(mref, i) ->+                    logInfo ("waiting for " <> pack (show mref) <> " of " <> pack (show i))+                      >> receiveSelectedMessage (selectProcessDown mref)+                      >>= logInfo . (("down: " <> pack (show i) <> " ") <> ) . pack . show)+                  (mRefs `zip` messages)+     , testCase "flush messages" $ applySchedulerFactory schedulerFactory $ do       me <- self-      spawn_ "sender-bool" $ replicateM_ 10 (sendMessage me True) >> sendMessage me ()+      spawn_ "sender-bool"+        $  replicateM_ 10 (sendMessage me True)+        >> sendMessage me ()       spawn_ "sender-float"         $  replicateM_ 10 (sendMessage me (123.23411 :: Float))         >> sendMessage me ()-      spawn_ "sender-string" $ replicateM_ 10 (sendMessage me ("123"::String)) >> sendMessage me ()-      ()   <- receiveMessage-      ()   <- receiveMessage-      ()   <- receiveMessage+      spawn_ "sender-string"+        $  replicateM_ 10 (sendMessage me ("123" :: String))+        >> sendMessage me ()+      ()       <- receiveMessage+      ()       <- receiveMessage+      ()       <- receiveMessage       -- replicateCheapM_ 40 yieldProcess       messages <- flushMessages       lift (length messages @?= 30)@@ -199,11 +207,62 @@   )  +delayTests+  :: forall r . (LogIo r) => IO (Eff (Processes r) () -> IO ()) -> TestTree+delayTests schedulerFactory =+  let maxN = 100000+  in  setTravisTestOptions+        (testGroup+          "delay tests"+          [ testCase+            "delay many times (forM_)"+            (applySchedulerFactory+              schedulerFactory+              (forM_ [1 :: Int .. maxN] (\_ -> delay (TimeoutMicros 1)))+            )+          , testCase+            "process can be interrupted during a delay"+            (applySchedulerFactory+              schedulerFactory+              (do+                me <- self+                sleeper <- spawn "sleeper"+                              (tryUninterrupted (delay (TimeoutMicros 10_000_000)) >>= sendMessage me)+                delay (TimeoutMicros 1_000)+                let expected = TimeoutInterrupt "test timeout interrupt"+                sendInterrupt sleeper expected+                receiveMessage @(Either (Interrupt 'Recoverable) ()) >>= lift . assertEqual "wrong message" (Left expected)+              )+            )+          , testCase+            "messages are enqueued for a process that is currently delaying"+            (applySchedulerFactory+              schedulerFactory+              (do+                me <- self+                sleeper <- spawn "sleeper"+                              (do sendMessage me True+                                  delay (TimeoutMicros 500_000)+                                  receiveMessage @Int >>= sendMessage me+                                  receiveMessage @Int >>= sendMessage me+                                  receiveMessage @Int >>= sendMessage me+                              )+                let x1, x2, x3 :: Int+                    [x1,x2,x3] = [1..3]+                receiveMessage >>= lift . assertBool "sleeper not started"+                sendMessage sleeper x1+                sendMessage sleeper x2+                sendMessage sleeper x3+                receiveMessage @Int >>= lift . assertEqual "wrong message 1" x1+                receiveMessage @Int >>= lift . assertEqual "wrong message 2" x2+                receiveMessage @Int >>= lift . assertEqual "wrong message 3" x3+              )+            )+          ]+        )+ yieldLoopTests-  :: forall r-   . (LogIo r)-  => IO (Eff (Processes r) () -> IO ())-  -> TestTree+  :: forall r . (LogIo r) => IO (Eff (Processes r) () -> IO ()) -> TestTree yieldLoopTests schedulerFactory =   let maxN = 100000   in  setTravisTestOptions@@ -242,10 +301,7 @@ instance NFData Pong  pingPongTests-  :: forall r-   . (LogIo r)-  => IO (Eff (Processes r) () -> IO ())-  -> TestTree+  :: forall r . (LogIo r) => IO (Eff (Processes r) () -> IO ()) -> TestTree pingPongTests schedulerFactory = testGroup   "Yield Tests"   [ testCase "ping pong a message between two processes, both don't yield"@@ -317,10 +373,7 @@   ]  errorTests-  :: forall r-   . (LogIo r)-  => IO (Eff (Processes r) () -> IO ())-  -> TestTree+  :: forall r . (LogIo r) => IO (Eff (Processes r) () -> IO ()) -> TestTree errorTests schedulerFactory = testGroup   "causing and handling errors"   [ testGroup@@ -343,7 +396,8 @@           traverse_             (\(i :: Int) -> spawn "test" $ foreverCheap               (void-                (  sendMessage (888888 + fromIntegral i) ("test message" :: String)+                (  sendMessage (888888 + fromIntegral i)+                               ("test message" :: String)                 >> yieldProcess                 )               )@@ -362,10 +416,7 @@   ]  concurrencyTests-  :: forall r-   . (LogIo r)-  => IO (Eff (Processes r) () -> IO ())-  -> TestTree+  :: forall r . (LogIo r) => IO (Eff (Processes r) () -> IO ()) -> TestTree concurrencyTests schedulerFactory =   let n = 100   in@@ -378,7 +429,8 @@           me <- self           traverse_             (const-              (spawn "reciever"+              (spawn+                "reciever"                 (do                   m <- receiveAnyMessage                   void (sendAnyMessage me m)@@ -398,24 +450,28 @@       $ scheduleAndAssert schedulerFactory       $ \assertEff -> do           me     <- self-          child1 <- spawn "reciever"+          child1 <- spawn+            "reciever"             (do               m <- receiveAnyMessage               void (sendAnyMessage me m)             )-          child2 <- spawn "sender" (foreverCheap (void (sendMessage 888888 (""::String))))+          child2 <- spawn+            "sender"+            (foreverCheap (void (sendMessage 888888 ("" :: String))))           sendMessage child1 ("test" :: String)           i <- receiveMessage           sendInterrupt child2 testInterruptReason-          assertEff "" (i == ("test"::String))+          assertEff "" (i == ("test" :: String))       , testCase "most processes send foreverCheap"       $ scheduleAndAssert schedulerFactory       $ \assertEff -> do           me <- self           traverse_-            (\(i :: Int) -> spawn "sender"$ do+            (\(i :: Int) -> spawn "sender" $ do               when (i `rem` 5 == 0) $ void $ sendMessage me i-              foreverCheap $ void (sendMessage 888 ("test message to 888" :: String))+              foreverCheap+                $ void (sendMessage 888 ("test message to 888" :: String))             )             [0 .. n]           oks <- replicateM (length [0, 5 .. n]) receiveMessage@@ -425,7 +481,7 @@       $ \assertEff -> do           me <- self           traverse_-            (\(i :: Int) -> spawn "sender"$ do+            (\(i :: Int) -> spawn "sender" $ do               when (i `rem` 5 == 0) $ void $ sendMessage me i               foreverCheap $ void self             )@@ -453,7 +509,11 @@               when (i `rem` 5 == 0) $ void $ sendMessage me i               parent <- self               foreverCheap $ void-                (spawn  "sender" (void (sendMessage parent ("test msg from child"::String))))+                (spawn+                  "sender"+                  (void (sendMessage parent ("test msg from child" :: String))+                  )+                )             )             [0 .. n]           oks <- replicateM (length [0, 5 .. n]) receiveMessage@@ -463,7 +523,7 @@       $ \assertEff -> do           me <- self           traverse_-            (\(i :: Int) -> spawn  "sender" $ do+            (\(i :: Int) -> spawn "sender" $ do               when (i `rem` 5 == 0) $ void $ sendMessage me i               foreverCheap $ void receiveAnyMessage             )@@ -473,10 +533,7 @@       ]  exitTests-  :: forall r-   . (LogIo r)-  => IO (Eff (Processes r) () -> IO ())-  -> TestTree+  :: forall r . (LogIo r) => IO (Eff (Processes r) () -> IO ()) -> TestTree exitTests schedulerFactory =   testGroup "process exit tests"     $ [ testGroup@@ -509,11 +566,18 @@           [ ( "receiving"             , void               (send-                (ReceiveSelectedMessage @r (filterMessage (== ("test message" :: String))))+                (ReceiveSelectedMessage @r+                  (filterMessage (== ("test message" :: String)))+                )               )             )           , ( "sending"-            , void (send (SendMessage @r 44444 (toStrictDynamic ("test message" :: String))))+            , void+              (send+                (SendMessage @r 44444+                                (toStrictDynamic ("test message" :: String))+                )+              )             )           , ( "sending shutdown"             , void (send (SendShutdown @r 44444 ExitNormally))@@ -522,7 +586,8 @@           , ( "spawn-ing"             , void               (send-                (Spawn @r  "reciever"+                (Spawn @r+                  "reciever"                   (void (send (ReceiveSelectedMessage @r selectAnyMessage)))                 )               )@@ -541,11 +606,18 @@           [ ( "receiving"             , void               (send-                (ReceiveSelectedMessage @r (filterMessage (== ("test message"::String))))+                (ReceiveSelectedMessage @r+                  (filterMessage (== ("test message" :: String)))+                )               )             )           , ( "sending"-            , void (send (SendMessage @r 44444 (toStrictDynamic ("test message"::String))))+            , void+              (send+                (SendMessage @r 44444+                                (toStrictDynamic ("test message" :: String))+                )+              )             )           , ( "sending shutdown"             , void (send (SendShutdown @r 44444 ExitNormally))@@ -555,7 +627,8 @@             , void               (send                 (Spawn @r-                  "reciever"  (void (send (ReceiveSelectedMessage @r selectAnyMessage)))+                  "reciever"+                  (void (send (ReceiveSelectedMessage @r selectAnyMessage)))                 )               )             )@@ -573,7 +646,7 @@           $ \assertEff -> do               p1 <- spawn "busyloop" $ foreverCheap busyEffect               lift (threadDelay 1000)-              void $ spawn "sleep loop"  $ do+              void $ spawn "sleep loop" $ do                 lift (threadDelay 1000)                 doExit               lift (threadDelay 100000)@@ -587,11 +660,18 @@           [ ( "receiving"             , void               (send-                (ReceiveSelectedMessage @r (filterMessage (== ("test message"::String))))+                (ReceiveSelectedMessage @r+                  (filterMessage (== ("test message" :: String)))+                )               )             )           , ( "sending"-            , void (send (SendMessage @r 44444 (toStrictDynamic ("test message"::String))))+            , void+              (send+                (SendMessage @r 44444+                                (toStrictDynamic ("test message" :: String))+                )+              )             )           , ( "sending shutdown"             , void (send (SendShutdown @r 44444 ExitNormally))@@ -610,8 +690,10 @@         , (howToExit, doExit    ) <-           [ ("normally"        , void exitNormally)           , ("simply returning", return ())-          , ("raiseError", void (interrupt (ErrorInterrupt "test error raised")))-          , ("exitWithError"   , void (exitWithError "test error exit"))+          , ( "raiseError"+            , void (interrupt (ErrorInterrupt "test error raised"))+            )+          , ("exitWithError", void (exitWithError "test error exit"))           , ( "sendShutdown to self"             , do               me <- self@@ -623,10 +705,7 @@   sendShutdownTests-  :: forall r-   . (LogIo r)-  => IO (Eff (Processes r) () -> IO ())-  -> TestTree+  :: forall r . (LogIo r) => IO (Eff (Processes r) () -> IO ()) -> TestTree sendShutdownTests schedulerFactory = testGroup   "sendShutdown"   [ testCase "... self" $ applySchedulerFactory schedulerFactory $ do@@ -650,19 +729,22 @@     $ scheduleAndAssert schedulerFactory     $ \assertEff -> do         me    <- self-        other <- spawn "interrupted"+        other <- spawn+          "interrupted"           (do-            untilInterrupted (SendMessage @r 666666 (toStrictDynamic ("test"::String)))-            void (sendMessage me ("OK"::String))+            untilInterrupted+              (SendMessage @r 666666 (toStrictDynamic ("test" :: String)))+            void (sendMessage me ("OK" :: String))           )         void (sendInterrupt other testInterruptReason)         a <- receiveMessage-        assertEff "" (a == ("OK"::String))+        assertEff "" (a == ("OK" :: String))     , testCase "while it is receiving"     $ scheduleAndAssert schedulerFactory     $ \assertEff -> do         me    <- self-        other <- spawn "interrupted"+        other <- spawn+          "interrupted"           (do             untilInterrupted (ReceiveSelectedMessage @r selectAnyMessage)             void (sendMessage me ("OK" :: String))@@ -674,7 +756,8 @@     $ scheduleAndAssert schedulerFactory     $ \assertEff -> do         me    <- self-        other <- spawn "interrupted"+        other <- spawn+          "interrupted"           (do             untilInterrupted (SelfPid @r)             void (sendMessage me ("OK" :: String))@@ -686,26 +769,28 @@     $ scheduleAndAssert schedulerFactory     $ \assertEff -> do         me    <- self-        other <- spawn "interrupted"+        other <- spawn+          "interrupted"           (do             untilInterrupted (Spawn @r "returner" (return ()))-            void (sendMessage me ("OK"::String))+            void (sendMessage me ("OK" :: String))           )         void (sendInterrupt other testInterruptReason)         a <- receiveMessage-        assertEff "" (a == ("OK"::String))+        assertEff "" (a == ("OK" :: String))     , testCase "while it is sending shutdown messages"     $ scheduleAndAssert schedulerFactory     $ \assertEff -> do         me    <- self-        other <- spawn "interrupted"+        other <- spawn+          "interrupted"           (do             untilInterrupted (SendShutdown @r 777 ExitNormally)-            void (sendMessage me ("OK"::String))+            void (sendMessage me ("OK" :: String))           )         void (sendInterrupt other testInterruptReason)         a <- receiveMessage-        assertEff "" (a == ("OK"::String))+        assertEff "" (a == ("OK" :: String))     , testCase "handleInterrupt handles my own interrupts"     $ scheduleAndAssert schedulerFactory     $ \assertEff ->@@ -716,10 +801,7 @@   ]  linkingTests-  :: forall r-   . (LogIo r)-  => IO (Eff (Processes r) () -> IO ())-  -> TestTree+  :: forall r . (LogIo r) => IO (Eff (Processes r) () -> IO ()) -> TestTree linkingTests schedulerFactory = setTravisTestOptions   (testGroup     "process linking tests"@@ -762,7 +844,7 @@           (lift . (@?= LinkedProcessCrashed foo))           (do             linkProcess foo-            sendShutdown foo ExitProcessCancelled+            self >>= sendShutdown foo . ExitProcessCancelled . Just             void (receiveMessage @Void)           )     , testCase "link multiple times"@@ -779,7 +861,7 @@             linkProcess foo             linkProcess foo             linkProcess foo-            sendShutdown foo ExitProcessCancelled+            self >>= sendShutdown foo . ExitProcessCancelled . Just             void (receiveMessage @Void)           )     , testCase "unlink multiple times"@@ -798,18 +880,22 @@             unlinkProcess foo             unlinkProcess foo             withMonitor foo $ \ref -> do-              sendShutdown foo ExitProcessCancelled+              self >>= sendShutdown foo . ExitProcessCancelled . Just               void (receiveSelectedMessage (selectProcessDown ref))           )     , testCase "spawnLink" $ applySchedulerFactory schedulerFactory $ do       let foo = void (receiveMessage @Void)-      handleInterrupts (\er -> lift (isProcessDownInterrupt Nothing er @? show er)) $ do-        x <- spawnLink "foo" foo-        sendShutdown x ExitProcessCancelled-        void (receiveMessage @Void)-    , testCase "spawnLink and child exits by returning from spawn" $ applySchedulerFactory schedulerFactory $ do+      handleInterrupts+          (\er -> lift (isProcessDownInterrupt Nothing er @? show er))+        $ do+            x <- spawnLink "foo" foo+            self >>= sendShutdown x . ExitProcessCancelled . Just+            void (receiveMessage @Void)+    , testCase "spawnLink and child exits by returning from spawn"+    $ applySchedulerFactory schedulerFactory+    $ do         me <- self-        u <- spawn "unlinker" $ do+        u  <- spawn "unlinker" $ do           logCritical "unlinked child started"           l <- spawnLink "linked" $ do             logCritical "linked child started"@@ -823,16 +909,17 @@         sendMessage l ()         pL@(ProcessDown _ _ _) <- receiveMessage         logCritical' ("linked process down: " <> show pL)-        _ <- monitor u+        _   <- monitor u         mpU <- receiveAfter (TimeoutMicros 1000)         case mpU of           Just (pU@(ProcessDown _ _ _)) ->             error ("unlinked process down: " <> show pU)-          Nothing ->  logInfo "passed"--    , testCase "spawnLink and child exits via exitWithError" $ applySchedulerFactory schedulerFactory $ do+          Nothing -> logInfo "passed"+    , testCase "spawnLink and child exits via exitWithError"+    $ applySchedulerFactory schedulerFactory+    $ do         me <- self-        u <- spawn "unlinker" $ do+        u  <- spawn "unlinker" $ do           logCritical "unlinked child started"           l <- spawnLink "linker" $ do             logCritical "linked child started"@@ -847,13 +934,12 @@         sendMessage l ()         pL@(ProcessDown _ _ _) <- receiveMessage         logCritical' ("linked process down: " <> show pL)-        _ <- monitor u+        _   <- monitor u         mpU <- receiveAfter (TimeoutMicros 1000)         case mpU of           Just (pU@(ProcessDown _ _ _)) ->             logInfo' ("unlinked process down: " <> show pU)-          Nothing ->  error "linked process not exited!"-+          Nothing -> error "linked process not exited!"     , testCase "ignore normal exit"     $ applySchedulerFactory schedulerFactory     $ do@@ -862,26 +948,31 @@               logNotice "linker"               foreverCheap $ do                 x <- receiveMessage-                linkProcess x-                sendMessage mainProc True+                case x of+                  Right p -> do+                    linkProcess p+                    sendMessage mainProc True+                  Left e ->+                    exitBecause e         linker <- spawnLink "link-server" linkingServer         logNotice "mainProc"         do           x <- spawnLink "x1" (logNotice "x 1" >> void (receiveMessage @Void))           withMonitor x $ \xRef -> do-            sendMessage linker x+            sendMessage linker (Right x :: Either (Interrupt 'NoRecovery) ProcessId)             void $ receiveSelectedMessage (filterMessage id)             sendShutdown x ExitNormally             void (receiveSelectedMessage (selectProcessDown xRef))         do           x <- spawnLink "x2" (logNotice "x 2" >> void (receiveMessage @Void))           withMonitor x $ \xRef -> do-            sendMessage linker x+            sendMessage linker (Right x :: Either (Interrupt 'NoRecovery) ProcessId)             void $ receiveSelectedMessage (filterMessage id)             sendShutdown x ExitNormally             void (receiveSelectedMessage (selectProcessDown xRef))         handleInterrupts (lift . (LinkedProcessCrashed linker @=?)) $ do-          sendShutdown linker ExitProcessCancelled+          me <- self+          sendMessage linker (Left (ExitProcessCancelled (Just me)) :: Either (Interrupt 'NoRecovery) ProcessId)           void (receiveMessage @Void)     , testCase "unlink" $ applySchedulerFactory schedulerFactory $ do       let@@ -892,7 +983,7 @@           lift (("unlink foo1" :: String) @=? r1)           unlinkProcess foo1Pid           sendMessage barPid ("unlinked foo1" :: String, foo1Pid)-          receiveMessage >>= lift . (@?= ("the end":: String))+          receiveMessage >>= lift . (@?= ("the end" :: String))           exitWithError "foo two"         bar foo2Pid parentPid = do           linkProcess foo2Pid@@ -904,7 +995,7 @@             (const (return ()))             (do               linkProcess foo1Pid-              sendShutdown foo1Pid ExitProcessCancelled+              self >>= sendShutdown foo1Pid . ExitProcessCancelled . Just               void (receiveMessage @Void)             )           handleInterrupts@@ -930,10 +1021,7 @@   )  monitoringTests-  :: forall r-   . (LogIo r)-  => IO (Eff (Processes r) () -> IO ())-  -> TestTree+  :: forall r . (LogIo r) => IO (Eff (Processes r) () -> IO ()) -> TestTree monitoringTests schedulerFactory = setTravisTestOptions   (testGroup     "process monitoring tests"@@ -943,8 +1031,42 @@         let badPid = 132123         ref <- monitor badPid         pd  <- receiveSelectedMessage (selectProcessDown ref)-        lift (downReason pd @?= SomeExitReason (OtherProcessNotRunning badPid))+        lift (downReason pd @?= ExitOtherProcessNotRunning badPid)         lift (threadDelay 10000)+    , testCase+      "monitor twice, once when it is running and one, when the monitored process is not running (variant 1)"+    $ applySchedulerFactory schedulerFactory+    $ do+        target <- spawn "reciever-exit-value" (receiveMessage >>= exitBecause)+        me     <- self+        spawn_ "monitor 1" $ do+          ref1 <- monitor target+          receiveSelectedMessage (selectProcessDown ref1)+            >>= sendMessage me+            .   Just+        lift (threadDelay 10000)+        sendMessage target ExitNormally+        pd1 <- receiveMessage+        lift (downReason <$> pd1 @?= Just ExitNormally)+        ref <- monitor target+        pd2 <- receiveSelectedMessage (selectProcessDown ref)+        lift (downReason pd2 @?= ExitOtherProcessNotRunning target)+        lift (threadDelay 10000)+    , testCase+      "spawn, shutdown and monitor many times in a tight loop"+    $ applySchedulerFactory schedulerFactory+    $ do+        tests <- replicateM 60 $ spawn "spawn, shutdown and monitor test" $ do+          target <- spawn "target" (receiveMessage >>= exitBecause)+          replicateM_ 102 $ spawn_ "monitor" $ do+            ref <- monitor target+            logInfo ("monitoring now" <> pack (show ref))+            void $ receiveSelectedMessage (selectProcessDown ref)+          sendMessage target ExitNormally+          ref <- monitor target+          logInfo ("monitoring now" <> pack (show ref))+          void (receiveSelectedMessage (selectProcessDown ref))+        traverse_ awaitProcessDown tests     , testCase "monitored process exit normally"     $ applySchedulerFactory schedulerFactory     $ do@@ -952,7 +1074,7 @@         ref    <- monitor target         sendMessage target ExitNormally         pd <- receiveSelectedMessage (selectProcessDown ref)-        lift (downReason pd @?= SomeExitReason ExitNormally)+        lift (downReason pd @?= ExitNormally)         lift (threadDelay 10000)     , testCase "multiple monitors some demonitored"     $ applySchedulerFactory schedulerFactory@@ -967,20 +1089,21 @@         demonitor ref5         sendMessage target ExitNormally         pd1 <- receiveSelectedMessage (selectProcessDown ref1)-        lift (downReason pd1 @?= SomeExitReason ExitNormally)+        lift (downReason pd1 @?= ExitNormally)         pd2 <- receiveSelectedMessage (selectProcessDown ref2)-        lift (downReason pd2 @?= SomeExitReason ExitNormally)+        lift (downReason pd2 @?= ExitNormally)         pd4 <- receiveSelectedMessage (selectProcessDown ref4)-        lift (downReason pd4 @?= SomeExitReason ExitNormally)+        lift (downReason pd4 @?= ExitNormally)         lift (threadDelay 10000)     , testCase "monitored process killed"     $ applySchedulerFactory schedulerFactory     $ do         target <- spawn "reciever-exit-value" (receiveMessage >>= exitBecause)         ref    <- monitor target-        sendMessage target ExitProcessCancelled+        self >>= sendMessage target . ExitProcessCancelled . Just         pd <- receiveSelectedMessage (selectProcessDown ref)-        lift (downReason pd @?= SomeExitReason ExitProcessCancelled)+        me <- self+        lift (downReason pd @?= ExitProcessCancelled (Just me))         lift (threadDelay 10000)     , testCase "demonitored process killed"     $ applySchedulerFactory schedulerFactory@@ -988,7 +1111,7 @@         target <- spawn "reciever-exit-value" (receiveMessage >>= exitBecause)         ref    <- monitor target         demonitor ref-        sendMessage target ExitProcessCancelled+        self >>= sendMessage target . ExitProcessCancelled . Just         me <- self         spawn_ "wait-and-send" (lift (threadDelay 10000) >> sendMessage me ())         pd <- receiveSelectedMessage@@ -1000,10 +1123,7 @@   )  timerTests-  :: forall r-   . (LogIo r)-  => IO (Eff (Processes r) () -> IO ())-  -> TestTree+  :: forall r . (LogIo r) => IO (Eff (Processes r) () -> IO ()) -> TestTree timerTests schedulerFactory = setTravisTestOptions   (testGroup     "process timer tests"@@ -1017,7 +1137,8 @@     $ applySchedulerFactory schedulerFactory     $ do         me    <- self-        other <- spawn "reciever"+        other <- spawn+          "reciever"           (do             r <- receiveMessage @()             lift (r @?= ())@@ -1036,7 +1157,8 @@             testMsg :: Float             testMsg = 123         me    <- self-        other <- spawn "test"+        other <- spawn+          "test"           (do             replicateM_ n $ sendMessage me ("bad message" :: String)             r <- receiveMessage @()@@ -1056,55 +1178,72 @@     ]   ) -processDetailsTests ::-     forall r. (LogIo r)-  => IO (Eff (Processes r) () -> IO ())-  -> TestTree-processDetailsTests schedulerFactory =-  setTravisTestOptions-    (testGroup-       "process info tests"-       [ testCase-           "no infos for a non-existant process"-           (applySchedulerFactory-              schedulerFactory-              (do let nonExistentPid = ProcessId 123-                  me <- self-                  pInfo <- getProcessState nonExistentPid-                  lift (assertEqual "" (nonExistentPid /= me) (isNothing pInfo))))-       , testCase-           "no infos for a dead process"-           (applySchedulerFactory-              schedulerFactory-              (do deadProc <- spawn "dead" (return ())-                  mr <- monitor deadProc-                  void $ receiveSelectedMessage (selectProcessDown mr)-                  pInfo <- getProcessState deadProc-                  lift (assertBool "no process state of a dead process expected" (isNothing pInfo))))-       , testGroup-           "process title tests"-           [ testCase-               "getProcessState returns the title passed to spawn"-               (applySchedulerFactory-                  schedulerFactory-                  (do let expectedTitle = "expected title"-                      p <- spawn expectedTitle (void receiveAnyMessage)-                      (actualTitle, _, _) <- fromJust <$> getProcessState p-                      lift (assertEqual "unexpected process title" expectedTitle actualTitle)))-           ]-       , testGroup "process details tests" [-            testCase-                       "update"-                       (applySchedulerFactory-                          schedulerFactory-                          (do let expected1 = "test details 1"-                                  expected2 = "test details 2"-                              updateProcessDetails expected1-                              (_, actual1 , _) <- fromJust <$> (self >>= getProcessState)-                              lift (assertEqual "1" expected1 actual1)-                              updateProcessDetails expected2-                              (_, actual2 , _) <- fromJust <$> (self >>= getProcessState)-                              lift (assertEqual "2" expected2 actual2)-                              ))-       ]-       ])+processDetailsTests+  :: forall r . (LogIo r) => IO (Eff (Processes r) () -> IO ()) -> TestTree+processDetailsTests schedulerFactory = setTravisTestOptions+  (testGroup+    "process info tests"+    [ testCase+      "no infos for a non-existant process"+      (applySchedulerFactory+        schedulerFactory+        (do+          let nonExistentPid = ProcessId 123+          me    <- self+          pInfo <- getProcessState nonExistentPid+          lift (assertEqual "" (nonExistentPid /= me) (isNothing pInfo))+        )+      )+    , testCase+      "no infos for a dead process"+      (applySchedulerFactory+        schedulerFactory+        (do+          deadProc <- spawn "dead" (return ())+          mr       <- monitor deadProc+          void $ receiveSelectedMessage (selectProcessDown mr)+          pInfo <- getProcessState deadProc+          lift+            (assertBool "no process state of a dead process expected"+                        (isNothing pInfo)+            )+        )+      )+    , testGroup+      "process title tests"+      [ testCase+          "getProcessState returns the title passed to spawn"+          (applySchedulerFactory+            schedulerFactory+            (do+              let expectedTitle = "expected title"+              p <- spawn expectedTitle (void receiveAnyMessage)+              (actualTitle, _, _) <- fromJust <$> getProcessState p+              lift+                (assertEqual "unexpected process title"+                             expectedTitle+                             actualTitle+                )+            )+          )+      ]+    , testGroup+      "process details tests"+      [ testCase+          "update"+          (applySchedulerFactory+            schedulerFactory+            (do+              let expected1 = "test details 1"+                  expected2 = "test details 2"+              updateProcessDetails expected1+              (_, actual1, _) <- fromJust <$> (self >>= getProcessState)+              lift (assertEqual "1" expected1 actual1)+              updateProcessDetails expected2+              (_, actual2, _) <- fromJust <$> (self >>= getProcessState)+              lift (assertEqual "2" expected2 actual2)+            )+          )+      ]+    ]+  )
− test/SupervisorTests.hs
@@ -1,291 +0,0 @@-module SupervisorTests-  ( test_Supervisor-  ) where--import Common-import Control.Concurrent (threadDelay)-import Control.DeepSeq-import Control.Eff-import Control.Eff.Concurrent-import Control.Eff.Concurrent.Protocol.EffectfulServer (Event(..))-import qualified Control.Eff.Concurrent.Protocol.StatefulServer as Stateful-import Control.Eff.Concurrent.Protocol.Supervisor as Sup-import Control.Eff.Concurrent.Process.Timer-import Data.Either (fromRight, isLeft, isRight)-import Data.Maybe (fromMaybe)-import Data.Text (pack)-import Data.Type.Pretty-import Data.Typeable (Typeable)-import Test.Tasty-import Test.Tasty.HUnit--test_Supervisor :: HasCallStack => TestTree-test_Supervisor =-  setTravisTestOptions $-  testGroup-    "Supervisor"-    (let startTestSup = startTestSupWith ExitWhenRequested (TimeoutMicros 500000)-         startTestSupWith e t = Sup.startSupervisor (MkSupConfig t (Stateful.Init . TestServerArgs e))-         spawnTestChild sup i = Sup.spawnChild sup i >>= either (lift . assertFailure . show) pure-      in [ runTestCase "The supervisor starts and is shut down" $ do-             outerSelf <- self-             testWorker <--               spawn "test-worker" $ do-                 sup <- startTestSup-                 sendMessage outerSelf sup-                 () <- receiveMessage-                 sendMessage outerSelf ()-                 () <- receiveMessage-                 Sup.stopSupervisor sup-             unlinkProcess testWorker-             sup <- receiveMessage :: Eff Effects  (Endpoint (Sup.Sup (Stateful.Stateful TestProtocol)))-             supAliveAfter1 <- isSupervisorAlive sup-             logInfo ("still alive 1: " <> pack (show supAliveAfter1))-             lift (supAliveAfter1 @=? True)-             sendMessage testWorker ()-             () <- receiveMessage-             supAliveAfter2 <- isSupervisorAlive sup-             logInfo ("still alive 2: " <> pack (show supAliveAfter2))-             lift (supAliveAfter2 @=? True)-             sendMessage testWorker ()-             testWorkerMonitorRef <- monitor testWorker-             d1 <- receiveSelectedMessage (selectProcessDown testWorkerMonitorRef)-             logInfo ("got test worker down: " <> pack (show d1))-             testSupervisorMonitorRef <- monitorSupervisor sup-             d2 <- receiveSelectedMessage (selectProcessDown testSupervisorMonitorRef)-             logInfo ("got supervisor down: " <> pack (show d2))-             supAliveAfterOwnerExited <- isSupervisorAlive sup-             logInfo ("still alive after owner exited: " <> pack (show supAliveAfterOwnerExited))-             lift (supAliveAfterOwnerExited @=? False)-         , testGroup-             "Diagnostics"-             [ runTestCase "When only time passes the diagnostics do not change" $ do-                 sup <- startTestSup-                 info1 <- Sup.getDiagnosticInfo sup-                 lift (threadDelay 10000)-                 info2 <- Sup.getDiagnosticInfo sup-                 lift (assertEqual "diagnostics should not differ: " info1 info2)-             , runTestCase "When a child is started the diagnostics change" $ do-                 sup <- startTestSup-                 info1 <- Sup.getDiagnosticInfo sup-                 logInfo ("got diagnostics: " <> info1)-                 let childId = 1-                 _child <- fromRight (error "failed to spawn child") <$> Sup.spawnChild sup childId-                 info2 <- Sup.getDiagnosticInfo sup-                 logInfo ("got diagnostics: " <> info2)-                 lift $ assertBool ("diagnostics should differ: " ++ show (info1, info2)) (info1 /= info2)-             ]-         , let childId = 1-            in testGroup-                 "Startup and shutdown"-                 [ runTestCase "When a supervisor is shut down, all children are shutdown" $ do-                     sup <- startTestSup-                     child <- spawnTestChild sup childId-                     let childPid = _fromEndpoint child-                     supMon <- monitorSupervisor sup-                     childMon <- monitor childPid-                     isProcessAlive childPid >>= lift . assertBool "child process not running"-                     isSupervisorAlive sup >>= lift . assertBool "supervisor process not running"-                     call child (TestGetStringLength "123") >>= lift . assertEqual "child not working" 3-                     stopSupervisor sup-                     d1@(ProcessDown mon1 er1 _) <--                       fromMaybe (error "receive timeout 1") <$> receiveAfter (TimeoutMicros 1000000)-                     logInfo ("got process down: " <> pack (show d1))-                     d2@(ProcessDown mon2 er2 _) <--                       fromMaybe (error "receive timeout 2") <$> receiveAfter (TimeoutMicros 1000000)-                     logInfo ("got process down: " <> pack (show d2))-                     case if mon1 == supMon && mon2 == childMon-                            then Right (er1, er2)-                            else if mon1 == childMon && mon2 == supMon-                                   then Right (er2, er1)-                                   else Left-                                          ("unexpected monitor down: first: " <> show (mon1, er1) <> ", and then: " <>-                                           show (mon2, er2) <>-                                           ", supMon: " <>-                                           show supMon <>-                                           ", childMon: " <>-                                           show childMon) of-                       Right (supER, childER) -> do-                         lift (assertEqual "bad supervisor exit reason" (SomeExitReason ExitNormally) supER)-                         lift (assertEqual "bad child exit reason" (SomeExitReason ExitNormally) childER)-                       Left x -> lift (assertFailure x)-                 , runTestCase-                     "When a supervisor is shut down, children that won't shutdown, are killed after some time" $ do-                     sup <- startTestSupWith IgnoreNormalExitRequest (TimeoutMicros 10000)-                     child <- spawnTestChild sup childId-                     let childPid = _fromEndpoint child-                     supMon <- monitorSupervisor sup-                     childMon <- monitor childPid-                     isProcessAlive childPid >>= lift . assertBool "child process not running"-                     isSupervisorAlive sup >>= lift . assertBool "supervisor process not running"-                     call child (TestGetStringLength "123") >>= lift . assertEqual "child not working" 3-                     stopSupervisor sup-                     d1@(ProcessDown mon1 er1 _) <--                       fromMaybe (error "receive timeout 1") <$> receiveAfter (TimeoutMicros 1000000)-                     logInfo ("got process down: " <> pack (show d1))-                     d2@(ProcessDown mon2 er2 _) <--                       fromMaybe (error "receive timeout 2") <$> receiveAfter (TimeoutMicros 1000000)-                     logInfo ("got process down: " <> pack (show d2))-                     case if mon1 == supMon && mon2 == childMon-                            then Right (er1, er2)-                            else if mon1 == childMon && mon2 == supMon-                                   then Right (er2, er1)-                                   else Left-                                          ("unexpected monitor down: first: " <> show (mon1, er1) <> ", and then: " <>-                                           show (mon2, er2) <>-                                           ", supMon: " <>-                                           show supMon <>-                                           ", childMon: " <>-                                           show childMon) of-                       Right (supER, childER) -> do-                         lift (assertEqual "bad supervisor exit reason" (SomeExitReason ExitNormally) supER)-                         lift (assertBool "bad child exit reason" (isLeft (fromSomeExitReason childER)))-                       Left x -> lift (assertFailure x)-                 ]-         , let i = 123-            in testGroup-                 "Spawning and Using Children"-                 [ runTestCase-                     "When a supervisor is requested to start two children with the same id, an already started error is returned" $ do-                     sup <- startTestSup-                     c <- spawnTestChild sup i-                     x <- Sup.spawnChild sup i-                     case x of-                       Left (AlreadyStarted i' c') ->-                         lift $ do-                           assertEqual "bad pid returned" c c'-                           assertEqual "bad child id returned" 123 i'-                       _ -> lift (assertFailure "AlreadyStarted expected!")-                 , runTestCase "When a child is started it can be lookup up" $ do-                     sup <- startTestSup-                     c <- spawnTestChild sup i-                     c' <- Sup.lookupChild sup i >>= maybe (lift (assertFailure "child not found")) pure-                     lift (assertEqual "lookupChild returned wrong child" c c')-                 , runTestCase "When several children are started they can be lookup up and don't crash" $ do-                     sup <- startTestSup-                     c <- spawnTestChild sup i-                     someOtherChild <- spawnTestChild sup (i + 1)-                     c' <- Sup.lookupChild sup i >>= maybe (lift (assertFailure "child not found")) pure-                     lift (assertEqual "lookupChild returned wrong child" c c')-                     childStillRunning <- isProcessAlive (_fromEndpoint c)-                     lift (assertBool "child not running" childStillRunning)-                     someOtherChildStillRunning <- isProcessAlive (_fromEndpoint someOtherChild)-                     lift (assertBool "someOtherChild not running" someOtherChildStillRunning)-                 , let startTestSupAndChild = do-                         sup <- startTestSup-                         c <- spawnTestChild sup i-                         cm <- monitor (_fromEndpoint c)-                         return (sup, cm)-                    in testGroup-                         "Stopping children"-                         [ runTestCase "When a child is started it can be stopped" $ do-                             (sup, cm) <- startTestSupAndChild-                             Sup.stopChild sup i >>= lift . assertBool "child not found"-                             (ProcessDown _ r _) <- receiveSelectedMessage (selectProcessDown cm)-                             lift (assertEqual "bad exit reason" (SomeExitReason ExitNormally) r)-                         , runTestCase-                             "When a child is stopped but doesn't exit voluntarily, it is kill after some time" $ do-                             sup <- startTestSupWith IgnoreNormalExitRequest (TimeoutMicros 5000)-                             c <- spawnTestChild sup i-                             cm <- monitor (_fromEndpoint c)-                             Sup.stopChild sup i >>= lift . assertBool "child not found"-                             (ProcessDown _ r _) <- receiveSelectedMessage (selectProcessDown cm)-                             case r of-                               SomeExitReason (ExitUnhandledError _) -> return ()-                               _ -> lift (assertFailure ("bad exit reason: " ++ show r))-                         , runTestCase "When a stopChild is called with an unallocated ID, False is returned" $ do-                             (sup, _) <- startTestSupAndChild-                             Sup.stopChild sup (i + 1) >>= lift . assertBool "child not found" . not-                         , runTestCase "When a child is stopped, lookup won't find it" $ do-                             (sup, _) <- startTestSupAndChild-                             Sup.stopChild sup i >>= lift . assertBool "child not found"-                             x <- Sup.lookupChild sup i-                             lift (assertEqual "lookup should not find a child" Nothing x)-                         , runTestCase "When a child is stopped, the child id can be reused" $ do-                             (sup, _) <- startTestSupAndChild-                             Sup.stopChild sup i >>= lift . assertBool "child not found"-                             Sup.spawnChild sup i >>= lift . assertBool "id could not be reused" . isRight-                         ]-                 , let startTestSupAndChild = do-                         sup <- startTestSup-                         c <- spawnTestChild sup i-                         cm <- monitor (_fromEndpoint c)-                         return (sup, c, cm)-                    in testGroup-                         "Child exit handling"-                         [ runTestCase "When a child exits normally, lookupChild will not find it" $ do-                             (sup, c, cm) <- startTestSupAndChild-                             cast c (TestInterruptWith NormalExitRequested)-                             (ProcessDown _ r _) <- receiveSelectedMessage (selectProcessDown cm)-                             lift (assertEqual "bad exit reason" (SomeExitReason ExitNormally) r)-                             x <- Sup.lookupChild sup i-                             lift (assertEqual "lookup should not find a child" Nothing x)-                         , runTestCase "When a child exits with an error, lookupChild will not find it" $ do-                             (sup, c, cm) <- startTestSupAndChild-                             cast c (TestInterruptWith (ErrorInterrupt "test error reason"))-                             (ProcessDown _ _ _) <- receiveSelectedMessage (selectProcessDown cm)-                             x <- Sup.lookupChild sup i-                             lift (assertEqual "lookup should not find a child" Nothing x)-                         , runTestCase "When a child is interrupted from another process and dies, lookupChild will not find it" $ do-                             (sup, c, cm) <- startTestSupAndChild-                             sendInterrupt (_fromEndpoint c) NormalExitRequested-                             (ProcessDown _ _ _) <- receiveSelectedMessage (selectProcessDown cm)-                             x <- Sup.lookupChild sup i-                             lift (assertEqual "lookup should not find a child" Nothing x)-                         , runTestCase "When a child is shutdown from another process and dies, lookupChild will not find it" $ do-                             (sup, c, cm) <- startTestSupAndChild-                             sendShutdown (_fromEndpoint c) ExitProcessCancelled-                             (ProcessDown _ _ _) <- receiveSelectedMessage (selectProcessDown cm)-                             x <- Sup.lookupChild sup i-                             lift (assertEqual "lookup should not find a child" Nothing x)-                         ]-                 ]-         ])--data TestProtocol-  deriving (Typeable)--type instance ToPretty TestProtocol = PutStr "test"--instance HasPdu TestProtocol where-  data instance Pdu TestProtocol x where-    TestGetStringLength :: String -> Pdu TestProtocol ('Synchronous Int)-    TestInterruptWith :: Interrupt 'Recoverable -> Pdu TestProtocol 'Asynchronous-      deriving Typeable--instance NFData (Pdu TestProtocol x) where-  rnf (TestGetStringLength x) = rnf x-  rnf (TestInterruptWith x) = rnf x--instance Show (Pdu TestProtocol r) where-  show (TestGetStringLength s) = "TestGetStringLength " ++ show s-  show (TestInterruptWith s) = "TestInterruptWith " ++ show s--data TestProtocolServerMode-  = IgnoreNormalExitRequest-  | ExitWhenRequested-  deriving Eq--instance Stateful.Server TestProtocol Effects where-  update _me (TestServerArgs testMode tId) evt =-    case evt of-      OnCast (TestInterruptWith i) -> do-        logInfo (pack (show tId) <> ": stopping with: " <> pack (show i))-        interrupt i-      OnCall rt (TestGetStringLength str) -> do-        logInfo (pack (show tId) <> ": calculating length of: " <> pack str)-        sendReply rt (length str)-      OnInterrupt x -> do-        logNotice (pack (show tId) <> ": " <> pack (show x))-        if testMode == IgnoreNormalExitRequest-          then-            logNotice $ pack (show tId) <> ": ignoring normal exit request"-          else do-            logNotice $ pack (show tId) <> ": exitting normally"-            exitBecause (interruptToExit x)-      _ ->-        logDebug (pack (show tId) <> ": got some info: " <> pack (show evt))-  data instance StartArgument TestProtocol Effects = TestServerArgs TestProtocolServerMode Int--type instance ChildId (Stateful.Stateful TestProtocol) = Int-
+ test/WatchdogTests.hs view
@@ -0,0 +1,737 @@+{-# LANGUAGE UndecidableInstances #-}+module WatchdogTests(test_watchdogTests) where++import Common  hiding (runTestCase)+import qualified Common+import qualified Control.Eff.Concurrent.Protocol.StatefulServer as Stateful+import Control.Eff.Concurrent.Protocol.StatefulServer (Stateful)+import qualified Control.Eff.Concurrent.Protocol.Broker as Broker+import Control.Eff.Concurrent.Protocol.Broker (Broker)+import qualified Control.Eff.Concurrent.Protocol.Observer.Queue as OQ+import qualified Control.Eff.Concurrent.Protocol.Watchdog as Watchdog+import Control.Lens+import Data.Set (Set)+import qualified Data.Set as Set+import qualified Data.Map as Map+import Data.Maybe (isNothing, fromJust)+++runTestCase :: TestName -> Eff Effects () -> TestTree+runTestCase = Common.runTestCase+++test_watchdogTests :: HasCallStack => TestTree+test_watchdogTests =+  testGroup "watchdog"+    [ runTestCase "demonstrate Bookshelf" bookshelfDemo++    , testGroup "watchdog broker interaction"+      [++        runTestCase "test 1: when the broker exits, the watchdog does not care, it can be re-attached to a new broker" $ do+          wd <- Watchdog.startLink def+          unlinkProcess (wd ^. fromEndpoint)+          do+            broker <- Broker.startLink (Broker.statefulChild @BookShelf (TimeoutMicros 1_000_000) id)+            unlinkProcess (broker ^. fromEndpoint)+            logNotice "started broker"+            Watchdog.attachTemporary wd broker+            logNotice "attached watchdog"+            assertShutdown (broker ^. fromEndpoint) ExitNormally+            logNotice "stopped broker"++          do+            broker <- Broker.startLink (Broker.statefulChild @BookShelf (TimeoutMicros 1_000_000) id)+            unlinkProcess (broker ^. fromEndpoint)+            logNotice "started new broker"+            Watchdog.attachTemporary wd broker+            logNotice "attached broker"+            let expected = ExitUnhandledError "test-broker-kill"+            logNotice "crashing broker"+            assertShutdown (broker ^. fromEndpoint) expected+            logNotice "crashed broker"++          logNotice "shutting down watchdog"+          assertShutdown (wd^.fromEndpoint) ExitNormally+          logNotice "watchdog down"+++      , runTestCase "test 2: when the broker exits, and the watchdog is linked to it, it will exit" $ do+          wd <- Watchdog.startLink def+          unlinkProcess (wd ^. fromEndpoint)+          logNotice "started watchdog"+          do+            broker <- Broker.startLink (Broker.statefulChild @BookShelf (TimeoutMicros 1_000_000) id)+            unlinkProcess (broker ^. fromEndpoint)+            logNotice "started broker"+            Watchdog.attachPermanent wd broker+            logNotice "attached and linked broker"+            logNotice "crashing broker"+            let expected = ExitUnhandledError "test-broker-kill"+            sendShutdown (broker ^. fromEndpoint) expected+            logNotice "crashed broker"+            logNotice "waiting for watchdog to crash"+            awaitProcessDown (wd^.fromEndpoint)+              >>= lift+              . assertEqual "bad exit reason" (ExitOtherProcessNotRunning (broker^.fromEndpoint))+              . downReason+            logNotice "watchdog crashed"++      , runTestCase "test 3: when the same broker is attached twice, the second attachment is ignored" $ do+          wd <- Watchdog.startLink def+          unlinkProcess (wd ^. fromEndpoint)+          logNotice "started watchdog"+          do+            broker <- Broker.startLink (Broker.statefulChild @BookShelf (TimeoutMicros 1_000_000) id)+            unlinkProcess (broker ^. fromEndpoint)+            logNotice "started broker"+            Watchdog.attachTemporary wd broker+            logNotice "attached broker"+            Watchdog.attachTemporary wd broker+            logNotice "attached broker"+            logNotice "restart child test begin"+            restartChildTest broker+            logNotice "restart child test finished"++      , runTestCase "test 4: when the same broker is attached twice, the\+                     \ first linked then not linked, the watchdog is not linked anymore" $ do+          wd <- Watchdog.startLink def+          unlinkProcess (wd ^. fromEndpoint)+          logNotice "started watchdog"+          do+            broker <- Broker.startLink (Broker.statefulChild @BookShelf (TimeoutMicros 1_000_000) id)+            unlinkProcess (broker ^. fromEndpoint)+            logNotice "started broker"+            Watchdog.attachPermanent wd broker+            logNotice "attached and linked broker"+            Watchdog.attachTemporary wd broker+            logNotice "attached broker"+            logNotice "run restart child test"+            restartChildTest broker+            logNotice "restart child test finished"+            logNotice "crashing broker"+            let expected = ExitUnhandledError "test-broker-kill"+            assertShutdown (broker ^. fromEndpoint) expected+            logNotice "crashed broker"+          do+            broker <- Broker.startLink (Broker.statefulChild @BookShelf (TimeoutMicros 1_000_000) id)+            unlinkProcess (broker ^. fromEndpoint)+            logNotice "started new broker"+            Watchdog.attachTemporary wd broker+            logNotice "attached new broker"+            logNotice "run restart child test again to show everything is ok"+            restartChildTest broker+            logNotice "restart child test finished"+++      , runTestCase "test 5: when the same broker is attached twice,\+                    \ the first time not linked but then linked, the watchdog is linked \+                    \ to that broker" $ do+          wd <- Watchdog.startLink def+          unlinkProcess (wd ^. fromEndpoint)+          mwd <- monitor (wd ^. fromEndpoint)+          logNotice "started watchdog"+          do+            broker <- Broker.startLink (Broker.statefulChild @BookShelf (TimeoutMicros 1_000_000) id)+            unlinkProcess (broker ^. fromEndpoint)+            logNotice "started broker"+            Watchdog.attachTemporary wd broker+            logNotice "attached broker"+            Watchdog.attachPermanent wd broker+            logNotice "attached and linked broker"+            logNotice "run restart child test"+            restartChildTest broker+            logNotice "restart child test finished"+            logNotice "crashing broker"+            let expected = ExitUnhandledError "test-broker-kill"+            assertShutdown (broker ^. fromEndpoint) expected+            logNotice "crashed broker"+            logNotice "waiting for watchdog to crash"+            receiveSelectedMessage (selectProcessDown mwd)+              >>= lift+              . assertEqual "bad exit reason" (ExitOtherProcessNotRunning (broker^.fromEndpoint))+              . downReason+            logNotice "watchdog down"+++      , runTestCase "test 6: when multiple brokers are attached to a watchdog,\+                     \ and a linked broker exits, the watchdog should exit" $ do+          wd <- Watchdog.startLink def+          unlinkProcess (wd ^. fromEndpoint)+          wdMon <- monitor (wd ^. fromEndpoint)+          logNotice "started watchdog"+          do+            broker1 <- Broker.startLink (Broker.statefulChild @BookShelf (TimeoutMicros 1_000_000) id)+            unlinkProcess (broker1^.fromEndpoint)+            logNotice "started broker 1"+            Watchdog.attachPermanent wd broker1+            logNotice "attached + linked broker 1"++            broker2 <- Broker.startLink (Broker.statefulChild @BookShelf (TimeoutMicros 1_000_000) id)+            logNotice "started broker 2"+            Watchdog.attachTemporary wd broker2+            logNotice "attached broker 2"++            broker3 <- Broker.startLink (Broker.statefulChild @BookShelf (TimeoutMicros 1_000_000) id)+            logNotice "started broker 3"+            Watchdog.attachTemporary wd broker3+            logNotice "attached broker 3"+            unlinkProcess (broker3 ^. fromEndpoint)++            let expected = ExitUnhandledError "test-broker-kill 1"+            logNotice "crashing broker 1"+            assertShutdown (broker1 ^. fromEndpoint) expected+            logNotice "crashed broker 1"++            isProcessAlive (broker2 ^. fromEndpoint)+              >>= lift . assertBool "broker2 should be running"+            isProcessAlive (broker3 ^. fromEndpoint)+              >>= lift . assertBool "broker3 should be running"+            logNotice "other brokers are alive"++            logNotice "waiting for watchdog to crash"+            receiveSelectedMessage (selectProcessDown wdMon)+                >>= lift+                . assertEqual "bad exit reason" (ExitOtherProcessNotRunning (broker1^.fromEndpoint))+                . downReason+            logNotice "watchdog crashed"++      ]+    , testGroup "restarting children"+      [ runTestCase "test 7: when a child exits it is restarted" $ do+          broker <- Broker.startLink (Broker.statefulChild @BookShelf (TimeoutMicros 1_000_000) id)+          logNotice "started broker"+          wd <- Watchdog.startLink def+          logNotice "started watchdog"+          Watchdog.attachTemporary wd broker+          logNotice "attached broker"+          logNotice "running child tests"+          restartChildTest broker+          logNotice "finished child tests"++      , runTestCase "test 8: when the broker emits the shutting down\+                    \ event the watchdog does not restart children" $ do+          broker <- Broker.startLink (Broker.statefulChild @BookShelf (TimeoutMicros 1_000_000) id)+          unlinkProcess (broker ^. fromEndpoint)+          logNotice "started broker"+          wd <- Watchdog.startLink def+          unlinkProcess (wd ^. fromEndpoint)+          logNotice "started watchdog"+          Watchdog.attachTemporary wd broker+          OQ.observe @(Broker.ChildEvent (Stateful BookShelf)) (100 :: Int) broker $ do+            let c0 = BookShelfId 0+            do+              void $ Broker.spawnOrLookup broker c0+              awaitChildStartedEvent c0+              logNotice "bookshelf started"++              Broker.stopBroker broker+              awaitBrokershuttingDownEvent broker+              lift (threadDelay 10_000)+              logNotice "checking state"+              Watchdog.getCrashReports wd >>= lift . assertBool "no crash reports expected" . null++          assertShutdown (wd ^. fromEndpoint) ExitNormally+          logNotice "watchdog stopped"++      , runTestCase "test 9: a child is only restarted if it crashes" $ do+          broker <- Broker.startLink (Broker.statefulChild @BookShelf (TimeoutMicros 1_000_000) id)+          logNotice "started broker"+          wd <- Watchdog.startLink def+          logNotice "started watchdog"+          Watchdog.attachTemporary wd broker+          OQ.observe @(Broker.ChildEvent (Stateful BookShelf)) (100 :: Int) broker $ do+            let shelfId = BookShelfId 0+            do+              shelf <- Broker.spawnOrLookup broker shelfId+              awaitChildStartedEvent shelfId+              logNotice "bookshelf started"++              cast shelf StopShelf+              awaitChildDownEvent shelfId+              void $ awaitProcessDown (shelf ^. fromEndpoint)+              logNotice "shelf stopped"+              lift (threadDelay 10_000)+              Broker.lookupChild broker shelfId >>= lift . assertBool "child must not be restarted" . isNothing++          assertShutdown (wd ^. fromEndpoint) ExitNormally+          logNotice "watchdog stopped"++      , testGroup "child restart"+        $ let threeTimesASecond = 3 `Watchdog.crashesPerSeconds` 1+          in  [ runTestCase "test 10: if a child crashes 3 times in 300ms, waits 1.1 seconds\+                            \ and crashes again 3 times, and is restarted three times" $ do++                  broker <- Broker.startLink (Broker.statefulChild @BookShelf (TimeoutMicros 1_000_000) id)+                  logNotice "started broker"+                  wd <- Watchdog.startLink threeTimesASecond+                  logNotice "started watchdog"+                  Watchdog.attachTemporary wd broker++                  OQ.observe @(Broker.ChildEvent (Stateful BookShelf)) (100 :: Int) broker $ do+                    let crash3Times = do+                          logNotice "crashing 3 times"+                          replicateM_ 3 $ do+                            spawnAndCrashBookShelf broker shelfId+                            logNotice "waiting for last restart"+                            awaitChildStartedEvent shelfId+                            lift (threadDelay 100_000)+                        shelfId = BookShelfId 0++                    crash3Times+                    logNotice "sleeping"+                    lift (threadDelay 1_100_000)+                    logNotice "crashing again"+                    crash3Times++                  assertShutdown (wd ^. fromEndpoint) ExitNormally+                  logNotice "watchdog stopped"++              , runTestCase "test 11: if a child crashes 4 times within 1s it is not restarted\+                            \ and the watchdog exits with an error" $ do+                  broker <- Broker.startLink (Broker.statefulChild @BookShelf (TimeoutMicros 1_000_000) id)+                  logNotice "started broker"+                  wd <- Watchdog.startLink threeTimesASecond+                  logNotice "started watchdog"+                  unlinkProcess (wd ^. fromEndpoint)+                  Watchdog.attachTemporary wd broker++                  OQ.observe @(Broker.ChildEvent (Stateful BookShelf)) (100 :: Int) broker $ do+                    let shelfId = BookShelfId 0+                        crash3Times = do+                          logNotice "crashing 3 times"+                          replicateM_ 3 $ do+                            spawnAndCrashBookShelf broker shelfId+                            logNotice "waiting for restart"+                            awaitChildStartedEvent shelfId+                            cr <- Watchdog.getCrashReports @(Stateful BookShelf) wd+                            logNotice (pack (show cr))+                            lift (threadDelay 100_000)++                    crash3Times++                    logNotice "crashing 4. time"+                    spawnAndCrashBookShelf broker shelfId+                    lift (threadDelay 10_000)+                    Broker.lookupChild broker shelfId+                      >>= lift . assertBool "child must not be reststarted" . isNothing++                  assertShutdown (wd ^. fromEndpoint) ExitNormally+                  logNotice "watchdog stopped"++              , runTestCase "test 12: if a child of a linked broker crashes too often,\+                            \ the watchdog exits with an error and interrupts the broker" $ do++                  broker <- Broker.startLink (Broker.statefulChild @BookShelf (TimeoutMicros 1_000_000) id)+                  logNotice "started broker"+                  unlinkProcess (broker ^. fromEndpoint)+                  mBroker <- monitor (broker ^. fromEndpoint)+                  wd <- Watchdog.startLink threeTimesASecond+                  logNotice "started watchdog"+                  mwd <- monitor (wd ^. fromEndpoint)+                  unlinkProcess (wd ^. fromEndpoint)+                  Watchdog.attachPermanent wd broker++                  OQ.observe @(Broker.ChildEvent (Stateful BookShelf)) (100 :: Int) broker $ do+                    let shelfId = BookShelfId 0+                        crash3Times = do+                          logNotice "crashing 3 times"+                          replicateM_ 3 $ do+                            spawnAndCrashBookShelf broker shelfId+                            logNotice "waiting for restart"+                            awaitChildStartedEvent shelfId+                            lift (threadDelay 100_000)++                    crash3Times++                    logNotice "crashing 4. time"+                    spawnAndCrashBookShelf broker shelfId++                  logNotice "await broker down"+                  receiveSelectedMessage (selectProcessDown mBroker)+                    >>= lift+                        . assertEqual+                              "wrong exit reason"+                              (ExitUnhandledError "restart frequency exceeded")+                        . downReason+                  logNotice "broker down"++                  logNotice "await watchdog down"+                  receiveSelectedMessage (selectProcessDown mwd)+                    >>= lift+                        . assertEqual+                              "wrong exit reason"+                              (ExitUnhandledError "restart frequency exceeded")+                        . downReason+                  logNotice "watchdog down"++              , runTestCase "test 13: if the watchdog is killed, all watched children of all\+                            \ brokers are still alive" $ do+                  wd <- Watchdog.startLink (1 `Watchdog.crashesPerSeconds` 1)+                  unlinkProcess (wd ^. fromEndpoint)+                  logNotice "started watchdog"++                  brokerT <- Broker.startLink (Broker.statefulChild @BookShelf (TimeoutMicros 1_000_000) id)+                  unlinkProcess (brokerT^.fromEndpoint)+                  logNotice "started temporary broker"++                  brokerP <- Broker.startLink (Broker.statefulChild @BookShelf (TimeoutMicros 1_000_000) id)+                  unlinkProcess (brokerP^.fromEndpoint)+                  logNotice "started permanent broker"++                  Watchdog.attachTemporary wd brokerT+                  logNotice "attached temporary broker"++                  Watchdog.attachPermanent wd brokerP+                  logNotice "attached permanent broker"++                  let b0 = BookShelfId 0+                      b1 = BookShelfId 1+                      b2 = BookShelfId 2+                      b3 = BookShelfId 3++                  (e0, e1) <- OQ.observe @(Broker.ChildEvent (Stateful BookShelf)) (100 :: Int) brokerT $+                    (,) <$> spawnBookShelf brokerT b0 <*> spawnBookShelf brokerT b1+                  m0 <- monitor (e0^.fromEndpoint)+                  m1 <- monitor (e1^.fromEndpoint)+                  logNotice ("started bookshelfs of temporary broker: " <> pack (show (m0, m1)))++                  (e2, e3) <- OQ.observe @(Broker.ChildEvent (Stateful BookShelf)) (100 :: Int) brokerP $+                    (,) <$> spawnBookShelf brokerP b2 <*> spawnBookShelf brokerP b3+                  m2 <- monitor (e2^.fromEndpoint)+                  m3 <- monitor (e3^.fromEndpoint)+                  logNotice ("started bookshelfs of permanent broker: " <> pack (show (m2, m3)))++                  logNotice "crashing the watchdog"+                  assertShutdown (wd ^. fromEndpoint) (ExitUnhandledError "watchdog test crash")+                  logNotice "watchdog stopped"++                  call e0 (AddBook "Solaris")+                  logNotice "e0 still alive"+                  call e1 (AddBook "Solaris")+                  logNotice "e1 still alive"+                  call e2 (AddBook "Solaris")+                  logNotice "e2 still alive"+                  call e3 (AddBook "Solaris")+                  logNotice "e3 still alive"+                  assertShutdown (e0 ^. fromEndpoint) ExitNormally+                  assertShutdown (e1 ^. fromEndpoint) ExitNormally+                  assertShutdown (e2 ^. fromEndpoint) ExitNormally+                  assertShutdown (e3 ^. fromEndpoint) ExitNormally+                  assertShutdown (brokerT ^. fromEndpoint) ExitNormally+                  assertShutdown (brokerP ^. fromEndpoint) ExitNormally++              , runTestCase "test 14: if the watchdog receives OnChildDown\+                            \ for a known broker it behaves as if there\+                            \ was an OnChildSpawned for that child" $ do+                  wd <- Watchdog.startLink (1 `Watchdog.crashesPerSeconds` 1)+                  logNotice "started watchdog"++                  brokerT <- Broker.startLink (Broker.statefulChild @BookShelf (TimeoutMicros 1_000_000) id)+                  logNotice "started temporary broker"++                  OQ.observe @(Broker.ChildEvent (Stateful BookShelf)) (100 :: Int) brokerT $ do+                    let b0 = BookShelfId 0+                    e0 <- spawnBookShelf brokerT b0+                    logNotice ("started bookshelf of temporary broker: " <> pack (show e0))+                    Watchdog.attachTemporary wd brokerT+                    logNotice "attached temporary broker"+                    logNotice "crash the bookshelf"+                    assertShutdown (e0 ^. fromEndpoint) (ExitUnhandledError "bookshelf test crash")+                    awaitChildDownEvent b0+                    logNotice "bookshelf down"+                    awaitChildStartedEvent b0+                    logNotice "bookshelf restarted"+                    assertShutdown (wd ^. fromEndpoint) ExitNormally+                    assertShutdown (brokerT ^. fromEndpoint) ExitNormally++              , runTestCase "test 15: when a child exits normally,\+                            \ and a new child with the same ChildId\+                            \ crashes, the watchdog behaves as if\+                            \ the first child never existed" $ do+                  wd <- Watchdog.startLink (1 `Watchdog.crashesPerSeconds` 10)+                  logNotice "started watchdog"++                  brokerT <- Broker.startLink (Broker.statefulChild @BookShelf (TimeoutMicros 1_000_000) id)+                  logNotice "started temporary broker"++                  OQ.observe @(Broker.ChildEvent (Stateful BookShelf)) (100 :: Int) brokerT $ do+                    let b0 = BookShelfId 0++                    e0 <- spawnBookShelf brokerT b0+                    logNotice ("started bookshelf of temporary broker: " <> pack (show e0))++                    Watchdog.attachTemporary wd brokerT+                    logNotice "attached temporary broker"+                    logNotice "crash the bookshelf"+                    assertShutdown (e0 ^. fromEndpoint) (ExitUnhandledError "bookshelf test crash")+                    awaitChildDownEvent b0+                    logNotice "bookshelf down"+                    awaitChildStartedEvent b0+                    e1 <- fromJust <$> Broker.lookupChild brokerT b0+                    logNotice "bookshelf restarted"++                    logNotice "exit the bookshelf normally"+                    assertShutdown (e1 ^. fromEndpoint) ExitNormally+                    awaitChildDownEvent b0+                    logNotice "bookshelf stopped, starting a new one"+                    e2 <- spawnBookShelf brokerT b0+                    logNotice "bookshelf restarted"++                    assertShutdown (e2 ^. fromEndpoint) (ExitUnhandledError "bookshelf test crash")+                    awaitChildDownEvent b0+                    logNotice "bookshelf down"+                    awaitChildStartedEvent b0+                    logNotice "bookshelf restarted"++                    assertShutdown (wd ^. fromEndpoint) ExitNormally+                    assertShutdown (brokerT ^. fromEndpoint) ExitNormally++              , testGroup "test 16: the amount of state is kept to a minimum" $+                let+                  setup k = do+                    wd <- Watchdog.startLink (4 `Watchdog.crashesPerSeconds` 30)+                    logNotice "started watchdog"+                    brokerT <- Broker.startLink (Broker.statefulChild @BookShelf (TimeoutMicros 1_000_000) id)+                    logNotice "started temporary broker"+                    Watchdog.attachTemporary wd brokerT+                    logNotice "attached temporary broker"+                    brokerP <- Broker.startLink (Broker.statefulChild @BookShelf (TimeoutMicros 1_000_000) id)+                    logNotice "started permanent broker"+                    Watchdog.attachTemporary wd brokerP+                    logNotice "attached permanent broker"+                    do+                      p <- self+                      t <- spawn "test-proc" $ do+                              void $ k wd brokerT brokerP+                              sendMessage p ()+                      res <- receiveSelectedWithMonitorAfter t selectMessage (TimeoutMicros 10_000_000)+                      logNotice ("test-proc finish message: " <> pack (show res))+                      case res of+                        Left x ->+                          do logError  ("test-proc crashed: " <> pack (show x))+                             lift (assertFailure ("test-proc crashed: " <> show x))+                        Right () ->+                          logNotice "test-proc succeeded"++                  setupThenShutdown k =+                    setup $ \wd brokerT brokerP -> do+                      void $ k wd brokerT brokerP+                      assertShutdown (wd ^. fromEndpoint) ExitNormally+                      assertShutdown (brokerT ^. fromEndpoint) ExitNormally+                      assertShutdown (brokerP ^. fromEndpoint) ExitNormally+                in+                 [+                  runTestCase "test 17: child events of unknown broker don't add state" $+                    setupThenShutdown $ \wd _brokerT _brokerP -> do+                      someBroker <- asEndpoint @(Broker (Stateful BookShelf)) <$> spawn "someBroker" (pure ())+                      someChild <- asEndpoint @BookShelf <$> spawn "someChild" (pure ())+                      let someChildId = BookShelfId 2321+                      cast wd (Observed (Broker.OnChildDown someBroker someChildId someChild (ExitUnhandledError "test error")))+                      lift (threadDelay 1_000)+                      cr <- Watchdog.getCrashReports @(Stateful BookShelf) wd+                      logNotice (pack (show cr))+                      lift (assertBool "no crash reports expected" (Map.null cr))++                  , runTestCase "test 18: when a broker exits, the children of that broker are forgotten and ignored" $ do+                      setup $ \wd brokerT brokerP -> do+                        let+                          someChildId1 = BookShelfId 2321+                          someChildId2 = BookShelfId 2322+                          someChildId3 = BookShelfId 2323++                        (someChild1, someChild2)+                          <- OQ.observe @(Broker.ChildEvent (Stateful BookShelf))+                              (100 :: Int)+                              brokerT+                                ((,) <$> spawnBookShelf brokerT someChildId1 <*> spawnBookShelf brokerT someChildId2)++                        someChild3+                          <- OQ.observe @(Broker.ChildEvent (Stateful BookShelf))+                              (100 :: Int) brokerP (spawnBookShelf brokerP someChildId3)+                        do+                          cr <- Watchdog.getCrashReports @(Stateful BookShelf) wd+                          logNotice ("crash-reports: " <> pack (show cr))+                          lift (assertBool "no crash reports expected" (Map.null cr))++                        OQ.observe @(Broker.ChildEvent (Stateful BookShelf)) (100 :: Int) brokerT $ do+                            crashBookShelf someChild1 someChildId1+                            awaitChildStartedEvent someChildId1+                            crashBookShelf someChild2 someChildId2+                            awaitChildStartedEvent someChildId2++                        OQ.observe @(Broker.ChildEvent (Stateful BookShelf)) (100 :: Int) brokerP $ do+                            crashBookShelf someChild3 someChildId3+                            awaitChildStartedEvent someChildId3++                        do+                          cr <- Watchdog.getCrashReports @(Stateful BookShelf) wd+                          logNotice ("crash-reports: " <> pack (show cr))+                          lift (assertEqual "wrong number of crash reports" 3 (Map.size cr))++                        assertShutdown (brokerT ^. fromEndpoint) ExitNormally+                        do+                          cr <- Watchdog.getCrashReports @(Stateful BookShelf) wd+                          logNotice ("crash-reports: " <> pack (show cr))+                          lift (assertEqual "wrong number of crash reports" 1 (Map.size cr))++                        assertShutdown (wd ^. fromEndpoint) ExitNormally+                        assertShutdown (brokerP ^. fromEndpoint) ExitNormally+                 ]+              ]+      ]+    ]+++bookshelfDemo :: HasCallStack => Eff Effects ()+bookshelfDemo = do+  logNotice "Bookshelf Demo Begin"+  broker <- Broker.startLink (Broker.statefulChild @BookShelf (TimeoutMicros 1_000_000) id)+  shelf1 <- Broker.spawnOrLookup broker (BookShelfId 1)+  call shelf1 (AddBook "Solaris")+  call shelf1 GetBookList >>= logDebug . pack . show+  cast shelf1 (TakeBook "Solaris")+  call shelf1 GetBookList >>= logDebug . pack . show+  logNotice "Bookshelf Demo End"++restartChildTest :: HasCallStack => Endpoint (Broker (Stateful BookShelf)) -> Eff Effects ()+restartChildTest broker =+  OQ.observe @(Broker.ChildEvent (Stateful BookShelf)) (100 :: Int) broker $ do+    let c0 = BookShelfId 123+    spawnAndCrashBookShelf broker c0+    c01m <- Broker.lookupChild broker c0+    case c01m of+      Nothing ->+        lift (assertFailure "failed to lookup child, seems it wasn't restarted!")+      Just c01 -> do+        call c01 (AddBook "Solaris")+        logNotice "part 3 passed"++spawnBookShelf+  :: HasCallStack+  => Endpoint (Broker (Stateful BookShelf))+  -> Broker.ChildId (Stateful BookShelf)+  -> Eff (OQ.Reader (Broker.ChildEvent (Stateful BookShelf)) : Effects) (Endpoint BookShelf)+spawnBookShelf broker c0 = do+   logNotice "spawn or lookup book shelf"+   res <- Broker.spawnChild broker c0+   c00 <- case res of+     Left (Broker.AlreadyStarted _ ep) -> pure ep+     Right ep -> awaitChildStartedEvent c0 >> pure ep+   logNotice ("got book shelf: " <> pack (show c00))+   pure c00++spawnAndCrashBookShelf+  :: HasCallStack+  => Endpoint (Broker (Stateful BookShelf))+  -> Broker.ChildId (Stateful BookShelf)+  -> Eff (OQ.Reader (Broker.ChildEvent (Stateful BookShelf)) : Effects) ()+spawnAndCrashBookShelf broker c0 = do+   c00 <- spawnBookShelf broker c0+   crashBookShelf c00 c0++crashBookShelf+  :: HasCallStack+  => Endpoint BookShelf+  -> Broker.ChildId (Stateful BookShelf)+  -> Eff (OQ.Reader (Broker.ChildEvent (Stateful BookShelf)) : Effects) ()+crashBookShelf c00 c0 = do+   logNotice "adding Solaris"+   call c00 (AddBook "Solaris")+   logNotice "added Solaris"+   logNotice "adding Solaris again to crash the book shelf"+   handleInterrupts (const (pure ())) (call c00 (AddBook "Solaris")) -- adding twice the same book causes a crash+   logNotice "added Solaris again waiting for crash"+   void $ awaitProcessDown (c00 ^. fromEndpoint)+   logNotice "shelf crashed"+   awaitChildDownEvent c0++awaitBrokershuttingDownEvent broker = do+   logNotice ("waiting for broker shutting down event of " <> pack (show broker))+   evt <- OQ.await @(Broker.ChildEvent (Stateful BookShelf))+   case evt of+    (Broker.OnBrokerShuttingDown broker') | broker == broker' ->+      logNotice ("broker shutting down: " <> pack (show broker))+    otherEvent ->+      lift (assertFailure ("wrong event received: " <> show otherEvent))++awaitChildDownEvent c0 = do+   logNotice ("waiting for down event of " <> pack (show c0))+   evt <- OQ.await @(Broker.ChildEvent (Stateful BookShelf))+   case evt of+    (Broker.OnChildDown _ c' _ _) | c0 == c' ->+      logNotice ("child down: " <> pack (show c0))+    otherEvent ->+      lift (assertFailure ("wrong broker down event received: " <> show otherEvent))++awaitChildStartedEvent c0 = do+   logNotice ("waiting for start event of " <> pack (show c0))+   evt <- OQ.await @(Broker.ChildEvent (Stateful BookShelf))+   case evt of+    (Broker.OnChildSpawned _ c' _) | c0 == c' ->+      logNotice ("child started: " <> pack (show c0))+    otherEvent ->+      lift (assertFailure ("wrong broker start event received: " <> show otherEvent))+++data BookShelf deriving Typeable++type Book = String++instance HasPdu BookShelf where+  data Pdu BookShelf r where+    GetBookList :: Pdu BookShelf ('Synchronous [Book])+    AddBook :: Book -> Pdu BookShelf ('Synchronous ())+    TakeBook :: Book -> Pdu BookShelf 'Asynchronous+    StopShelf :: Pdu BookShelf 'Asynchronous+      deriving Typeable++instance Stateful.Server BookShelf Effects where+  newtype StartArgument BookShelf Effects = BookShelfId Int+    deriving (Show, Eq, Ord, Num, NFData, Typeable)++  newtype instance Model BookShelf = BookShelfModel (Set String) deriving (Eq, Show, Default)++  update _ shelfId event = do+    logDebug (pack (show shelfId ++ " " ++ show event))+    case event of++      Stateful.OnCast StopShelf -> do+        logNotice "stop shelf"+        exitNormally++      Stateful.OnCast (TakeBook book) -> do+        booksBefore <- Stateful.getModel @BookShelf+        booksAfter <- Stateful.modifyAndGetModel (over bookShelfModel (Set.delete book))+        when (booksBefore == booksAfter) (exitWithError "book not taken")++      Stateful.OnCall rt callEvent ->+        case callEvent of+          GetBookList -> do+            books <- Stateful.useModel bookShelfModel+            sendReply rt (toList books)++          AddBook book -> do+            booksBefore <- Stateful.getModel @BookShelf+            booksAfter <- Stateful.modifyAndGetModel (over bookShelfModel (Set.insert book))+            when (booksBefore == booksAfter) (exitWithError "book not added")+            sendReply rt ()++      other -> logWarning (pack (show other))++bookShelfModel :: Iso' (Stateful.Model BookShelf) (Set String)+bookShelfModel = iso (\(BookShelfModel s) -> s) BookShelfModel++type instance Broker.ChildId BookShelf = Stateful.StartArgument BookShelf Effects++instance NFData (Pdu BookShelf r) where+  rnf GetBookList = ()+  rnf StopShelf = ()+  rnf (AddBook b) = rnf b+  rnf (TakeBook b) = rnf b++instance Show (Pdu BookShelf r) where+  show GetBookList = "get-book-list"+  show StopShelf = "stop-shelf"+  show (AddBook b) = "add-book: " ++ b+  show (TakeBook b) = "take-book: " ++ b