packages feed

extensible-effects-concurrent 0.3.0.2 → 0.4.0.0

raw patch · 27 files changed

+1059/−606 lines, 27 filesdep +deepseqdep ~QuickCheckdep ~extensible-effectsbinary-addedPVP ok

version bump matches the API change (PVP)

Dependencies added: deepseq

Dependency ranges changed: QuickCheck, extensible-effects

API changes (from Hackage documentation)

- Control.Eff.Concurrent.Process.SingleThreadedScheduler: schedule :: forall r finalResult. Eff r () -> Eff (Process r : r) finalResult -> Eff r (Either String finalResult)
- Control.Eff.Concurrent.Process.SingleThreadedScheduler: scheduleWithLogging :: Eff (ConsProcess '[Logs m]) a -> (Either String a, Seq m)
+ Control.Eff.Concurrent: Asynchronous :: Synchronicity
+ Control.Eff.Concurrent: ProcessExitError :: String -> SchedulerError
+ Control.Eff.Concurrent: ProcessId :: Int -> ProcessId
+ Control.Eff.Concurrent: ProcessNotFound :: ProcessId -> SchedulerError
+ Control.Eff.Concurrent: ProcessRaisedError :: String -> SchedulerError
+ Control.Eff.Concurrent: ProcessShuttingDown :: SchedulerError
+ Control.Eff.Concurrent: SchedulerShuttingDown :: SchedulerError
+ Control.Eff.Concurrent: Server :: ProcessId -> Server api
+ Control.Eff.Concurrent: Synchronous :: Type -> Synchronicity
+ Control.Eff.Concurrent: [ApiHandler] :: {_handleCast :: HasCallStack => Api p 'Asynchronous -> Eff r () A cast will not return a result directly. This is used for async methods., _handleCall :: forall x. HasCallStack => Api p ( 'Synchronous x) -> (x -> Eff r ()) -> Eff r () A call is a blocking operation, the caller is blocked until this handler calls the reply continuation., _handleTerminate :: HasCallStack => Maybe String -> Eff r () This callback is called with @Nothing@ if the process exits peacefully, or @Just "error message..."@ if the process exits with an error. This function is responsible to exit the process if necessary. The default behavior is defined in 'defaultTermination'.} -> ApiHandler p r
+ Control.Eff.Concurrent: [ExitWithError] :: String -> Process r b
+ Control.Eff.Concurrent: [OnError] :: String -> ResumeProcess v
+ Control.Eff.Concurrent: [RaiseError] :: String -> Process r b
+ Control.Eff.Concurrent: [ReceiveMessage] :: Process r (ResumeProcess Dynamic)
+ Control.Eff.Concurrent: [ResumeWith] :: a -> ResumeProcess a
+ Control.Eff.Concurrent: [RetryLastAction] :: ResumeProcess v
+ Control.Eff.Concurrent: [SP] :: SchedulerProxy q
+ Control.Eff.Concurrent: [SchedulerProxy] :: SchedulerProxy q
+ Control.Eff.Concurrent: [SelfPid] :: Process r (ResumeProcess ProcessId)
+ Control.Eff.Concurrent: [SendMessage] :: ProcessId -> Dynamic -> Process r (ResumeProcess Bool)
+ Control.Eff.Concurrent: [SendShutdown] :: ProcessId -> Process r (ResumeProcess Bool)
+ Control.Eff.Concurrent: [ShutdownRequested] :: ResumeProcess v
+ Control.Eff.Concurrent: [Shutdown] :: Process r a
+ Control.Eff.Concurrent: [SomeObserver] :: (Show (Server p), Typeable p, Observer p o) => Server p -> SomeObserver o
+ Control.Eff.Concurrent: [Spawn] :: Eff (Process r : r) () -> Process r (ResumeProcess ProcessId)
+ Control.Eff.Concurrent: [YieldProcess] :: Process r (ResumeProcess ())
+ Control.Eff.Concurrent: [_fromProcessId] :: ProcessId -> Int
+ Control.Eff.Concurrent: [_fromServer] :: Server api -> ProcessId
+ Control.Eff.Concurrent: addObserver :: (SetMember Process (Process q) r, Member (State (Observers o)) r, Observable o) => SomeObserver o -> Eff r ()
+ Control.Eff.Concurrent: asServer :: forall api. ProcessId -> Server api
+ Control.Eff.Concurrent: call :: forall result api r q. (SetMember Process (Process q) r, Typeable api, Typeable (Api api ( 'Synchronous result)), Typeable result, HasCallStack) => SchedulerProxy q -> Server api -> Api api ( 'Synchronous result) -> Eff r result
+ Control.Eff.Concurrent: callRegistered :: (Typeable reply, ServesApi o r q) => SchedulerProxy q -> Api o ( 'Synchronous reply) -> Eff r reply
+ Control.Eff.Concurrent: callRegisteredA :: forall r q o f reply. (Alternative f, Typeable f, Typeable reply, ServesApi o r q) => SchedulerProxy q -> Api o ( 'Synchronous (f reply)) -> Eff r (f reply)
+ Control.Eff.Concurrent: cast :: forall r q o. (HasCallStack, SetMember Process (Process q) r, Typeable o, Typeable (Api o 'Asynchronous)) => SchedulerProxy q -> Server o -> Api o 'Asynchronous -> Eff r ()
+ Control.Eff.Concurrent: castChecked :: forall r q o. (HasCallStack, SetMember Process (Process q) r, Typeable o, Typeable (Api o 'Asynchronous)) => SchedulerProxy q -> Server o -> Api o 'Asynchronous -> Eff r Bool
+ Control.Eff.Concurrent: castRegistered :: (Typeable o, ServesApi o r q) => SchedulerProxy q -> Api o 'Asynchronous -> Eff r ()
+ Control.Eff.Concurrent: catchRaisedError :: forall r q w. (HasCallStack, SetMember Process (Process q) r) => SchedulerProxy q -> (String -> Eff r w) -> Eff r w -> Eff r w
+ Control.Eff.Concurrent: catchUnhandled :: forall r a. (Member (Exc UnhandledRequest) r, HasCallStack) => Eff r a -> (Dynamic -> Eff r a) -> Eff r a
+ Control.Eff.Concurrent: class (Typeable o, Typeable (Observation o)) => Observable o where {
+ Control.Eff.Concurrent: class (Typeable p, Observable o) => Observer p o
+ Control.Eff.Concurrent: data ApiHandler p r
+ Control.Eff.Concurrent: data CallbackObserver o
+ Control.Eff.Concurrent: data Observers o
+ Control.Eff.Concurrent: data Process (r :: [Type -> Type]) b
+ Control.Eff.Concurrent: data ResumeProcess v
+ Control.Eff.Concurrent: data SchedulerError
+ Control.Eff.Concurrent: data SchedulerProxy :: [Type -> Type] -> Type
+ Control.Eff.Concurrent: data SomeObserver o
+ Control.Eff.Concurrent: data Synchronicity
+ Control.Eff.Concurrent: data UnhandledRequest
+ Control.Eff.Concurrent: data family Observation o;
+ Control.Eff.Concurrent: defaultMain :: Eff (ConsProcess SchedulerIO) () -> IO ()
+ Control.Eff.Concurrent: defaultMainWithLogChannel :: LogChannel String -> Eff (ConsProcess SchedulerIO) () -> IO ()
+ Control.Eff.Concurrent: defaultTermination :: forall q r. (HasCallStack, SetMember Process (Process q) r) => SchedulerProxy q -> Maybe String -> Eff r ()
+ Control.Eff.Concurrent: ensureAllHandled :: forall r q. (HasCallStack, SetMember Process (Process q) r) => SchedulerProxy q -> Eff (Exc UnhandledRequest : r) () -> Eff r ()
+ Control.Eff.Concurrent: executeAndCatch :: forall q r v. (SetMember Process (Process q) r, HasCallStack) => SchedulerProxy q -> Eff r (ResumeProcess v) -> Eff r (Either String v)
+ Control.Eff.Concurrent: executeAndResume :: forall r q v. (SetMember Process (Process q) r, HasCallStack) => Process q (ResumeProcess v) -> Eff r v
+ Control.Eff.Concurrent: exitNormally :: forall r q a. (HasCallStack, SetMember Process (Process q) r) => SchedulerProxy q -> Eff r a
+ Control.Eff.Concurrent: exitUnhandled :: forall r q. (SetMember Process (Process q) r, HasCallStack) => SchedulerProxy q -> Dynamic -> Eff r ()
+ Control.Eff.Concurrent: exitWithError :: forall r q a. (HasCallStack, SetMember Process (Process q) r) => SchedulerProxy q -> String -> Eff r a
+ Control.Eff.Concurrent: forgetObserver :: (SetMember Process (Process q) r, Observable o, Observer p o) => SchedulerProxy q -> Server p -> Server o -> Eff r ()
+ Control.Eff.Concurrent: forgetObserverMessage :: Observable o => SomeObserver o -> Api o 'Asynchronous
+ Control.Eff.Concurrent: forkIoScheduler :: SchedulerProxy SchedulerIO
+ Control.Eff.Concurrent: fromProcessId :: Iso' ProcessId Int
+ Control.Eff.Concurrent: fromServer :: forall api_aoTY api_ap3U. Iso (Server api_aoTY) (Server api_ap3U) ProcessId ProcessId
+ Control.Eff.Concurrent: ignoreProcessError :: (HasCallStack, SetMember Process (Process q) r) => SchedulerProxy q -> Eff r a -> Eff r (Either String a)
+ Control.Eff.Concurrent: manageObservers :: Eff (State (Observers o) : r) a -> Eff r a
+ Control.Eff.Concurrent: newtype ProcessId
+ Control.Eff.Concurrent: newtype Server api
+ Control.Eff.Concurrent: notifyObserver :: (SetMember Process (Process q) r, Observable o, Observer p o, HasCallStack) => SchedulerProxy q -> Server p -> Server o -> Observation o -> Eff r ()
+ Control.Eff.Concurrent: notifyObservers :: forall o r q. (Observable o, SetMember Process (Process q) r, Member (State (Observers o)) r) => SchedulerProxy q -> Observation o -> Eff r ()
+ Control.Eff.Concurrent: notifySomeObserver :: (SetMember Process (Process q) r, Observable o, HasCallStack) => SchedulerProxy q -> Server o -> Observation o -> SomeObserver o -> Eff r ()
+ Control.Eff.Concurrent: observationMessage :: Observer p o => Server o -> Observation o -> Api p 'Asynchronous
+ Control.Eff.Concurrent: proxyAsServer :: proxy api -> ProcessId -> Server api
+ Control.Eff.Concurrent: raiseError :: forall r q b. (HasCallStack, SetMember Process (Process q) r) => SchedulerProxy q -> String -> Eff r b
+ Control.Eff.Concurrent: receiveLoop :: forall r q. (SetMember Process (Process q) r, HasCallStack) => SchedulerProxy q -> (Either (Maybe String) Dynamic -> Eff r ()) -> Eff r ()
+ Control.Eff.Concurrent: receiveMessage :: forall r q. (HasCallStack, SetMember Process (Process q) r) => SchedulerProxy q -> Eff r Dynamic
+ Control.Eff.Concurrent: receiveMessageAs :: forall a r q. (HasCallStack, Typeable a, SetMember Process (Process q) r) => SchedulerProxy q -> Eff r a
+ Control.Eff.Concurrent: registerObserver :: (SetMember Process (Process q) r, Observable o, Observer p o, HasCallStack) => SchedulerProxy q -> Server p -> Server o -> Eff r ()
+ Control.Eff.Concurrent: registerObserverMessage :: Observable o => SomeObserver o -> Api o 'Asynchronous
+ Control.Eff.Concurrent: registerServer :: Server o -> Eff (Reader (Server o) : r) a -> Eff r a
+ Control.Eff.Concurrent: removeObserver :: (SetMember Process (Process q) r, Member (State (Observers o)) r, Observable o) => SomeObserver o -> Eff r ()
+ Control.Eff.Concurrent: requestFromDynamic :: forall r a. (HasCallStack, Typeable a, Member (Exc UnhandledRequest) r) => Dynamic -> Eff r a
+ Control.Eff.Concurrent: schedule :: Eff (ConsProcess SchedulerIO) () -> LogChannel String -> IO ()
+ Control.Eff.Concurrent: schedulePure :: Eff (ConsProcess '[]) a -> Either String a
+ Control.Eff.Concurrent: self :: (HasCallStack, SetMember Process (Process q) r) => SchedulerProxy q -> Eff r ProcessId
+ Control.Eff.Concurrent: sendMessage :: forall r q. (HasCallStack, SetMember Process (Process q) r) => SchedulerProxy q -> ProcessId -> Dynamic -> Eff r ()
+ Control.Eff.Concurrent: sendMessageAs :: forall o r q. (HasCallStack, SetMember Process (Process q) r, Typeable o) => SchedulerProxy q -> ProcessId -> o -> Eff r ()
+ Control.Eff.Concurrent: sendMessageChecked :: forall r q. (HasCallStack, SetMember Process (Process q) r) => SchedulerProxy q -> ProcessId -> Dynamic -> Eff r Bool
+ Control.Eff.Concurrent: sendShutdown :: forall r q. (HasCallStack, SetMember Process (Process q) r) => SchedulerProxy q -> ProcessId -> Eff r ()
+ Control.Eff.Concurrent: sendShutdownChecked :: forall r q. (HasCallStack, SetMember Process (Process q) r) => SchedulerProxy q -> ProcessId -> Eff r Bool
+ Control.Eff.Concurrent: serve :: forall r q p. (Typeable p, SetMember Process (Process q) r, HasCallStack) => SchedulerProxy q -> ApiHandler p r -> Eff r ()
+ Control.Eff.Concurrent: serve3 :: forall r q p1 p2 p3. (Typeable p1, Typeable p2, Typeable p3, SetMember Process (Process q) r, HasCallStack) => SchedulerProxy q -> ApiHandler p1 r -> ApiHandler p2 r -> ApiHandler p3 r -> Eff r ()
+ Control.Eff.Concurrent: serveBoth :: forall r q p1 p2. (Typeable p1, Typeable p2, SetMember Process (Process q) r, HasCallStack) => SchedulerProxy q -> ApiHandler p1 r -> ApiHandler p2 r -> Eff r ()
+ Control.Eff.Concurrent: spawn :: forall r q. (HasCallStack, SetMember Process (Process q) r) => Eff (Process q : q) () -> Eff r ProcessId
+ Control.Eff.Concurrent: spawnCallbackObserver :: forall o r q. (SetMember Process (Process q) r, Typeable o, Show (Observation o), Observable o, Member (Logs String) q) => SchedulerProxy q -> (Server o -> Observation o -> Eff (Process q : q) Bool) -> Eff r (Server (CallbackObserver o))
+ Control.Eff.Concurrent: spawnLoggingObserver :: forall o r q. (SetMember Process (Process q) r, Typeable o, Show (Observation o), Observable o, Member (Logs String) q) => SchedulerProxy q -> Eff r (Server (CallbackObserver o))
+ Control.Eff.Concurrent: spawn_ :: forall r q. (HasCallStack, SetMember Process (Process q) r) => Eff (Process q : q) () -> Eff r ()
+ Control.Eff.Concurrent: thisSchedulerProxy :: Eff (Process r : r) (SchedulerProxy r)
+ Control.Eff.Concurrent: tryApiHandler :: forall r q p. (Typeable p, SetMember Process (Process q) r, HasCallStack) => SchedulerProxy q -> ApiHandler p r -> Dynamic -> Eff (Exc UnhandledRequest : r) ()
+ Control.Eff.Concurrent: type ConsProcess r = Process r : r
+ Control.Eff.Concurrent: type HasSchedulerIO r = (HasCallStack, SetMember Lift (Lift IO) r, Member (Logs String) r, Member (Reader SchedulerVar) r)
+ Control.Eff.Concurrent: type SchedulerIO = '[Reader SchedulerVar, Logs String, Lift IO]
+ Control.Eff.Concurrent: type ServesApi o r q = (Typeable o, SetMember Process (Process q) r, Member (Reader (Server o)) r)
+ Control.Eff.Concurrent: unhandledCallError :: forall p x r q. (Show (Api p ( 'Synchronous x)), Typeable p, HasCallStack, SetMember Process (Process q) r) => SchedulerProxy q -> Api p ( 'Synchronous x) -> (x -> Eff r ()) -> Eff r ()
+ Control.Eff.Concurrent: unhandledCastError :: forall p r q. (Show (Api p 'Asynchronous), Typeable p, HasCallStack, SetMember Process (Process q) r) => SchedulerProxy q -> Api p 'Asynchronous -> Eff r ()
+ Control.Eff.Concurrent: yieldProcess :: forall r q. (HasCallStack, SetMember Process (Process q) r) => SchedulerProxy q -> Eff r ()
+ Control.Eff.Concurrent: }
+ Control.Eff.Concurrent.Process: executeAndResume :: forall r q v. (SetMember Process (Process q) r, HasCallStack) => Process q (ResumeProcess v) -> Eff r v
+ Control.Eff.Concurrent.Process: instance Control.DeepSeq.NFData a => Control.DeepSeq.NFData (Control.Eff.Concurrent.Process.ResumeProcess a)
+ Control.Eff.Concurrent.Process: instance Control.DeepSeq.NFData1 Control.Eff.Concurrent.Process.ResumeProcess
+ Control.Eff.Concurrent.Process: instance GHC.Generics.Generic (Control.Eff.Concurrent.Process.ResumeProcess v)
+ Control.Eff.Concurrent.Process: instance GHC.Generics.Generic1 Control.Eff.Concurrent.Process.ResumeProcess
+ Control.Eff.Concurrent.Process.SingleThreadedScheduler: scheduleIOWithLogging :: (NFData l, MonadIO m) => (l -> m ()) -> Eff (ConsProcess '[Logs l, Lift m]) a -> m (Either String a)
+ Control.Eff.Concurrent.Process.SingleThreadedScheduler: scheduleM :: Monad m => (forall b. Eff r b -> m b) -> m () -> Eff (ConsProcess r) a -> m (Either String a)
+ Control.Eff.Concurrent.Process.SingleThreadedScheduler: scheduleMonadIOEff :: MonadIO (Eff r) => Eff (ConsProcess r) a -> Eff r (Either String a)
+ Control.Eff.Log: data JoinLogChannelException m
+ Control.Eff.Log: data KillLogChannelException m
+ Control.Eff.Log: handleLogsWithLoggingTHandler :: forall m r message a. (Monad m, SetMember Lift (Lift m) r) => Eff (Logs message : r) a -> (forall b. (Handler m message -> m b) -> m b) -> Eff r a
+ Control.Eff.Loop: foreverCheap :: Monad m => m a -> m ()
+ Control.Eff.Loop: replicateCheapM_ :: Monad m => Int -> m a -> m ()
- Control.Eff.Concurrent.Api: fromServer :: forall api_ai3V api_aieo. Iso (Server api_ai3V) (Server api_aieo) ProcessId ProcessId
+ Control.Eff.Concurrent.Api: fromServer :: forall api_aoTY api_ap3U. Iso (Server api_aoTY) (Server api_ap3U) ProcessId ProcessId
- Control.Eff.Concurrent.Process.SingleThreadedScheduler: scheduleIO :: SetMember Lift (Lift IO) r => Eff (ConsProcess r) a -> Eff r (Either String a)
+ Control.Eff.Concurrent.Process.SingleThreadedScheduler: scheduleIO :: MonadIO m => (forall b. Eff r b -> Eff '[Lift m] b) -> Eff (ConsProcess r) a -> m (Either String a)
- Control.Eff.Log: captureLogs :: Eff (Logs message : r) a -> Eff r (a, Seq message)
+ Control.Eff.Log: captureLogs :: NFData message => Eff (Logs message : r) a -> Eff r (a, Seq message)
- Control.Eff.Log: handleLogsWith :: forall m r message a. (Monad m, SetMember Lift (Lift m) r) => Eff (Logs message : r) a -> (forall b. (Handler m message -> m b) -> m b) -> Eff r a
+ Control.Eff.Log: handleLogsWith :: forall m r message a. (NFData message, Monad m, SetMember Lift (Lift m) r) => (message -> m ()) -> Eff (Logs message : r) a -> Eff r a
- Control.Eff.Log: ignoreLogs :: Eff (Logs message : r) a -> Eff r a
+ Control.Eff.Log: ignoreLogs :: forall message r a. Eff (Logs message : r) a -> Eff r a

Files

ChangeLog.md view
@@ -1,5 +1,15 @@ # Changelog for extensible-effects-concurrent +## 0.4.0.0++- Switch to `extensible-effects` version `3.0.0.0`+- Improve single threaded scheduler to be more space efficient+- Add some strictness annotations+- Add `Control.Eff.Loop` with (hopefully) constant space `forever` and+  `replicateM_`+- Add `Control.Eff.Concurrent`, a module that conveniently re-exports most+  library functions.+ ## 0.3.0.2  - Improve single threaded scheduler such that the main process can return a value
+ docs/extensible-effects-concurrent-test-Loop-WITH-space-leaks.png view

binary file changed (absent → 83907 bytes)

+ docs/extensible-effects-concurrent-test-Loop-without-space-leaks.png view

binary file changed (absent → 87117 bytes)

extensible-effects-concurrent.cabal view
@@ -1,5 +1,5 @@ name:           extensible-effects-concurrent-version:        0.3.0.2+version:        0.4.0.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@@ -10,9 +10,15 @@ license:        BSD3 license-file:   LICENSE build-type:     Simple-cabal-version:  >= 1.10+cabal-version:  >= 1.19 +extra-doc-files:+    docs/extensible-effects-concurrent-test-Loop-without-space-leaks.png+    docs/extensible-effects-concurrent-test-Loop-WITH-space-leaks.png+ extra-source-files:+    stack.yaml+    stack.nightly-2018-05-29.yaml     ChangeLog.md     README.md @@ -25,12 +31,13 @@       src   build-depends:       base >=4.9 && <5,+      deepseq,       directory,       filepath,       time,       mtl,       containers,-      QuickCheck <2.11,+      QuickCheck <2.12,       lens,       logging-effect,       transformers,@@ -38,12 +45,14 @@       process,       monad-control,       random,-      extensible-effects >= 2.4 && < 2.5,+      extensible-effects >= 3,       stm >= 2.4.5,       tagged   exposed-modules:+                  Control.Eff.Loop,                   Control.Eff.ExceptionExtra,                   Control.Eff.Log,+                  Control.Eff.Concurrent,                   Control.Eff.Concurrent.Api,                   Control.Eff.Concurrent.Api.Client,                   Control.Eff.Concurrent.Api.Server,@@ -53,32 +62,35 @@                   Control.Eff.Concurrent.Process.SingleThreadedScheduler,                   Control.Eff.Concurrent.Api.Observer   other-modules:-                Control.Eff.InternalExtra,                 Control.Eff.Concurrent.Api.Internal,                 Paths_extensible_effects_concurrent,                 Control.Eff.Concurrent.Examples,                 Control.Eff.Concurrent.Examples2   default-extensions:+                     BangPatterns,                      ConstraintKinds,                      DeriveFoldable,                      DeriveFunctor,                      DeriveFunctor,                      DeriveGeneric,                      DeriveTraversable,-                     GADTs,                      FlexibleContexts,+                     FlexibleInstances,+                     GADTs,                      GeneralizedNewtypeDeriving,+                     MultiParamTypeClasses,                      RankNTypes,                      ScopedTypeVariables,                      StandaloneDeriving,                      TemplateHaskell,                      TupleSections,                      TypeApplications,+                     TypeFamilies,                      TypeInType,                      TypeOperators,                      ViewPatterns   default-language: Haskell2010-  ghc-options: -Wall+  ghc-options: -Wall -fno-full-laziness   test-suite extensible-effects-concurrent-test@@ -88,30 +100,34 @@   other-modules:                 Paths_extensible_effects_concurrent               , ForkIOScheduler+              , Interactive               , SingleThreadedScheduler+              , LoopTests               , ProcessBehaviourTestCases               , Debug               , Common-  ghc-options: -threaded -rtsopts -with-rtsopts=-N+  ghc-options: -Wall -threaded -fno-full-laziness -rtsopts -with-rtsopts=-N   build-depends:                 base >=4.9 && <5               , extensible-effects-concurrent-              , extensible-effects+              , extensible-effects >= 3               , tasty               , tasty-discover               , tasty-hunit               , containers-              , QuickCheck <2.11+              , deepseq+              , QuickCheck <2.12               , lens               , HUnit               , stm   default-language: Haskell2010-  default-extensions:   TypeApplications+  default-extensions:   BangPatterns+                      , DataKinds+                      , FlexibleContexts+                      , GADTs                       , RankNTypes                       , ScopedTypeVariables-                      , TypeOperators-                      , GADTs                       , TemplateHaskell-                      , DataKinds-                      , FlexibleContexts-                      , BangPatterns+                      , TypeApplications+                      , TypeFamilies+                      , TypeOperators
+ src/Control/Eff/Concurrent.hs view
@@ -0,0 +1,127 @@+-- | Erlang style processes with message passing concurrency based on+-- (more) @extensible-effects@.+module Control.Eff.Concurrent+  (+    -- * Concurrent Processes with Message Passing Concurrency+    module Control.Eff.Concurrent.Process+  ,+    -- * Data Types and Functions for APIs (aka Protocols)+    module Control.Eff.Concurrent.Api+  ,+    -- ** /Client/ Functions for Consuming APIs+    module Control.Eff.Concurrent.Api.Client+  ,+    -- ** /Server/ Functions for Providing APIs+    module Control.Eff.Concurrent.Api.Server+  ,+    -- ** /Observer/ Functions for Events and Event Listener+    module Control.Eff.Concurrent.Api.Observer+  ,+    -- * /Scheduler/ Process Effect Handler+    -- ** Concurrent Scheduler+    module Control.Eff.Concurrent.Process.ForkIOScheduler+  ,+    -- ** Single Threaded Scheduler+    module Control.Eff.Concurrent.Process.SingleThreadedScheduler+  ,+    -- * Utilities+    -- ** Logging Effect+    module Control.Eff.Log+  ,+    -- ** Preventing Space Leaks+    module Control.Eff.Loop+  )+where++import           Control.Eff.Concurrent.Process ( ProcessId(..)+                                                , fromProcessId+                                                , Process(..)+                                                , ConsProcess+                                                , ResumeProcess(..)+                                                , SchedulerProxy(..)+                                                , thisSchedulerProxy+                                                , executeAndCatch+                                                , executeAndResume+                                                , yieldProcess+                                                , sendMessage+                                                , sendMessageAs+                                                , sendMessageChecked+                                                , spawn+                                                , spawn_+                                                , receiveMessage+                                                , receiveMessageAs+                                                , receiveLoop+                                                , self+                                                , sendShutdown+                                                , sendShutdownChecked+                                                , exitWithError+                                                , exitNormally+                                                , raiseError+                                                , catchRaisedError+                                                , ignoreProcessError+                                                )+import           Control.Eff.Concurrent.Api+                                                ( Api+                                                , Synchronicity(..)+                                                , Server(..)+                                                , fromServer+                                                , proxyAsServer+                                                , asServer+                                                )+import           Control.Eff.Concurrent.Api.Client+                                                ( cast+                                                , castChecked+                                                , call+                                                , castRegistered+                                                , callRegistered+                                                , callRegisteredA+                                                , ServesApi+                                                , registerServer+                                                )+import           Control.Eff.Concurrent.Api.Server+                                                ( serve+                                                , ApiHandler(..)+                                                , unhandledCallError+                                                , unhandledCastError+                                                , defaultTermination+                                                , serveBoth+                                                , serve3+                                                , tryApiHandler+                                                , UnhandledRequest()+                                                , catchUnhandled+                                                , ensureAllHandled+                                                , requestFromDynamic+                                                , exitUnhandled+                                                )++import           Control.Eff.Concurrent.Api.Observer+                                                ( Observer(..)+                                                , Observable(..)+                                                , notifyObserver+                                                , registerObserver+                                                , forgetObserver+                                                , SomeObserver(..)+                                                , notifySomeObserver+                                                , Observers()+                                                , manageObservers+                                                , addObserver+                                                , removeObserver+                                                , notifyObservers+                                                , CallbackObserver+                                                , spawnCallbackObserver+                                                , spawnLoggingObserver+                                                )+import           Control.Eff.Concurrent.Process.ForkIOScheduler+                                                ( schedule+                                                , defaultMain+                                                , defaultMainWithLogChannel+                                                , SchedulerError(..)+                                                , SchedulerIO+                                                , forkIoScheduler+                                                , HasSchedulerIO+                                                )++import           Control.Eff.Concurrent.Process.SingleThreadedScheduler+                                                ( schedulePure )+import           Control.Eff.Log+import           Control.Eff.Loop
src/Control/Eff/Concurrent/Api.hs view
@@ -1,18 +1,3 @@-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE ConstraintKinds #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE KindSignatures #-}-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE DeriveFunctor #-}-{-# LANGUAGE StandaloneDeriving #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeOperators #-}-{-# LANGUAGE TypeApplications #-}-{-# LANGUAGE GADTs #-}- -- | This module contains a mechanism to specify what kind of messages (aka -- /requests/) a 'Server' ('Process') can handle, and if the caller blocks and -- waits for an answer, which the server process provides.@@ -41,7 +26,9 @@  import           Data.Kind import           Control.Lens-import           Data.Typeable (Typeable, typeRep)+import           Data.Typeable                  ( Typeable+                                                , typeRep+                                                ) import           Control.Eff.Concurrent.Process  -- | This data family defines an API, a communication interface description
src/Control/Eff/Concurrent/Api/Client.hs view
@@ -1,26 +1,12 @@-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE ConstraintKinds #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE KindSignatures #-}-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE DeriveFunctor #-}-{-# LANGUAGE StandaloneDeriving #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeOperators #-}-{-# LANGUAGE TypeApplications #-}-{-# LANGUAGE GADTs #-}- -- | Functions for 'Api' clients. -- -- This modules is required to write clients that consume an 'Api'.- module Control.Eff.Concurrent.Api.Client-  ( cast+  ( -- * Calling APIs directly+    cast   , castChecked   , call+  -- * Server Process Registration   , castRegistered   , callRegistered   , callRegisteredA@@ -31,7 +17,7 @@  import           Control.Applicative import           Control.Eff-import           Control.Eff.Reader.Lazy+import           Control.Eff.Reader.Strict import           Control.Eff.Concurrent.Api import           Control.Eff.Concurrent.Api.Internal import           Control.Eff.Concurrent.Process@@ -107,7 +93,6 @@       px       ("Could not send request message " ++ show (typeRep requestMessage)) --- * Registered Servers  -- | Instead of passing around a 'Server' value and passing to functions like -- 'cast' or 'call', a 'Server' can provided by a 'Reader' effect, if there is@@ -123,7 +108,7 @@ -- | Run a reader effect that contains __the one__ server handling a specific -- 'Api' instance. registerServer :: Server o -> Eff (Reader (Server o) ': r) a -> Eff r a-registerServer = flip runReader+registerServer = runReader  -- | Like 'call' but take the 'Server' from the reader provided by -- 'registerServer'.
src/Control/Eff/Concurrent/Api/Internal.hs view
@@ -1,18 +1,3 @@-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE ConstraintKinds #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE KindSignatures #-}-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE DeriveFunctor #-}-{-# LANGUAGE StandaloneDeriving #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeOperators #-}-{-# LANGUAGE TypeApplications #-}-{-# LANGUAGE GADTs #-}- -- | Internal request and response type for casts and calls  module Control.Eff.Concurrent.Api.Internal
src/Control/Eff/Concurrent/Api/Observer.hs view
@@ -3,29 +3,14 @@ -- This module supports the implementation of observers and observables. One use -- case is event propagation. The tools in this module are tailored towards -- 'Api' servers/clients.-{-# LANGUAGE IncoherentInstances #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE ConstraintKinds #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE KindSignatures #-}-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE DeriveFunctor #-}-{-# LANGUAGE StandaloneDeriving #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeOperators #-}-{-# LANGUAGE TypeApplications #-}-{-# LANGUAGE GADTs #-} module Control.Eff.Concurrent.Api.Observer-  ( Observer(..)+  ( -- * Observation API+    Observer(..)   , Observable(..)   , notifyObserver   , registerObserver   , forgetObserver+  -- ** Generalized observation   , SomeObserver(..)   , notifySomeObserver   , Observers()@@ -33,6 +18,7 @@   , addObserver   , removeObserver   , notifyObservers+  -- * Callback 'Observer'   , CallbackObserver   , spawnCallbackObserver   , spawnLoggingObserver@@ -48,12 +34,10 @@ import Control.Eff.Concurrent.Api.Client import Control.Eff.Concurrent.Api.Server import Control.Eff.Log-import Control.Eff.State.Lazy+import Control.Eff.State.Strict import Control.Lens import Control.Monad --- * Observation API- -- | An 'Api' index that support observation of the -- another 'Api' that is 'Observable'. class (Typeable p, Observable o) => Observer p o where@@ -100,8 +84,6 @@ forgetObserver px observer observed =   cast px observed (forgetObserverMessage (SomeObserver observer)) --- ** Generalized observation- -- | An existential wrapper around a 'Server' of an 'Observer'. -- Needed to support different types of observers to observe the -- same 'Observable' in a general fashion.@@ -143,7 +125,7 @@ -- | Keep track of registered 'Observer's Observers can be added and removed, -- and an 'Observation' can be sent to all registerd observers at once. manageObservers :: Eff (State (Observers o) ': r) a -> Eff r a-manageObservers = flip evalState (Observers Set.empty)+manageObservers = evalState (Observers Set.empty)  -- | Add an 'Observer' to the 'Observers' managed by 'manageObservers'. addObserver@@ -173,8 +155,6 @@   me <- asServer @o <$> self px   os <- view observers <$> get   mapM_ (notifySomeObserver px me observation) os---- * Callback 'Observer'  -- | An 'Observer' that schedules the observations to an effectful callback. data CallbackObserver o
src/Control/Eff/Concurrent/Api/Server.hs view
@@ -1,20 +1,4 @@-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE ConstraintKinds #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE KindSignatures #-}-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE DeriveFunctor #-}-{-# LANGUAGE StandaloneDeriving #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeOperators #-}-{-# LANGUAGE TypeApplications #-}-{-# LANGUAGE GADTs #-}- -- | Functions to implement 'Api' __servers__.- module Control.Eff.Concurrent.Api.Server   (   -- * Api Server@@ -37,8 +21,8 @@ where  import           Control.Eff-import           Control.Eff.InternalExtra import qualified Control.Eff.Exception         as Exc+import           Control.Eff.Extend import           Control.Eff.Concurrent.Api import           Control.Eff.Concurrent.Api.Internal import           Control.Eff.Concurrent.Process@@ -90,7 +74,7 @@      } -> ApiHandler p r  -- | Apply either the '_handleCall', '_handleCast' or the '_handleTerminate'--- callback to an incoming 'Request'. Note, it is unlikely that this function must be used.+-- callback to an incoming request. Note, it is unlikely that this function must be used. applyApiHandler   :: forall r q p    . (Typeable p, SetMember Process (Process q) r, HasCallStack)
src/Control/Eff/Concurrent/Examples.hs view
@@ -1,19 +1,4 @@-{-# LANGUAGE IncoherentInstances #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE ConstraintKinds #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE KindSignatures #-}-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE DeriveFunctor #-}-{-# LANGUAGE StandaloneDeriving #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeOperators #-}-{-# LANGUAGE TypeApplications #-}-{-# LANGUAGE GADTs #-}+-- | A complete example for the library module Control.Eff.Concurrent.Examples where  import GHC.Stack@@ -21,12 +6,7 @@ import Control.Eff.Lift import Control.Monad import Data.Dynamic-import Control.Eff.Concurrent.Api-import Control.Eff.Concurrent.Api.Client-import Control.Eff.Concurrent.Api.Server-import Control.Eff.Concurrent.Process-import Control.Eff.Concurrent.Process.ForkIOScheduler as Scheduler-import Control.Eff.Log+import Control.Eff.Concurrent import qualified Control.Exception as Exc  data TestApi
src/Control/Eff/Concurrent/Examples2.hs view
@@ -1,32 +1,12 @@-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE ConstraintKinds #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE KindSignatures #-}-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE DeriveFunctor #-}-{-# LANGUAGE StandaloneDeriving #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeOperators #-}-{-# LANGUAGE TypeApplications #-}-{-# LANGUAGE GADTs #-}+-- | Another complete example for the library module Control.Eff.Concurrent.Examples2 where  import Data.Dynamic import Control.Eff import qualified Control.Eff.Concurrent.Process.ForkIOScheduler as ForkIO import qualified Control.Eff.Concurrent.Process.SingleThreadedScheduler as Pure-import Control.Eff.Concurrent.Api-import Control.Eff.Concurrent.Api.Server-import Control.Eff.Concurrent.Api.Client-import Control.Eff.Concurrent.Process-import Control.Eff.Concurrent.Api.Observer-import Control.Eff.Log-import Control.Eff.State.Lazy+import Control.Eff.Concurrent+import Control.Eff.State.Strict import Control.Monad  main :: IO ()@@ -92,7 +72,6 @@      c <- get      logMsg ("Cnt is " ++ show c)      reply c-     return ()  -- * Second API @@ -134,11 +113,11 @@                -> Eff r () serverLoop px = do   evalState @Integer+    0     (manageObservers @Counter      (serveBoth px        (counterHandler px)        (pocketCalcHandler px)))-    0   -- ** Counter client
src/Control/Eff/Concurrent/Process.hs view
@@ -10,32 +10,18 @@ -- --  * And a /pure/(rer) coroutine based scheduler in: --    "Control.Eff.Concurrent.Process.SingleThreadedScheduler"-{-# LANGUAGE DeriveTraversable #-}-{-# LANGUAGE DeriveFoldable #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE ConstraintKinds #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE KindSignatures #-}-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE DeriveFunctor #-}-{-# LANGUAGE StandaloneDeriving #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeOperators #-}-{-# LANGUAGE TypeApplications #-}-{-# LANGUAGE GADTs #-} module Control.Eff.Concurrent.Process-  ( ProcessId(..)+  ( -- * ProcessId Type+    ProcessId(..)   , fromProcessId+   -- * Process Effects   , Process(..)   , ConsProcess   , ResumeProcess(..)   , SchedulerProxy(..)   , thisSchedulerProxy   , executeAndCatch+  , executeAndResume   , yieldProcess   , sendMessage   , sendMessageAs@@ -56,19 +42,19 @@   ) where +import           GHC.Generics                   ( Generic+                                                , Generic1+                                                ) import           GHC.Stack+import           Control.DeepSeq import           Control.Eff+import           Control.Eff.Extend import           Control.Lens-import           Control.Monad                  ( forever-                                                , void-                                                )+import           Control.Monad                  ( void ) import           Data.Dynamic import           Data.Kind import           Text.Printf ---- * Process Effects- -- | The process effect is the basis for message passing concurrency. This -- effect describes an interface for concurrent, communicating isolated -- processes identified uniquely by a process-id.@@ -139,8 +125,13 @@   ResumeWith :: a -> ResumeProcess a   -- | This indicates that the action did not complete, and maybe retried   RetryLastAction :: ResumeProcess v-  deriving (Typeable, Foldable, Functor, Show, Eq, Ord, Traversable)+  deriving ( Typeable, Foldable, Functor, Show, Eq, Ord+           , Traversable, Generic, Generic1) +instance NFData a => NFData (ResumeProcess a)++instance NFData1 ResumeProcess+ -- | /Cons/ 'Process' onto a list of effects. type ConsProcess r = Process r ': r @@ -154,7 +145,7 @@ executeAndResume processAction = do   result <- send processAction   case result of-    ResumeWith value  -> return value+    ResumeWith !value -> return value     RetryLastAction   -> executeAndResume processAction     ShutdownRequested -> send (Shutdown @q)     OnError e         -> send (ExitWithError @q e)@@ -169,7 +160,7 @@ executeAndCatch px processAction = do   result <- processAction   case result of-    ResumeWith value  -> return (Right value)+    ResumeWith !value -> return (Right value)     RetryLastAction   -> executeAndCatch px processAction     ShutdownRequested -> send (Shutdown @q)     OnError e         -> return (Left e)@@ -299,13 +290,13 @@   => SchedulerProxy q   -> (Either (Maybe String) Dynamic -> Eff r ())   -> Eff r ()-receiveLoop _ handlers = forever $ do+receiveLoop px handlers = do   mReq <- send (ReceiveMessage @q)   case mReq of-    RetryLastAction    -> return ()-    ShutdownRequested  -> handlers (Left Nothing)-    OnError    reason  -> handlers (Left (Just reason))-    ResumeWith message -> handlers (Right message)+    RetryLastAction    -> receiveLoop px handlers+    ShutdownRequested  -> handlers (Left Nothing) >> receiveLoop px handlers+    OnError reason -> handlers (Left (Just reason)) >> receiveLoop px handlers+    ResumeWith message -> handlers (Right message) >> receiveLoop px handlers  -- | Returns the 'ProcessId' of the current process. self
src/Control/Eff/Concurrent/Process/ForkIOScheduler.hs view
@@ -15,21 +15,6 @@ -- -- 'spawn' uses 'forkFinally' and 'STM.TQueue's and tries to catch -- most exceptions.-{-# LANGUAGE ConstraintKinds #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE KindSignatures #-}-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE DeriveFunctor #-}-{-# LANGUAGE StandaloneDeriving #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeOperators #-}-{-# LANGUAGE TypeApplications #-}-{-# LANGUAGE GADTs #-} module Control.Eff.Concurrent.Process.ForkIOScheduler   ( schedule   , defaultMain@@ -50,8 +35,8 @@ import           Control.Concurrent            as Concurrent import           Control.Concurrent.STM        as STM import           Control.Eff+import           Control.Eff.Extend import           Control.Eff.Concurrent.Process--- import           Control.Eff.ExceptionExtra import           Control.Eff.Lift import           Control.Eff.Log import           Control.Eff.Reader.Strict     as Reader@@ -80,7 +65,7 @@ instance Show ProcessInfo where   show p =  "ProcessInfo: " ++ show (p ^. processId) --- | Contains all 'ProcessInfo' elements, as well as the state needed to+-- | Contains all process info'elements, as well as the state needed to -- implement inter process communication. It contains also a 'LogChannel' to -- which the logs of all processes are forwarded to. data Scheduler =@@ -93,7 +78,7 @@  makeLenses ''Scheduler --- | A newtype wrapper around an 'STM.TVar' holding a 'Scheduler' state.+-- | A newtype wrapper around an 'STM.TVar' holding the scheduler state. -- This is needed by 'spawn' and provided by 'runScheduler'. newtype SchedulerVar = SchedulerVar { fromSchedulerVar :: STM.TVar Scheduler }   deriving Typeable@@ -101,7 +86,7 @@ -- | A sum-type with errors that can occur when scheduleing messages. data SchedulerError =     ProcessNotFound ProcessId-    -- ^ No 'ProcessInfo' was found for a 'ProcessId' during internal+    -- ^ No process info was found for a 'ProcessId' during internal     -- processing. NOTE: This is **ONLY** caused by internal errors, probably by     -- an incorrect 'MessagePassing' handler in this module. **Sending a message     -- to a process ALWAYS succeeds!** Even if the process does not exist.@@ -238,8 +223,8 @@       (logToChannel         l         (runReader-          (scheduleProcessWithCleanup shutdownAction saveCleanupAndSchedule)           schedulerVar+          (scheduleProcessWithCleanup shutdownAction saveCleanupAndSchedule)         )       )    where@@ -343,14 +328,14 @@     -> (ResumeProcess v -> Eff SchedulerIO a)     -> Eff SchedulerIO a     -> Eff SchedulerIO a-  shutdownOrGo pid k ok = do+  shutdownOrGo pid !k !ok = do     psVar          <- getSchedulerTVar     eHasShutdowReq <- lift       (do         p <- atomically (readTVar psVar)         let mPinfo = p ^. processTable . at pid         case mPinfo of-          Just pinfo -> atomically+          Just !pinfo -> atomically             (do               wasRequested <- readTVar (pinfo ^. shutdownRequested)               -- reset the shutdwown request flag, in case the shutdown@@ -432,7 +417,7 @@     lift Concurrent.yield     k (ResumeWith pid) -  go pid YieldProcess k = shutdownOrGo pid k $ do+  go pid YieldProcess !k = shutdownOrGo pid k $ do     lift Concurrent.yield     k (ResumeWith ()) 
src/Control/Eff/Concurrent/Process/Interactive.hs view
@@ -1,38 +1,18 @@-module Control.Eff.Concurrent.Process.Interactive-  ( SchedulerSession()-  , forkInteractiveScheduler-  , killInteractiveScheduler-  , submit-  , submitCast-  , submitCall-  )-where--import           Control.Arrow-import           Control.Concurrent-import           Control.Concurrent.STM-import           Control.Eff-import           Control.Eff.Lift-import           Control.Eff.Concurrent.Api-import           Control.Eff.Concurrent.Api.Client-import           Control.Eff.Concurrent.Process-import           Control.Monad-import           Data.Typeable                  ( Typeable )-import           System.Timeout- -- | This module provides support for executing 'Process' actions from 'IO'. -- -- One use case is interacting with processes from the REPL, e.g.: -- -- >>> import Control.Eff.Concurrent.Process.SingleThreadedScheduler (defaultMain) --+-- >>> import Control.Eff.Loop+-- -- >>> import Data.Dynamic -- -- >>> import Data.Maybe -- -- >>> s <- forkInteractiveScheduler Control.Eff.Concurrent.Process.SingleThreadedScheduler.defaultMain ----- >>> fooPid <- submit s (spawn (forever (receiveMessage SP >>= (logMsg . fromMaybe "Huh!??" . fromDynamic))))+-- >>> fooPid <- submit s (spawn (foreverCheap (receiveMessage SP >>= (logMsg . fromMaybe "Huh!??" . fromDynamic)))) -- -- >>> fooPid -- <0.1.0>@@ -45,7 +25,28 @@ -- -- -- @since 0.3.0.1+module Control.Eff.Concurrent.Process.Interactive+  ( SchedulerSession()+  , forkInteractiveScheduler+  , killInteractiveScheduler+  , submit+  , submitCast+  , submitCall+  )+where +import           Control.Concurrent+import           Control.Concurrent.STM+import           Control.Eff+import           Control.Eff.Lift+import           Control.Eff.Concurrent.Api+import           Control.Eff.Concurrent.Api.Client+import           Control.Eff.Concurrent.Process+import           Control.Monad+import           Data.Foldable+import           Data.Typeable                  ( Typeable )+import           System.Timeout+ -- | Contains the communication channels to interact with a scheduler running in -- its' own thread. newtype SchedulerSession r = SchedulerSession (TMVar (SchedulerQueue r))@@ -74,28 +75,26 @@     )   return (SchedulerSession queueVar)  where-  readEvalPrintLoop = forever . (readAction >>> evalAction >=> printResult)+  readEvalPrintLoop queueVar = do+    nextActionOrExit <- readAction+    case nextActionOrExit of+      Left  True       -> return ()+      Left  False      -> readEvalPrintLoop queueVar+      Right nextAction -> do+        res <- nextAction+        traverse_ (lift . putStrLn . (">>> " ++)) res+        yieldProcess SP+        readEvalPrintLoop queueVar    where-    readAction queueVar = do-      nextActionOrExit <- lift $ atomically-        (do-          mInQueue <- tryReadTMVar queueVar-          case mInQueue of-            Nothing                       -> return (Left True)-            Just (SchedulerQueue inQueue) -> do-              mnextAction <- tryReadTChan inQueue-              case mnextAction of-                Nothing         -> return (Left False)-                Just nextAction -> return (Right nextAction)-        )-      case nextActionOrExit of-        Left True  -> exitNormally SP-        Left False -> do-          yieldProcess SP-          readAction queueVar-        Right r -> return r-    evalAction  = join-    printResult = mapM_ (lift . putStrLn)+    readAction = lift $ atomically $ do+      mInQueue <- tryReadTMVar queueVar+      case mInQueue of+        Nothing                       -> return (Left True)+        Just (SchedulerQueue inQueue) -> do+          mnextAction <- tryReadTChan inQueue+          case mnextAction of+            Nothing         -> return (Left False)+            Just nextAction -> return (Right nextAction)  -- | Exit the schedulder immediately using an asynchronous exception. killInteractiveScheduler :: SchedulerSession r -> IO ()
src/Control/Eff/Concurrent/Process/SingleThreadedScheduler.hs view
@@ -1,9 +1,10 @@ -- | A coroutine based, single threaded scheduler for 'Process'es. module Control.Eff.Concurrent.Process.SingleThreadedScheduler-  ( schedule+  ( scheduleM   , schedulePure   , scheduleIO-  , scheduleWithLogging+  , scheduleMonadIOEff+  , scheduleIOWithLogging   , defaultMain   , singleThreadedIoScheduler   , LoggingAndIo@@ -11,14 +12,17 @@ where  import           Control.Concurrent             ( yield )+import           Control.DeepSeq import           Control.Eff-import           Control.Eff.Concurrent.Process import           Control.Eff.Lift+import           Control.Eff.Extend+import           Control.Eff.Concurrent.Process import           Control.Eff.Log import           Control.Lens            hiding ( (|>)                                                 , Empty                                                 ) import           Control.Monad                  ( void )+import           Control.Monad.IO.Class import qualified Data.Sequence                 as Seq import           Data.Sequence                  ( Seq(..) ) import qualified Data.Map.Strict               as Map@@ -27,141 +31,79 @@ import           Data.Dynamic import           Data.Maybe --- | Invoke 'schedule' with @lift 'yield'@ as yield effect.--- @since 0.3.0.2-scheduleIO-  :: SetMember Lift (Lift IO) r-  => Eff (ConsProcess r) a-  -> Eff r (Either String a)-scheduleIO = schedule (lift yield)  -- | Like 'schedule' but /pure/. The @yield@ effect is just @return ()@.--- @schedulePure == 'run' . 'schedule' (return ())@+-- @schedulePure == runIdentity . 'scheduleM' (Identity . run)  (return ())@+-- -- @since 0.3.0.2 schedulePure :: Eff (ConsProcess '[]) a -> Either String a-schedulePure = run . schedule (return ())+schedulePure = runIdentity . scheduleM (Identity . run) (return ()) --- | Like 'schedulePure' but with logging.--- @scheduleWithLogging == 'run' . 'captureLogs' . 'schedule' (return ())@+-- | Invoke 'schedule' with @lift 'Control.Concurrent.yield'@ as yield effect.+-- @scheduleIO runEff == 'scheduleM' (runLift . runEff) (liftIO 'yield')@+--+-- @since 0.4.0.0+scheduleIO+  :: MonadIO m+  => (forall b . Eff r b -> Eff '[Lift m] b)+  -> Eff (ConsProcess r) a+  -> m (Either String a)+scheduleIO runEff = scheduleM (runLift . runEff) (liftIO yield)++-- | Invoke 'schedule' with @lift 'Control.Concurrent.yield'@ as yield effect.+-- @scheduleMonadIOEff == 'scheduleM' id (liftIO 'yield')@+-- -- @since 0.3.0.2-scheduleWithLogging :: Eff (ConsProcess '[Logs m]) a -> (Either String a, Seq m)-scheduleWithLogging = run . captureLogs . schedule (return ())+scheduleMonadIOEff+  :: MonadIO (Eff r) => Eff (ConsProcess r) a -> Eff r (Either String a)+scheduleMonadIOEff = -- schedule (lift yield)+  scheduleM id (liftIO yield) --- | Execute a 'Process' and all the other processes 'spawn'ed by it in the+-- | Run processes that have the 'Logs' and the 'Lift' effects.+-- The user must provide a log handler function.+--+-- Log messages are evaluated strict.+--+-- @scheduleIOWithLogging == 'run' . 'captureLogs' . 'schedule' (return ())@+--+-- @since 0.4.0.0+scheduleIOWithLogging+  :: (NFData l, MonadIO m)+  => (l -> m ())+  -> Eff (ConsProcess '[Logs l, Lift m]) a+  -> m (Either String a)+scheduleIOWithLogging handleLog e = scheduleIO (handleLogsWith handleLog) e++-- | Handle the 'Process' effect, as well as all lower effects using an effect handler function.+--+-- Execute the __main__ 'Process' and all the other processes 'spawn'ed by it in the -- current thread concurrently, using a co-routine based, round-robin -- scheduler. If a process exits with 'exitNormally', 'exitWithError', -- 'raiseError' or is killed by another process @Left ...@ is returned. -- Otherwise, the result will be wrapped in a @Right@.-schedule-  :: forall r finalResult-   . Eff r () -- ^ An that performs a __yield__ w.r.t. the underlying effect-              --  @r@. E.g. if @Lift IO@ is present, this might be:-              --  @lift 'Control.Concurrent.yield'.-  -> Eff (Process r ': r) finalResult-  -> Eff r (Either String finalResult)-schedule yieldEff mainProcessAction = do-  y <- runAsCoroutine mainProcessAction-  go 1 (Map.singleton 0 Seq.empty) (Seq.singleton (y, 0))- where-  go-    :: ProcessId-    -> Map.Map ProcessId (Seq Dynamic)-    -> Seq (OnYield r finalResult, ProcessId)-    -> Eff r (Either String finalResult)-  go _newPid _msgQs Empty = return (Left "no main process")--  go newPid msgQs allProcs@((processState, pid) :<| rest)-    = let-        handleExit res = if pid == 0-          then return res-          else go newPid (msgQs & at pid .~ Nothing) rest-        maybeYield = if pid == 0 then yieldEff else return ()-      in-        case processState of-          OnDone r                   -> handleExit (Right r)--          OnShutdown -> handleExit (Left "process exited normally")--          OnRaiseError errM          -> handleExit (Left errM)--          OnExitError  errM          -> handleExit (Left errM)--          OnSendShutdown targetPid k -> do-            let allButTarget =-                  Seq.filter (\(_, e) -> e /= pid && e /= targetPid) allProcs-                targets     = Seq.filter (\(_, e) -> e == targetPid) allProcs-                suicide     = targetPid == pid-                targetFound = suicide || not (Seq.null targets)-            if suicide-              then do-                nextK <- k ShutdownRequested-                go newPid msgQs (rest :|> (nextK, pid))-              else do-                let deliverTheGoodNews (targetState, tPid) = do-                      nextTargetState <- case targetState of-                        OnSendShutdown _ tk -> tk ShutdownRequested-                        OnYield tk          -> tk ShutdownRequested-                        OnSelf  tk          -> tk ShutdownRequested-                        OnSend _ _ tk       -> tk ShutdownRequested-                        OnRecv tk           -> tk ShutdownRequested-                        OnSpawn _ tk        -> tk ShutdownRequested-                        OnDone x            -> return (OnDone x)-                        OnShutdown          -> return OnShutdown-                        OnExitError  er     -> return (OnExitError er)-                        OnRaiseError er     -> return (OnExitError er) -- return (error ("TODO write test "++er))-                      return (nextTargetState, tPid)-                nextTargets <- traverse deliverTheGoodNews targets-                nextK       <- k (ResumeWith targetFound)-                maybeYield-                go newPid-                   msgQs-                   (allButTarget Seq.>< (nextTargets :|> (nextK, pid)))---          OnSelf k -> do-            nextK <- k (ResumeWith pid)-            maybeYield-            go newPid msgQs (rest :|> (nextK, pid))--          OnYield k -> do-            yieldEff-            nextK <- k (ResumeWith ())-            go newPid msgQs (rest :|> (nextK, pid))--          OnSend toPid msg k -> do-            nextK <- k (ResumeWith (msgQs ^. at toPid . to isJust))-            maybeYield-            go newPid-               (msgQs & at toPid . _Just %~ (:|> msg))-               (rest :|> (nextK, pid))--          recv@(OnRecv k) -> case msgQs ^. at pid of-            Nothing -> do-              nextK <- k (OnError (show pid ++ " has no message queue!"))-              maybeYield-              go newPid msgQs (rest :|> (nextK, pid))-            Just Empty -> if Seq.length rest == 0-              then do-                nextK <- k (OnError ("Process " ++ show pid ++ " deadlocked!"))-                maybeYield-                go newPid msgQs (rest :|> (nextK, pid))-              else go newPid msgQs (rest :|> (recv, pid))--            Just (nextMessage :<| restMessages) -> do-              nextK <- k (ResumeWith nextMessage)-              maybeYield-              go newPid-                 (msgQs & at pid . _Just .~ restMessages)-                 (rest :|> (nextK, pid))--          OnSpawn f k -> do-            nextK <- k (ResumeWith newPid)-            fk    <- runAsCoroutine (f >> exitNormally SP)-            maybeYield-            go (newPid + 1)-               (msgQs & at newPid .~ Just Seq.empty)-               (rest :|> (nextK, pid) :|> (fk, newPid))+--+-- Every time a process _yields_ the effects are evaluated down to the a value+-- of type @m (Either String a)@.+--+-- If the evaluator function runs the action down e.g. @IO@ this might improve+-- memory consumption, for long running services, with processes that loop+-- endlessly.+--+-- @since 0.4.0.0+scheduleM+  :: Monad m+  => (forall b . Eff r b -> m b)+  -> m () -- ^ An that performs a __yield__ w.r.t. the underlying effect+  --  @r@. E.g. if @Lift IO@ is present, this might be:+  --  @lift 'Control.Concurrent.yield'.+  -> Eff (ConsProcess r) a+  -> m (Either String a)+scheduleM runEff yieldEff e = do+  y <- runAsCoroutinePure runEff e+  handleProcess runEff yieldEff 1 (Map.singleton 0 Seq.empty) (Seq.singleton (y, 0)) +-- | Internal data structure that is part of the coroutine based scheduler+-- implementation. data OnYield r a where   OnYield :: (ResumeProcess () -> Eff r (OnYield r a))          -> OnYield r a@@ -170,30 +112,138 @@   OnSpawn :: Eff (Process r ': r) ()           -> (ResumeProcess ProcessId -> Eff r (OnYield r a))           -> OnYield r a-  OnDone :: a -> OnYield r a+  OnDone :: !a -> OnYield r a   OnShutdown :: OnYield r a-  OnExitError :: String -> OnYield r a-  OnRaiseError :: String -> OnYield r a-  OnSend :: ProcessId -> Dynamic+  OnExitError :: !String -> OnYield r a+  OnRaiseError :: !String -> OnYield r a+  OnSend :: !ProcessId -> !Dynamic          -> (ResumeProcess Bool -> Eff r (OnYield r a))          -> OnYield r a   OnRecv :: (ResumeProcess Dynamic -> Eff r (OnYield r a))          -> OnYield r a-  OnSendShutdown :: ProcessId -> (ResumeProcess Bool -> Eff r (OnYield r a)) -> OnYield r a+  OnSendShutdown :: !ProcessId -> (ResumeProcess Bool -> Eff r (OnYield r a)) -> OnYield r a -runAsCoroutine :: forall r v . Eff (Process r ': r) v -> Eff r (OnYield r v)-runAsCoroutine m = handle_relay (return . OnDone) cont m+-- | Internal 'Process' handler function.+handleProcess+  :: Monad m+  => (forall a . Eff r a -> m a)+  -> m ()+  -> ProcessId+  -> Map.Map ProcessId (Seq Dynamic)+  -> Seq (OnYield r finalResult, ProcessId)+  -> m (Either String finalResult)+handleProcess _runEff _yieldEff _newPid _msgQs Empty = return $ Left "no main process"++handleProcess runEff yieldEff !newPid !msgQs allProcs@((!processState, !pid) :<| rest)+  = let handleExit res = if pid == 0+          then return res+          else handleProcess runEff yieldEff newPid (msgQs & at pid .~ Nothing) rest+    in+      case processState of+        OnDone r                   -> handleExit (Right r)++        OnShutdown -> handleExit (Left "process exited normally")++        OnRaiseError errM          -> handleExit (Left errM)++        OnExitError  errM          -> handleExit (Left errM)++        OnSendShutdown targetPid k -> do+          let allButTarget =+                Seq.filter (\(_, e) -> e /= pid && e /= targetPid) allProcs+              targets     = Seq.filter (\(_, e) -> e == targetPid) allProcs+              suicide     = targetPid == pid+              targetFound = suicide || not (Seq.null targets)+          if suicide+            then do+              nextK <- runEff $ k ShutdownRequested+              handleProcess runEff yieldEff newPid msgQs (rest :|> (nextK, pid))+            else do+              let deliverTheGoodNews (targetState, tPid) = do+                    nextTargetState <- case targetState of+                      OnSendShutdown _ tk -> tk ShutdownRequested+                      OnYield tk          -> tk ShutdownRequested+                      OnSelf  tk          -> tk ShutdownRequested+                      OnSend _ _ tk       -> tk ShutdownRequested+                      OnRecv tk           -> tk ShutdownRequested+                      OnSpawn _ tk        -> tk ShutdownRequested+                      OnDone x            -> return (OnDone x)+                      OnShutdown          -> return OnShutdown+                      OnExitError  er     -> return (OnExitError er)+                      OnRaiseError er     -> return (OnExitError er)+                    return (nextTargetState, tPid)+              nextTargets <- runEff $ traverse deliverTheGoodNews targets+              nextK       <- runEff $ k (ResumeWith targetFound)+              handleProcess runEff+                     yieldEff+                     newPid+                     msgQs+                     (allButTarget Seq.>< (nextTargets :|> (nextK, pid)))+++        OnSelf k -> do+          nextK <- runEff $ k (ResumeWith pid)+          handleProcess runEff yieldEff newPid msgQs (rest :|> (nextK, pid))++        OnYield k -> do+          yieldEff+          nextK <- runEff $ k (ResumeWith ())+          handleProcess runEff yieldEff newPid msgQs (rest :|> (nextK, pid))++        OnSend toPid msg k -> do+          nextK <- runEff $ k (ResumeWith (msgQs ^. at toPid . to isJust))+          handleProcess runEff+                 yieldEff+                 newPid+                 (msgQs & at toPid . _Just %~ (:|> msg))+                 (rest :|> (nextK, pid))++        recv@(OnRecv k) -> case msgQs ^. at pid of+          Nothing -> do+            nextK <- runEff $ k (OnError (show pid ++ " has no message queue!"))+            handleProcess runEff yieldEff newPid msgQs (rest :|> (nextK, pid))+          Just Empty -> if Seq.length rest == 0+            then do+              nextK <- runEff+                $ k (OnError ("Process " ++ show pid ++ " deadlocked!"))+              handleProcess runEff yieldEff newPid msgQs (rest :|> (nextK, pid))+            else handleProcess runEff yieldEff newPid msgQs (rest :|> (recv, pid))++          Just (nextMessage :<| restMessages) -> do+            nextK <- runEff $ k (ResumeWith nextMessage)+            handleProcess runEff+                   yieldEff+                   newPid+                   (msgQs & at pid . _Just .~ restMessages)+                   (rest :|> (nextK, pid))++        OnSpawn f k -> do+          nextK <- runEff $ k (ResumeWith newPid)+          fk    <- runAsCoroutinePure runEff (f >> exitNormally SP)+          handleProcess runEff+                 yieldEff+                 (newPid + 1)+                 (msgQs & at newPid .~ Just Seq.empty)+                 (rest :|> (nextK, pid) :|> (fk, newPid))++runAsCoroutinePure+  :: forall v r m+   . Monad m+  => (forall a . Eff r a -> m a)+  -> Eff (ConsProcess r) v+  -> m (OnYield r v)+runAsCoroutinePure runEff = runEff . handle_relay (return . OnDone) cont  where   cont :: Process r x -> (x -> Eff r (OnYield r v)) -> Eff r (OnYield r v)-  cont YieldProcess         k  = return (OnYield k)-  cont SelfPid              k  = return (OnSelf k)-  cont (Spawn e)            k  = return (OnSpawn e k)-  cont Shutdown             _k = return OnShutdown-  cont (ExitWithError e   ) _k = return (OnExitError e)-  cont (RaiseError    e   ) _k = return (OnRaiseError e)-  cont (SendMessage tp msg) k  = return (OnSend tp msg k)-  cont ReceiveMessage       k  = return (OnRecv k)-  cont (SendShutdown pid)   k  = return (OnSendShutdown pid k)+  cont YieldProcess           k  = return (OnYield k)+  cont SelfPid                k  = return (OnSelf k)+  cont (Spawn e)              k  = return (OnSpawn e k)+  cont Shutdown               _k = return OnShutdown+  cont (ExitWithError !e    ) _k = return (OnExitError e)+  cont (RaiseError    !e    ) _k = return (OnRaiseError e)+  cont (SendMessage !tp !msg) k  = return (OnSend tp msg k)+  cont ReceiveMessage         k  = return (OnRecv k)+  cont (SendShutdown !pid)    k  = return (OnSendShutdown pid k)  -- | The concrete list of 'Eff'ects for running this pure scheduler on @IO@ and -- with string logging.@@ -212,4 +262,6 @@   :: HasCallStack   => Eff '[Process '[Logs String, Lift IO], Logs String, Lift IO] ()   -> IO ()-defaultMain go = void $ runLift $ handleLogsWith (scheduleIO go) ($! putStrLn)+defaultMain e = void $ runLift $ handleLogsWithLoggingTHandler+  (scheduleMonadIOEff e)+  ($! putStrLn)
− src/Control/Eff/InternalExtra.hs
@@ -1,17 +0,0 @@--- | "Control.Eff" utilities, some stolen from future versions of--- @extensible-effects@. In future versions, there might be something like a--- @Control.Eff.Extend@ module that might contain a lot of it, according to--- the discussion here:--- https://github.com/suhailshergill/extensible-effects/issues/98-module Control.Eff.InternalExtra where--import Control.Eff---- | Embeds a less-constrained 'Eff' into a more-constrained one. Analogous to--- MTL's 'lift'.-raise :: Eff r a -> Eff (e ': r) a-raise = loop-  where-    loop (Val x) = pure x-    loop (E u q) = E (weaken u) $ qComps q loop-{-# INLINE raise #-}
src/Control/Eff/Log.hs view
@@ -1,15 +1,3 @@-{-# LANGUAGE UndecidableInstances #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE TypeOperators #-}-{-# LANGUAGE TypeInType #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE GADTs #-}-{-# LANGUAGE KindSignatures #-}-{-# LANGUAGE DataKinds #-} -- | A logging effect based on 'Control.Monad.Log.MonadLog'. module Control.Eff.Log   (@@ -22,6 +10,7 @@   , captureLogs   , ignoreLogs   , handleLogsWith+  , handleLogsWithLoggingTHandler     -- * Concurrent Logging   , LogChannel()   , logToChannel@@ -33,12 +22,17 @@   , closeLogChannelAfter   , logChannelBracket   , logChannelPutIO+  -- ** Internals+  , JoinLogChannelException()+  , KillLogChannelException()   ) where  import           Control.Concurrent import           Control.Concurrent.STM+import           Control.DeepSeq import           Control.Eff                   as Eff+import           Control.Eff.Extend            as Eff import           Control.Exception              ( bracket ) import qualified Control.Exception             as Exc import           Control.Monad                  ( void@@ -51,8 +45,8 @@ import qualified Control.Eff.Lift              as Eff import qualified Control.Monad.Log             as Log import           Data.Foldable                  ( traverse_ )-import           Data.Kind()-import           Data.Sequence                 (Seq())+import           Data.Kind                      ( )+import           Data.Sequence                  ( Seq() ) import qualified Data.Sequence                 as Seq import           Data.String import           Data.Typeable@@ -81,15 +75,14 @@ -- Approach: Install a callback that sneaks into to log message -- sending/receiving, to intercept the messages and execute some code and then -- return a new message.-foldLog :: forall r m a . Member (Logs m) r-            => (m -> Eff r ()) -> Eff r a -> Eff r a-foldLog interceptor effect =-  interpose return go effect-  where-    go :: Member (Logs m) r => Logs m x -> (Arr r x y) -> Eff r y-    go (LogMsg m) k =-      do interceptor m-         k ()+foldLog+  :: forall r m a . Member (Logs m) r => (m -> Eff r ()) -> Eff r a -> Eff r a+foldLog interceptor effect = interpose return go effect+ where+  go :: Member (Logs m) r => Logs m x -> (Arr r x y) -> Eff r y+  go (LogMsg m) k = do+    interceptor m+    k ()  -- | Change, add or remove log messages without side effects, faster than -- 'foldLog'.@@ -105,56 +98,71 @@ -- Approach: Install a callback that sneaks into to log message -- sending/receiving, to intercept the messages and execute some code and then -- return a new message.-foldLogFast :: forall r m a f . (Foldable f, Member (Logs m) r)-            => (m -> f m) -> Eff r a -> Eff r a-foldLogFast interceptor effect =-  interpose return go effect-  where-    go :: Member (Logs m) r => Logs m x -> (Arr r x y) -> Eff r y-    go (LogMsg m) k =-      do traverse_ logMsg (interceptor m)-         k ()+foldLogFast+  :: forall r m a f+   . (Foldable f, Member (Logs m) r)+  => (m -> f m)+  -> Eff r a+  -> Eff r a+foldLogFast interceptor effect = interpose return go effect+ where+  go :: Member (Logs m) r => Logs m x -> (Arr r x y) -> Eff r y+  go (LogMsg m) k = do+    traverse_ logMsg (interceptor m)+    k () --- | Capture the logs in a 'Seq'.-captureLogs :: Eff (Logs message ':  r) a-            -> Eff r (a, Seq message)-captureLogs actionThatLogs =-  Eff.handle_relay_s-     Seq.empty-     (\logs result -> return (result, logs))-     handleLogs-     actionThatLogs-  where-    handleLogs :: Seq message-               -> Logs message x-               -> (Seq message -> Arr r x y)-               -> Eff r y-    handleLogs logs (LogMsg m) k =-      k (logs Seq.:|> m) ()+-- | Capture all log messages in a 'Seq' (strict).+captureLogs+  :: NFData message => Eff (Logs message ': r) a -> Eff r (a, Seq message)+captureLogs actionThatLogs = Eff.handle_relay_s+  Seq.empty+  (\logs result -> return (result, logs))+  handleLogs+  actionThatLogs+ where+  handleLogs+    :: NFData message+    => Seq message+    -> Logs message x+    -> (Seq message -> Arr r x y)+    -> Eff r y+  handleLogs !logs (LogMsg !m) k = k (force (logs Seq.:|> m)) ()  -- | Throw away all log messages.-ignoreLogs :: Eff (Logs message ':  r) a-           -> Eff r a-ignoreLogs actionThatLogs =-  Eff.handle_relay return handleLogs actionThatLogs-  where-    handleLogs :: Logs m x -> Arr r x y -> Eff r y-    handleLogs (LogMsg _) k = k ()+ignoreLogs :: forall message r a . Eff (Logs message ': r) a -> Eff r a+ignoreLogs actionThatLogs = Eff.handle_relay return handleLogs actionThatLogs+ where+  handleLogs :: Logs m x -> Arr r x y -> Eff r y+  handleLogs (LogMsg _) k = k () --- | Handle 'Logs' effects using 'Log.LoggingT' 'Log.Handler's.+-- | Handle the 'Logs' effect with a monadic call back function (strict). handleLogsWith   :: forall m r message a+   . (NFData message, Monad m, SetMember Eff.Lift (Eff.Lift m) r)+  => (message -> m ())+  -> Eff (Logs message ': r) a+  -> Eff r a+handleLogsWith logMessageHandler = Eff.handle_relay return go+ where+  go :: Logs message b -> (b -> Eff r c) -> Eff r c+  go (LogMsg m) k = do+    res <- Eff.lift (logMessageHandler (force m))+    k res++-- | Handle the 'Logs' effect using 'Log.LoggingT' 'Log.Handler's.+handleLogsWithLoggingTHandler+  :: forall m r message a    . (Monad m, SetMember Eff.Lift (Eff.Lift m) r)   => Eff (Logs message ': r) a   -> (forall b . (Log.Handler m message -> m b) -> m b)   -> Eff r a-handleLogsWith actionThatLogs foldHandler = Eff.handle_relay return-                                                             go-                                                             actionThatLogs+handleLogsWithLoggingTHandler actionThatLogs foldHandler = Eff.handle_relay+  return+  go+  actionThatLogs  where   go :: Logs message b -> (b -> Eff r c) -> Eff r c-  go (LogMsg m) k =-    Eff.lift (foldHandler (\doLog -> doLog m)) >>= k+  go (LogMsg m) k = Eff.lift (foldHandler (\doLog -> doLog m)) >>= k  -- | A log channel processes logs from the 'Logs' effect by en-queuing them in a -- shared queue read from a seperate processes. A channel can contain log@@ -178,19 +186,17 @@   -> Eff (Logs message ': r) a   -> Eff r a logToChannel logChan actionThatLogs = do-  handleLogsWith actionThatLogs-                 (\withHandler -> withHandler (logChannelPutIO logChan))+  handleLogsWithLoggingTHandler+    actionThatLogs+    (\withHandler -> withHandler (logChannelPutIO logChan))  -- | Enqueue a log message into a log channel logChannelPutIO :: LogChannel message -> message -> IO ()-logChannelPutIO DiscardLogs _ =-  return ()-logChannelPutIO (FilteredLogChannel f lc) m =-  when (f m) (logChannelPutIO lc m)-logChannelPutIO c m =-  atomically $ do-      dropMessage <- isFullTBQueue (fromLogChannel c)-      unless dropMessage (writeTBQueue (fromLogChannel c) m)+logChannelPutIO DiscardLogs               _ = return ()+logChannelPutIO (FilteredLogChannel f lc) m = when (f m) (logChannelPutIO lc m)+logChannelPutIO c                         m = atomically $ do+  dropMessage <- isFullTBQueue (fromLogChannel c)+  unless dropMessage (writeTBQueue (fromLogChannel c) m)  -- | Create a 'LogChannel' that will discard all messages sent -- via 'forwardLogstochannel' or 'logChannelPutIO'.@@ -218,21 +224,18 @@   return (ConcurrentLogChannel msgQ thread)  where   writeLastLogs :: TBQueue message -> Either Exc.SomeException () -> IO ()-  writeLastLogs tq ee =-    do logMessages <- atomically $ flushTBQueue tq-       case ee of-         Right _ -> return ()-         Left se ->-           case Exc.fromException se of-             Just (JoinLogChannelException mCloseMsg) ->-               do traverse_ handle logMessages-                  traverse_ handle mCloseMsg-             Nothing ->-               case Exc.fromException se of-                 Just (KillLogChannelException mCloseMsg) ->-                   traverse_ handle mCloseMsg-                 Nothing ->-                   mapM_ handle logMessages+  writeLastLogs tq ee = do+    logMessages <- atomically $ flushTBQueue tq+    case ee of+      Right _  -> return ()+      Left  se -> case Exc.fromException se of+        Just (JoinLogChannelException mCloseMsg) -> do+          traverse_ handle logMessages+          traverse_ handle mCloseMsg+        Nothing -> case Exc.fromException se of+          Just (KillLogChannelException mCloseMsg) ->+            traverse_ handle mCloseMsg+          Nothing -> mapM_ handle logMessages    logLoop :: TBQueue message -> IO ()   logLoop tq = do@@ -241,62 +244,66 @@     logLoop tq  -- | Filter logs sent to a 'LogChannel' using a predicate.-filterLogChannel :: (message -> Bool) -> LogChannel message -> LogChannel message+filterLogChannel+  :: (message -> Bool) -> LogChannel message -> LogChannel message filterLogChannel = FilteredLogChannel  -- | Run an action and close a 'LogChannel' created by 'noLogger', 'forkLogger' -- or 'filterLogChannel' afterwards using 'joinLogChannel'. If a -- 'Exc.SomeException' was thrown, the log channel is killed with -- 'killLogChannel', and the exception is re-thrown.-closeLogChannelAfter :: (Show message, Typeable message, IsString message)+closeLogChannelAfter+  :: (Show message, Typeable message, IsString message)   => Maybe message   -> LogChannel message   -> IO a   -> IO a-closeLogChannelAfter mGoodbye logC ioAction =-  do res <- closeLogAndRethrow `Exc.handle` ioAction-     closeLogSuccess-     return res-  where-    closeLogAndRethrow :: Exc.SomeException -> IO a-    closeLogAndRethrow se =-      do let closeMsg = Just (fromString (Exc.displayException se))-         void $ Exc.try @Exc.SomeException-              $ killLogChannel closeMsg logC-         Exc.throw se+closeLogChannelAfter mGoodbye logC ioAction = do+  res <- closeLogAndRethrow `Exc.handle` ioAction+  closeLogSuccess+  return res+ where+  closeLogAndRethrow :: Exc.SomeException -> IO a+  closeLogAndRethrow se = do+    let closeMsg = Just (fromString (Exc.displayException se))+    void $ Exc.try @Exc.SomeException $ killLogChannel closeMsg logC+    Exc.throw se -    closeLogSuccess :: IO ()-    closeLogSuccess =-      joinLogChannel mGoodbye logC+  closeLogSuccess :: IO ()+  closeLogSuccess = joinLogChannel mGoodbye logC  -- | Close a log channel created by e.g. 'forkLogger'. Message already enqueue -- are handled, as well as an optional final message. Subsequent log message -- will not be handled anymore. If the log channel must be closed immediately, -- use 'killLogChannel' instead.-joinLogChannel :: (Show message, Typeable message)-                 => Maybe message -> LogChannel message -> IO ()+joinLogChannel+  :: (Show message, Typeable message)+  => Maybe message+  -> LogChannel message+  -> IO () joinLogChannel _closeLogMessage DiscardLogs = return () joinLogChannel Nothing (FilteredLogChannel _f lc) = joinLogChannel Nothing lc joinLogChannel (Just closeLogMessage) (FilteredLogChannel f lc) =-  if f closeLogMessage then-    joinLogChannel (Just closeLogMessage) lc-  else-    joinLogChannel Nothing lc+  if f closeLogMessage+    then joinLogChannel (Just closeLogMessage) lc+    else joinLogChannel Nothing lc joinLogChannel closeLogMessage (ConcurrentLogChannel _tq thread) = do   throwTo thread (JoinLogChannelException closeLogMessage)  -- | Close a log channel quickly, without logging messages already in the queue. -- Subsequent logging requests will not be handled anymore. If the log channel -- must be closed without loosing any messages, use 'joinLogChannel' instead.-killLogChannel :: (Show message, Typeable message)-                 => Maybe message -> LogChannel message -> IO ()+killLogChannel+  :: (Show message, Typeable message)+  => Maybe message+  -> LogChannel message+  -> IO () killLogChannel _closeLogMessage DiscardLogs = return () killLogChannel Nothing (FilteredLogChannel _f lc) = killLogChannel Nothing lc killLogChannel (Just closeLogMessage) (FilteredLogChannel f lc) =-  if f closeLogMessage then-    killLogChannel (Just closeLogMessage) lc-  else-    killLogChannel Nothing lc+  if f closeLogMessage+    then killLogChannel (Just closeLogMessage) lc+    else killLogChannel Nothing lc killLogChannel closeLogMessage (ConcurrentLogChannel _tq thread) =   throwTo thread (KillLogChannelException closeLogMessage) @@ -335,4 +342,7 @@ logChannelBracket queueLen mWelcome mGoodbye f = control   (\runInIO -> do     let logHandler = void . runInIO . logMessage-    bracket (forkLogger queueLen logHandler mWelcome) (joinLogChannel mGoodbye) f)+    bracket (forkLogger queueLen logHandler mWelcome)+            (joinLogChannel mGoodbye)+            f+  )
+ src/Control/Eff/Loop.hs view
@@ -0,0 +1,53 @@+-- | In rare occasions GHC optimizes innocent looking loops into space-leaking monsters.+-- See the discussion here: <https://ghc.haskell.org/trac/ghc/ticket/13080 GHC issue 13080> for more+-- details, or <https://ro-che.info/articles/2017-01-10-nested-loop-space-leak this blog post about space leaks in nested loops>+--+-- These functions in this module __/might/__ help, at least in conjunction with+-- the @-fno-full-laziness@ GHC option.+--+-- There is a unit test in the sources of this module, which can be used to do a+-- comperative heap profiling of these function vs. their counterparts in the+-- @base@ package.+--+-- Here are the images of the profiling results, the images show that the+-- functions in this module do not leak space, compared to the original functions+-- ('Control.Monad.forever' and 'Control.Monad.replicateM_'):+--+-- ![Heap profiling of the unit tests called Loop-without-space-leaks shows that less than 200k Bytes were used](docs/extensible-effects-concurrent-test-Loop-without-space-leaks.png)+-- ![Heap profiling of the unit tests called Loop-WITH-space-leaks shows that at least 160M Bytes were used](docs/extensible-effects-concurrent-test-Loop-WITH-space-leaks.png)+--+-- @since 0.4.0.0+module Control.Eff.Loop+  ( foreverCheap+  , replicateCheapM_+  )+where++import           Control.Monad                  ( void )++-- | A version of 'Control.Monad.forever' that hopefully tricks+-- GHC into __/not/__ creating a space leak.+-- The intuition is, that we want to do something that is /cheap/, and hence+-- should be __recomputed__ instead of shared.+--+-- @since 0.4.0.0+{-# NOINLINE foreverCheap #-}+foreverCheap :: Monad m => m a -> m ()+foreverCheap x = x >> foreverCheap (x >> x)++-- | A version of 'Control.Monad.replicateM_' that hopefully tricks+-- GHC into __/not/__ creating a space leak.+-- The intuition is, that we want to do something that is /cheap/, and hence+-- should be __recomputed__ instead of shared.+--+-- @since 0.4.0.0+{-# NOINLINE replicateCheapM_ #-}+replicateCheapM_ :: Monad m => Int -> m a -> m ()+replicateCheapM_ n e = if n <= 0+  then return ()+  else if n == 1+    then void e+    else+      let nL = n `div` 2+          nR = n - nL+      in  replicateCheapM_ nL e >> replicateCheapM_ nR e
+ stack.nightly-2018-05-29.yaml view
@@ -0,0 +1,68 @@+# This file was automatically generated by 'stack init'+#+# Some commonly used options have been documented as comments in this file.+# For advanced use and comprehensive documentation of the format, please see:+# https://docs.haskellstack.org/en/stable/yaml_configuration/++# Resolver to choose a 'specific' stackage snapshot or a compiler version.+# A snapshot resolver dictates the compiler version and the set of packages+# to be used for project dependencies. For example:+#+# resolver: lts-3.5+# resolver: nightly-2015-09-21+# resolver: ghc-7.10.2+# resolver: ghcjs-0.1.0_ghc-7.10.2+# resolver:+#  name: custom-snapshot+#  location: "./custom-snapshot.yaml"+resolver: nightly-2018-05-29++# User packages to be built.+# Various formats can be used as shown in the example below.+#+# packages:+# - some-directory+# - https://example.com/foo/bar/baz-0.0.2.tar.gz+# - location:+#    git: https://github.com/commercialhaskell/stack.git+#    commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a+# - location: https://github.com/commercialhaskell/stack/commit/e7b331f14bcffb8367cd58fbfc8b40ec7642100a+#   extra-dep: true+#  subdirs:+#  - auto-update+#  - wai+#+# A package marked 'extra-dep: true' will only be built if demanded by a+# non-dependency (i.e. a user package), and its test suites and benchmarks+# will not be run. This is useful for tweaking upstream packages.+packages:+- .+# Dependency packages to be pulled from upstream that are not in the resolver+# (e.g., acme-missiles-0.3)+extra-deps:+- brittany-0.10.0.0+- extensible-effects-3.0.0.0++# Override default flag values for local packages and extra-deps+# flags: {}++# Extra package databases containing global packages+# extra-package-dbs: []++# Control whether we use the GHC we find on the path+# system-ghc: true+#+# Require a specific version of stack, using version ranges+# require-stack-version: -any # Default+require-stack-version: ">=1.6"+#+# Override the architecture used by stack, especially useful on Windows+# arch: i386+# arch: x86_64+#+# Extra directories used by stack for building+# extra-include-dirs: [/path/to/dir]+# extra-lib-dirs: [/path/to/dir]+#+# Allow a newer minor version of GHC than the snapshot specifies+# compiler-check: newer-minor
+ stack.yaml view
@@ -0,0 +1,68 @@+# This file was automatically generated by 'stack init'+#+# Some commonly used options have been documented as comments in this file.+# For advanced use and comprehensive documentation of the format, please see:+# https://docs.haskellstack.org/en/stable/yaml_configuration/++# Resolver to choose a 'specific' stackage snapshot or a compiler version.+# A snapshot resolver dictates the compiler version and the set of packages+# to be used for project dependencies. For example:+#+# resolver: lts-3.5+# resolver: nightly-2015-09-21+# resolver: ghc-7.10.2+# resolver: ghcjs-0.1.0_ghc-7.10.2+# resolver:+#  name: custom-snapshot+#  location: "./custom-snapshot.yaml"+resolver: lts-11.9++# User packages to be built.+# Various formats can be used as shown in the example below.+#+# packages:+# - some-directory+# - https://example.com/foo/bar/baz-0.0.2.tar.gz+# - location:+#    git: https://github.com/commercialhaskell/stack.git+#    commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a+# - location: https://github.com/commercialhaskell/stack/commit/e7b331f14bcffb8367cd58fbfc8b40ec7642100a+#   extra-dep: true+#  subdirs:+#  - auto-update+#  - wai+#+# A package marked 'extra-dep: true' will only be built if demanded by a+# non-dependency (i.e. a user package), and its test suites and benchmarks+# will not be run. This is useful for tweaking upstream packages.+packages:+- .+# Dependency packages to be pulled from upstream that are not in the resolver+# (e.g., acme-missiles-0.3)+extra-deps:+- brittany-0.10.0.0+- extensible-effects-3.0.0.0++# Override default flag values for local packages and extra-deps+# flags: {}++# Extra package databases containing global packages+# extra-package-dbs: []++# Control whether we use the GHC we find on the path+# system-ghc: true+#+# Require a specific version of stack, using version ranges+# require-stack-version: -any # Default+require-stack-version: ">=1.6"+#+# Override the architecture used by stack, especially useful on Windows+# arch: i386+# arch: x86_64+#+# Extra directories used by stack for building+# extra-include-dirs: [/path/to/dir]+# extra-lib-dirs: [/path/to/dir]+#+# Allow a newer minor version of GHC than the snapshot specifies+# compiler-check: newer-minor
test/Common.hs view
@@ -1,38 +1,44 @@-module Common where+module Common+where -import Control.Concurrent.STM-import Control.Eff.Concurrent.Process-import Control.Eff-import Control.Eff.Log-import Control.Eff.Lift-import Control.Monad (void)-import Test.Tasty-import Test.Tasty.HUnit-import Test.Tasty.Runners+import           Control.Concurrent.STM+import           Control.Eff.Concurrent.Process+import           Control.Eff+import           Control.Eff.Extend+import           Control.Eff.Log+import           Control.Eff.Lift+import           Control.Monad                  ( void )+import           Test.Tasty+import           Test.Tasty.HUnit+import           Test.Tasty.Runners  setTravisTestOptions :: TestTree -> TestTree-setTravisTestOptions = localOption (timeoutSeconds 300) . localOption (NumThreads 1)+setTravisTestOptions =+  localOption (timeoutSeconds 300) . localOption (NumThreads 1)  timeoutSeconds :: Integer -> Timeout timeoutSeconds seconds = Timeout (seconds * 1000000) (show seconds ++ "s")  withTestLogC-  :: (e -> LogChannel String -> IO ()) -> (IO (e -> IO ()) -> TestTree) -> TestTree-withTestLogC doSchedule k =-  withResource+  :: (e -> LogChannel String -> IO ())+  -> (IO (e -> IO ()) -> TestTree)+  -> TestTree+withTestLogC doSchedule k = withResource   testLogC   testLogJoin-  (\ logCFactory ->-       k (return-            (\e ->-                do logC <- logCFactory-                   doSchedule e logC)))+  (\logCFactory -> k+    (return+      (\e -> do+        logC <- logCFactory+        doSchedule e logC -- noLogger+      )+    )+  )  testLogC :: IO (LogChannel String) testLogC =   filterLogChannel (\m -> take (length logPrefix) m == logPrefix)-  <$>-  forkLogger 1 (putStrLn)  Nothing+    <$> forkLogger 1 (putStrLn) Nothing  testLogJoin :: LogChannel String -> IO () testLogJoin = joinLogChannel Nothing@@ -43,29 +49,39 @@ logPrefix :: String logPrefix = "[TEST] " -untilShutdown-  :: Member t r => t (ResumeProcess v) -> Eff r ()+untilShutdown :: Member t r => t (ResumeProcess v) -> Eff r () untilShutdown pa = do   r <- send pa   case r of     ShutdownRequested -> return ()-    _ -> untilShutdown pa+    _                 -> untilShutdown pa -scheduleAndAssert :: forall r .-                    (SetMember Lift (Lift IO) r, Member (Logs String) r)-                  => IO (Eff (Process r ': r) () -> IO ())-                  -> ((String -> Bool -> Eff (Process r ': r) ()) -> Eff (Process r ': r) ())-                  -> IO ()-scheduleAndAssert schedulerFactory testCaseAction =-  do resultVar <- newEmptyTMVarIO-     void (applySchedulerFactory schedulerFactory-            (testCaseAction (\ title cond ->-                               do lift (atomically (putTMVar resultVar (title, cond))))))-     (title, result) <- atomically (takeTMVar resultVar)-     assertBool title result+scheduleAndAssert+  :: forall r+   . (SetMember Lift (Lift IO) r, Member (Logs String) r)+  => IO (Eff (Process r ': r) () -> IO ())+  -> (  (String -> Bool -> Eff (Process r ': r) ())+     -> Eff (Process r ': r) ()+     )+  -> IO ()+scheduleAndAssert schedulerFactory testCaseAction = do+  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 . (Member (Logs String) r, SetMember Lift (Lift IO) r)-                      => IO (Eff (Process r ': r) () -> IO ()) -> Eff (Process r ': r) () -> IO ()-applySchedulerFactory factory procAction =-  do scheduler <- factory-     scheduler procAction+applySchedulerFactory+  :: forall r+   . (Member (Logs String) r, SetMember Lift (Lift IO) r)+  => IO (Eff (Process r ': r) () -> IO ())+  -> Eff (Process r ': r) ()+  -> IO ()+applySchedulerFactory factory procAction = do+  scheduler <- factory+  scheduler procAction
test/ForkIOScheduler.hs view
@@ -4,14 +4,13 @@ import           Control.Exception import           Control.Concurrent import           Control.Concurrent.STM-import           Control.Eff+import           Control.Eff.Loop+import           Control.Eff.Extend import           Control.Eff.Lift import           Control.Eff.Concurrent.Process import           Control.Eff.Concurrent.Process.ForkIOScheduler                                                as Scheduler-import           Control.Monad                  ( void-                                                , forever-                                                )+import           Control.Monad                  ( void ) import           Test.Tasty import           Test.Tasty.HUnit import           Data.Dynamic@@ -30,7 +29,7 @@           aVar <- newEmptyTMVarIO           lc   <- testLogC           Scheduler.defaultMainWithLogChannel lc $ do-            p1 <- spawn $ forever busyEffect+            p1 <- spawn $ foreverCheap busyEffect             lift (threadDelay 1000)             void $ spawn $ do               lift (threadDelay 1000)@@ -104,7 +103,7 @@           (do             void               (spawn-                (forever+                (foreverCheap                   (void (sendMessage forkIoScheduler 1000 (toDyn "test")))                 )               )
+ test/Interactive.hs view
@@ -0,0 +1,36 @@+module Interactive+where++import           Control.Concurrent+import           Control.Eff+import           Control.Eff.Lift+import           Control.Eff.Concurrent.Process+import           Control.Eff.Concurrent.Process.Interactive+import qualified Control.Eff.Concurrent.Process.SingleThreadedScheduler+                                               as SingleThreaded+import qualified Control.Eff.Concurrent.Process.ForkIOScheduler+                                               as ForkIOScheduler+import           Test.Tasty+import           Test.Tasty.HUnit+import           Common++test_interactive :: TestTree+test_interactive = setTravisTestOptions $ testGroup+  "Interactive"+  [ testGroup "SingleThreadedScheduler"  $ allTests SingleThreaded.defaultMain+  , testGroup "ForkIOScheduler" $ allTests ForkIOScheduler.defaultMain+  ]++allTests :: SetMember Lift (Lift IO) r => (Eff (ConsProcess r) () -> IO ()) -> [TestTree]+allTests scheduler =+  [ happyCaseTest scheduler+  ]++happyCaseTest+  :: SetMember Lift (Lift IO) r => (Eff (ConsProcess r) () -> IO ()) -> TestTree+happyCaseTest scheduler =+  testCase "start, wait and stop interactive scheduler" $ do+    s <- forkInteractiveScheduler scheduler+    threadDelay 100000+    killInteractiveScheduler s+    return ()
+ test/LoopTests.hs view
@@ -0,0 +1,95 @@+module LoopTests+    ( test_loopTests+    , test_loopWithLeaksTests+    )+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_loopTests :: TestTree+test_loopTests+    = let soMany = 1000000+      in+          setTravisTestOptions $ testGroup+              "Loops without space leaks"+              [ testCase "scheduleMonadIOEff with many yields from replicateCheapM_" $ do+                  res <-+                      Scheduler.scheduleIOWithLogging+                          ($! (putStrLn . (">>> " ++)))+                      $ replicateCheapM_ soMany+                      $ yieldProcess SP+                  res @=? Right ()+              , testCase "replicateCheapM_ of strict Int increments via the state effect" $ do+                  let+                      res = run+                          (execState+                              (0 :: Int)+                              (replicateCheapM_ soMany $ modify (force . (+ 1)))+                          )+                  res @=? soMany+              , testCase+               "'foreverCheap' inside a child process and 'replicateCheapM_' in the main process"+                  $ do+                        res <-+                            Scheduler.scheduleIOWithLogging+                                    ($! (putStrLn . (">>> " ++)))+                                $ do+                                      me <- self SP+                                      spawn_+                                          (foreverCheap $ sendMessageAs SP me ()+                                          )+                                      replicateCheapM_+                                          soMany+                                          (void (receiveMessageAs @() SP))++                        res @=? Right ()+              ]+++test_loopWithLeaksTests :: TestTree+test_loopWithLeaksTests+    = let soMany = 1000000+      in+          setTravisTestOptions $ testGroup+              "Loops WITH space leaks"+              [ testCase "scheduleMonadIOEff with many yields from replicateM_" $ do+                    res <-+                        Scheduler.scheduleIOWithLogging+                            ($! (putStrLn . (">>> " ++)))+                        $ replicateM_ soMany+                        $ yieldProcess SP+                    res @=? Right ()+                , testCase "replicateM_ of strict Int increments via the state effect" $ do+                    let+                        res = run+                            (execState+                                (0 :: Int)+                                (replicateM_ soMany $ modify (force . (+ 1)))+                            )+                    res @=? soMany+                , testCase+                        "'forever' inside a child process and 'replicateM_' in the main process"+                    $ do+                        res <-+                            Scheduler.scheduleIOWithLogging+                                    ($! (putStrLn . (">>> " ++)))+                                $ do+                                        me <- self SP+                                        spawn_+                                            (forever $ sendMessageAs SP me ()+                                            )+                                        replicateM_+                                            soMany+                                            (void (receiveMessageAs @() SP))++                        res @=? Right ()+            ]
test/ProcessBehaviourTestCases.hs view
@@ -13,13 +13,11 @@ import qualified Control.Eff.Concurrent.Process.SingleThreadedScheduler                                                as SingleThreaded import           Control.Eff+import           Control.Eff.Extend import           Control.Eff.Log+import           Control.Eff.Loop import           Control.Eff.Lift-import           Control.Monad                  ( void-                                                , replicateM-                                                , forever-                                                , when-                                                )+import           Control.Monad import           Test.Tasty import           Test.Tasty.HUnit import           Common@@ -33,10 +31,16 @@ test_singleThreaded :: TestTree test_singleThreaded = setTravisTestOptions   (withTestLogC-    (const . SingleThreaded.defaultMain)+    (\e logC ->+        -- void (runLift (logToChannel logC (SingleThreaded.schedule (return ()) e)))+      let runEff :: Eff '[Logs String, Lift IO] a -> IO a+          runEff = runLift . logToChannel logC+      in  void $ SingleThreaded.scheduleM runEff yield e+    )     (\factory -> testGroup "SingleThreadedScheduler" [allTests factory])   ) + allTests   :: forall r    . (Member (Logs String) r, SetMember Lift (Lift IO) r)@@ -51,9 +55,45 @@     , concurrencyTests schedulerFactory     , exitTests schedulerFactory     , pingPongTests schedulerFactory+    , yieldLoopTests schedulerFactory     ]   ) +yieldLoopTests+  :: forall r+   . (Member (Logs String) r, SetMember Lift (Lift IO) r)+  => IO (Eff (Process r ': r) () -> IO ())+  -> TestTree+yieldLoopTests schedulerFactory+  = let maxN = 100000+    in+      setTravisTestOptions+        (testGroup+          "yield tests"+          [ testCase+            "yield many times (replicateM_)"+            (applySchedulerFactory schedulerFactory+                                   (replicateM_ maxN (yieldProcess SP))+            )+          , testCase+            "yield many times (forM_)"+            (applySchedulerFactory+              schedulerFactory+              (forM_ [1 :: Int .. maxN] (\_ -> yieldProcess SP))+            )+          , testCase+            "construct an effect with an exit first, followed by many yields"+            (applySchedulerFactory+              schedulerFactory+              (do+                void (exitNormally SP)+                replicateM_ 1000000000000 (yieldProcess SP)+              )+            )+          ]+        )++ data Ping = Ping ProcessId data Pong = Pong   deriving (Eq, Show)@@ -68,7 +108,7 @@   [ testCase "ping pong a message between two processes, both don't yield"   $ applySchedulerFactory schedulerFactory   $ do-      let pongProc = forever $ do+      let pongProc = foreverCheap $ do             Ping pinger <- receiveMessageAs SP             sendMessageAs SP pinger Pong           pingProc ponger parent = do@@ -85,7 +125,7 @@   $ applySchedulerFactory schedulerFactory   $ do       yieldProcess SP-      let pongProc = forever $ do+      let pongProc = foreverCheap $ do             yieldProcess SP             Ping pinger <- receiveMessageAs SP             yieldProcess SP@@ -117,7 +157,7 @@   $ applySchedulerFactory schedulerFactory   $ do       pongVar <- lift newEmptyMVar-      let pongProc = forever $ do+      let pongProc = foreverCheap $ do             Pong <- receiveMessageAs SP             lift (putMVar pongVar Pong)       ponger <- spawn pongProc@@ -152,7 +192,7 @@           $ do               void $ raiseError px "test error"               error "This should not happen"-          , testCase "catch raiseError"+          , testCase "catch raiseError 1"           $ scheduleAndAssert schedulerFactory           $ \assertEff -> catchRaisedError               px@@ -192,7 +232,7 @@                     void (exitWithError px (show i ++ " died"))                     assertEff "this should not be reached" False                   else-                    forever+                    foreverCheap                       (void (sendMessage px 888 (toDyn "test message to 888"))                       )                 )@@ -252,19 +292,19 @@                 m <- receiveMessage px                 void (sendMessage px me m)               )-            child2 <- spawn (forever (void (sendMessage px 888 (toDyn ""))))+            child2 <- spawn (foreverCheap (void (sendMessage px 888 (toDyn ""))))             True   <- sendMessageChecked px child1 (toDyn "test")             i      <- receiveMessageAs px             True   <- sendShutdownChecked px child2             assertEff "" (i == "test")-        , testCase "most processes send forever"+        , testCase "most processes send foreverCheap"         $ scheduleAndAssert schedulerFactory         $ \assertEff -> do             me <- self px             traverse_               (\(i :: Int) -> spawn $ do                 when (i `rem` 5 == 0) $ void $ sendMessage px me (toDyn i)-                forever+                foreverCheap                   $ void (sendMessage px 888 (toDyn "test message to 888"))               )               [0 .. n]@@ -275,14 +315,14 @@                 return j               )             assertEff "" (sort oks == [0, 5 .. n])-        , testCase "most processes self forever"+        , testCase "most processes self foreverCheap"         $ scheduleAndAssert schedulerFactory         $ \assertEff -> do             me <- self px             traverse_               (\(i :: Int) -> spawn $ do                 when (i `rem` 5 == 0) $ void $ sendMessage px me (toDyn i)-                forever $ void (self px)+                foreverCheap $ void (self px)               )               [0 .. n]             oks <- replicateM@@ -292,14 +332,14 @@                 return j               )             assertEff "" (sort oks == [0, 5 .. n])-        , testCase "most processes sendShutdown forever"+        , testCase "most processes sendShutdown foreverCheap"         $ scheduleAndAssert schedulerFactory         $ \assertEff -> do             me <- self px             traverse_               (\(i :: Int) -> spawn $ do                 when (i `rem` 5 == 0) $ void $ sendMessage px me (toDyn i)-                forever $ void (sendShutdown px 999)+                foreverCheap $ void (sendShutdown px 999)               )               [0 .. n]             oks <- replicateM@@ -309,7 +349,7 @@                 return j               )             assertEff "" (sort oks == [0, 5 .. n])-        , testCase "most processes spawn forever"+        , testCase "most processes spawn foreverCheap"         $ scheduleAndAssert schedulerFactory         $ \assertEff -> do             me <- self px@@ -317,7 +357,7 @@               (\(i :: Int) -> spawn $ do                 when (i `rem` 5 == 0) $ void $ sendMessage px me (toDyn i)                 parent <- self px-                forever+                foreverCheap                   $ void                       (spawn                         (void@@ -334,14 +374,14 @@                 return j               )             assertEff "" (sort oks == [0, 5 .. n])-        , testCase "most processes receive forever"+        , testCase "most processes receive foreverCheap"         $ scheduleAndAssert schedulerFactory         $ \assertEff -> do             me <- self px             traverse_               (\(i :: Int) -> spawn $ do                 when (i `rem` 5 == 0) $ void $ sendMessage px me (toDyn i)-                forever $ void (receiveMessage px)+                foreverCheap $ void (receiveMessage px)               )               [0 .. n]             oks <- replicateM@@ -383,7 +423,7 @@                       $ do                           tid <- lift $ myThreadId                           lift $ atomically $ putTMVar tidVar tid-                          forever busyEffect+                          foreverCheap busyEffect                     atomically $ putTMVar schedulerDoneVar ()                   tid <- atomically $ takeTMVar tidVar                   threadDelay 1000@@ -408,7 +448,7 @@           [ testCase ("a child process, busy with " ++ busyWith)             $ applySchedulerFactory schedulerFactory             $ do-                void $ spawn $ forever busyEffect+                void $ spawn $ foreverCheap busyEffect                 lift (threadDelay 10000)           | (busyWith, busyEffect) <-             [ ("receiving", void (send (ReceiveMessage @r)))@@ -432,7 +472,7 @@               )             $ scheduleAndAssert schedulerFactory             $ \assertEff -> do-                p1 <- spawn $ forever busyEffect+                p1 <- spawn $ foreverCheap busyEffect                 lift (threadDelay 1000)                 void $ spawn $ do                   lift (threadDelay 1000)
test/SingleThreadedScheduler.hs view
@@ -1,15 +1,40 @@ module SingleThreadedScheduler where +import           Control.Eff.Loop import           Control.Eff.Concurrent.Process import           Control.Eff.Concurrent.Process.SingleThreadedScheduler                                                as Scheduler-import           Control.Monad                  ( void-                                                )+import           Control.Monad import           Test.Tasty import           Test.Tasty.HUnit import           Data.Dynamic import           Common++test_pureScheduler :: TestTree+test_pureScheduler = setTravisTestOptions $ testGroup+    "Pure Scheduler"+    [ testCase "two processes, each calculate and report back to main process"+      $   Right (42 :: Int)+      @=? Scheduler.schedulePure+              (do+                  adderChild <- spawn $ do+                      (from, arg1, arg2) <- receiveMessageAs SP+                      sendMessageAs SP from ((arg1 + arg2) :: Int)+                      foreverCheap $ void $ receiveMessage SP++                  multChild <- spawn $ do+                      (from, arg1, arg2) <- receiveMessageAs SP+                      sendMessageAs SP from ((arg1 * arg2) :: Int)++                  me <- self SP+                  sendMessageAs SP adderChild (me, 3 :: Int, 4 :: Int)+                  x <- receiveMessageAs @Int SP+                  sendMessageAs SP multChild (me, x, 6 :: Int)+                  receiveMessageAs @Int SP+              )+    ]+  test_mainProcessSpawnsAChildAndExitsNormally :: TestTree test_mainProcessSpawnsAChildAndExitsNormally = setTravisTestOptions