polysemy-conc 0.2.0.0 → 0.3.0.0
raw patch · 34 files changed
+637/−532 lines, 34 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
- Polysemy.Conc.Critical: interpretCritical :: Member (Final IO) r => InterpreterFor Critical r
- Polysemy.Conc.Critical: interpretCriticalNull :: InterpreterFor Critical r
- Polysemy.Conc.Data.Critical: [Catch] :: Exception e => m a -> Critical m (Either e a)
- Polysemy.Conc.Data.Critical: catchAs :: forall e a r. Exception e => Member Critical r => a -> Sem r a -> Sem r a
- Polysemy.Conc.Data.Critical: data Critical :: Effect
- Polysemy.Conc.Data.Critical: run :: Member Critical r => Sem r a -> Sem r (Either SomeException a)
- Polysemy.Conc.Data.Critical: runAs :: Member Critical r => a -> Sem r a -> Sem r a
- Polysemy.Conc.Data.Interrupt: [Interrupted] :: Interrupt m Bool
- Polysemy.Conc.Data.Interrupt: [KillOnQuit] :: Text -> m a -> Interrupt m (Maybe a)
- Polysemy.Conc.Data.Interrupt: [Quit] :: Interrupt m ()
- Polysemy.Conc.Data.Interrupt: [Register] :: Text -> IO () -> Interrupt m ()
- Polysemy.Conc.Data.Interrupt: [Unregister] :: Text -> Interrupt m ()
- Polysemy.Conc.Data.Interrupt: [WaitQuit] :: Interrupt m ()
- Polysemy.Conc.Data.Interrupt: data Interrupt :: Effect
- Polysemy.Conc.Data.Interrupt: killOnQuit_ :: Member Interrupt r => Text -> Sem r a -> Sem r ()
- Polysemy.Conc.Data.Queue: [Close] :: Queue d m ()
- Polysemy.Conc.Data.Queue: [Closed] :: Queue d m Bool
- Polysemy.Conc.Data.Queue: [Peek] :: Queue d m (QueueResult d)
- Polysemy.Conc.Data.Queue: [ReadTimeout] :: TimeUnit t => t -> Queue d m (QueueResult d)
- Polysemy.Conc.Data.Queue: [Read] :: Queue d m (QueueResult d)
- Polysemy.Conc.Data.Queue: [TryPeek] :: Queue d m (QueueResult d)
- Polysemy.Conc.Data.Queue: [TryRead] :: Queue d m (QueueResult d)
- Polysemy.Conc.Data.Queue: [TryWrite] :: d -> Queue d m (QueueResult ())
- Polysemy.Conc.Data.Queue: [WriteTimeout] :: TimeUnit t => t -> d -> Queue d m (QueueResult ())
- Polysemy.Conc.Data.Queue: [Write] :: d -> Queue d m ()
- Polysemy.Conc.Data.Queue: data Queue d :: Effect
- Polysemy.Conc.Data.Race: [Race] :: m a -> m b -> Race m (Either a b)
- Polysemy.Conc.Data.Race: [Timeout] :: TimeUnit u => a -> u -> m b -> Race m (Either a b)
- Polysemy.Conc.Data.Race: data Race :: Effect
- Polysemy.Conc.Data.Race: race :: forall a b r. Member Race r => Sem r a -> Sem r b -> Sem r (Either a b)
- Polysemy.Conc.Data.Race: timeout :: forall a b u r. TimeUnit u => Member Race r => a -> u -> Sem r b -> Sem r (Either a b)
- Polysemy.Conc.Effect.Scoped: runScoped :: forall resource effect r. (forall x. (resource -> Sem r x) -> Sem r x) -> (resource -> InterpreterFor effect r) -> InterpreterFor (Scoped resource effect) r
- Polysemy.Conc.Effect.Scoped: runScopedAs :: forall resource effect r. Sem r resource -> (resource -> InterpreterFor effect r) -> InterpreterFor (Scoped resource effect) r
- Polysemy.Conc.Interrupt: interpretInterrupt :: Members [Critical, Race, Async, Embed IO] r => InterpreterFor Interrupt r
- Polysemy.Conc.Race: interpretRace :: Member (Final IO) r => InterpreterFor Race r
+ Polysemy.Conc: timeoutAs :: TimeUnit u => Member Race r => a -> u -> Sem r b -> Sem r (Either a b)
+ Polysemy.Conc: timeoutAs_ :: TimeUnit u => Member Race r => a -> u -> Sem r a -> Sem r a
+ Polysemy.Conc: timeoutMaybe :: TimeUnit u => Member Race r => u -> Sem r a -> Sem r (Maybe a)
+ Polysemy.Conc.Effect.Critical: [Catch] :: Exception e => m a -> Critical m (Either e a)
+ Polysemy.Conc.Effect.Critical: catchAs :: forall e a r. Exception e => Member Critical r => a -> Sem r a -> Sem r a
+ Polysemy.Conc.Effect.Critical: data Critical :: Effect
+ Polysemy.Conc.Effect.Critical: run :: Member Critical r => Sem r a -> Sem r (Either SomeException a)
+ Polysemy.Conc.Effect.Critical: runAs :: Member Critical r => a -> Sem r a -> Sem r a
+ Polysemy.Conc.Effect.Interrupt: [Interrupted] :: Interrupt m Bool
+ Polysemy.Conc.Effect.Interrupt: [KillOnQuit] :: Text -> m a -> Interrupt m (Maybe a)
+ Polysemy.Conc.Effect.Interrupt: [Quit] :: Interrupt m ()
+ Polysemy.Conc.Effect.Interrupt: [Register] :: Text -> IO () -> Interrupt m ()
+ Polysemy.Conc.Effect.Interrupt: [Unregister] :: Text -> Interrupt m ()
+ Polysemy.Conc.Effect.Interrupt: [WaitQuit] :: Interrupt m ()
+ Polysemy.Conc.Effect.Interrupt: data Interrupt :: Effect
+ Polysemy.Conc.Effect.Interrupt: killOnQuit_ :: Member Interrupt r => Text -> Sem r a -> Sem r ()
+ Polysemy.Conc.Effect.Queue: [Close] :: Queue d m ()
+ Polysemy.Conc.Effect.Queue: [Closed] :: Queue d m Bool
+ Polysemy.Conc.Effect.Queue: [Peek] :: Queue d m (QueueResult d)
+ Polysemy.Conc.Effect.Queue: [ReadTimeout] :: TimeUnit t => t -> Queue d m (QueueResult d)
+ Polysemy.Conc.Effect.Queue: [Read] :: Queue d m (QueueResult d)
+ Polysemy.Conc.Effect.Queue: [TryPeek] :: Queue d m (QueueResult d)
+ Polysemy.Conc.Effect.Queue: [TryRead] :: Queue d m (QueueResult d)
+ Polysemy.Conc.Effect.Queue: [TryWrite] :: d -> Queue d m (QueueResult ())
+ Polysemy.Conc.Effect.Queue: [WriteTimeout] :: TimeUnit t => t -> d -> Queue d m (QueueResult ())
+ Polysemy.Conc.Effect.Queue: [Write] :: d -> Queue d m ()
+ Polysemy.Conc.Effect.Queue: data Queue d :: Effect
+ Polysemy.Conc.Effect.Race: [Race] :: m a -> m b -> Race m (Either a b)
+ Polysemy.Conc.Effect.Race: [Timeout] :: TimeUnit u => m a -> u -> m b -> Race m (Either a b)
+ Polysemy.Conc.Effect.Race: data Race :: Effect
+ Polysemy.Conc.Effect.Race: race :: forall a b r. Member Race r => Sem r a -> Sem r b -> Sem r (Either a b)
+ Polysemy.Conc.Effect.Race: timeout :: forall a b u r. TimeUnit u => Member Race r => Sem r a -> u -> Sem r b -> Sem r (Either a b)
+ Polysemy.Conc.Interpreter.Critical: interpretCritical :: Member (Final IO) r => InterpreterFor Critical r
+ Polysemy.Conc.Interpreter.Critical: interpretCriticalNull :: InterpreterFor Critical r
+ Polysemy.Conc.Interpreter.Interrupt: interpretInterrupt :: Members [Critical, Race, Async, Embed IO] r => InterpreterFor Interrupt r
+ Polysemy.Conc.Interpreter.Race: interpretRace :: Member (Final IO) r => InterpreterFor Race r
+ Polysemy.Conc.Interpreter.Scoped: runScoped :: forall resource effect r. (forall x. (resource -> Sem r x) -> Sem r x) -> (resource -> InterpreterFor effect r) -> InterpreterFor (Scoped resource effect) r
+ Polysemy.Conc.Interpreter.Scoped: runScopedAs :: forall resource effect r. Sem r resource -> (resource -> InterpreterFor effect r) -> InterpreterFor (Scoped resource effect) r
+ Polysemy.Conc.Queue: close :: forall d_asPL r_asXY. MemberWithError (Queue d_asPL) r_asXY => Sem r_asXY ()
+ Polysemy.Conc.Queue: closed :: forall d_asPJ r_asXX. MemberWithError (Queue d_asPJ) r_asXX => Sem r_asXX Bool
+ Polysemy.Conc.Queue: data Queue d :: Effect
+ Polysemy.Conc.Queue: peek :: forall d_asPy r_asXO. MemberWithError (Queue d_asPy) r_asXO => Sem r_asXO (QueueResult d_asPy)
+ Polysemy.Conc.Queue: read :: forall d_asPr r_asXK. MemberWithError (Queue d_asPr) r_asXK => Sem r_asXK (QueueResult d_asPr)
+ Polysemy.Conc.Queue: readTimeout :: forall d_asPw r_asXM t_XsPw. (MemberWithError (Queue d_asPw) r_asXM, TimeUnit t_XsPw) => t_XsPw -> Sem r_asXM (QueueResult d_asPw)
+ Polysemy.Conc.Queue: tryPeek :: forall d_asPA r_asXP. MemberWithError (Queue d_asPA) r_asXP => Sem r_asXP (QueueResult d_asPA)
+ Polysemy.Conc.Queue: tryRead :: forall d_asPt r_asXL. MemberWithError (Queue d_asPt) r_asXL => Sem r_asXL (QueueResult d_asPt)
+ Polysemy.Conc.Queue: tryWrite :: forall d_asPE r_asXS. MemberWithError (Queue d_asPE) r_asXS => d_asPE -> Sem r_asXS (QueueResult ())
+ Polysemy.Conc.Queue: write :: forall d_asPC r_asXQ. MemberWithError (Queue d_asPC) r_asXQ => d_asPC -> Sem r_asXQ ()
+ Polysemy.Conc.Queue: writeTimeout :: forall d_asPH r_asXU t_XsPH. (MemberWithError (Queue d_asPH) r_asXU, TimeUnit t_XsPH) => t_XsPH -> d_asPH -> Sem r_asXU (QueueResult ())
+ Polysemy.Conc.Race: timeoutAs :: TimeUnit u => Member Race r => a -> u -> Sem r b -> Sem r (Either a b)
+ Polysemy.Conc.Race: timeoutAs_ :: TimeUnit u => Member Race r => a -> u -> Sem r a -> Sem r a
+ Polysemy.Conc.Race: timeoutMaybe :: TimeUnit u => Member Race r => u -> Sem r a -> Sem r (Maybe a)
+ Polysemy.Conc.Sync: block :: forall d_agY6 r_ahpX. MemberWithError (Sync d_agY6) r_ahpX => Sem r_ahpX d_agY6
+ Polysemy.Conc.Sync: data Sync d :: Effect
+ Polysemy.Conc.Sync: data SyncResources a
+ Polysemy.Conc.Sync: empty :: forall d_ah4A r_ahqg. MemberWithError (Sync d_ah4A) r_ahqg => Sem r_ahqg Bool
+ Polysemy.Conc.Sync: putBlock :: forall d_ah4t r_ahq9. MemberWithError (Sync d_ah4t) r_ahq9 => d_ah4t -> Sem r_ahq9 ()
+ Polysemy.Conc.Sync: putTry :: forall d_ah4y r_ahqe. MemberWithError (Sync d_ah4y) r_ahqe => d_ah4y -> Sem r_ahqe Bool
+ Polysemy.Conc.Sync: putWait :: forall d_ah4w r_ahqb u_Xh4w. (MemberWithError (Sync d_ah4w) r_ahqb, TimeUnit u_Xh4w) => u_Xh4w -> d_ah4w -> Sem r_ahqb Bool
+ Polysemy.Conc.Sync: readBlock :: forall d_ah4m r_ahq5. MemberWithError (Sync d_ah4m) r_ahq5 => Sem r_ahq5 d_ah4m
+ Polysemy.Conc.Sync: readTry :: forall d_ah4r r_ahq8. MemberWithError (Sync d_ah4r) r_ahq8 => Sem r_ahq8 (Maybe d_ah4r)
+ Polysemy.Conc.Sync: readWait :: forall d_ah4p r_ahq6 u_Xh4p. (MemberWithError (Sync d_ah4p) r_ahq6, TimeUnit u_Xh4p) => u_Xh4p -> Sem r_ahq6 (Maybe d_ah4p)
+ Polysemy.Conc.Sync: takeBlock :: forall d_ah4f r_ahq1. MemberWithError (Sync d_ah4f) r_ahq1 => Sem r_ahq1 d_ah4f
+ Polysemy.Conc.Sync: takeTry :: forall d_ah4k r_ahq4. MemberWithError (Sync d_ah4k) r_ahq4 => Sem r_ahq4 (Maybe d_ah4k)
+ Polysemy.Conc.Sync: takeWait :: forall d_ah4i r_ahq2 u_Xh4i. (MemberWithError (Sync d_ah4i) r_ahq2, TimeUnit u_Xh4i) => u_Xh4i -> Sem r_ahq2 (Maybe d_ah4i)
+ Polysemy.Conc.Sync: try :: forall d_ah4d r_ahq0. MemberWithError (Sync d_ah4d) r_ahq0 => Sem r_ahq0 (Maybe d_ah4d)
+ Polysemy.Conc.Sync: type ScopedSync res a = Scoped (SyncResources res) (Sync a)
+ Polysemy.Conc.Sync: wait :: forall d_agY9 r_ahpY u_XgY9. (MemberWithError (Sync d_agY9) r_ahpY, TimeUnit u_XgY9) => u_XgY9 -> Sem r_ahpY (Maybe d_agY9)
- Polysemy.Conc: consume :: forall e_ajQW r_ajRB. MemberWithError (Consume e_ajQW) r_ajRB => Sem r_ajRB e_ajQW
+ Polysemy.Conc: consume :: forall e_amwE r_amxj. MemberWithError (Consume e_amwE) r_amxj => Sem r_amxj e_amwE
- Polysemy.Conc: publish :: forall token_ajRL e_ajRK r_akEN. MemberWithError (Events token_ajRL e_ajRK) r_akEN => e_ajRK -> Sem r_akEN ()
+ Polysemy.Conc: publish :: forall token_amxt e_amxs r_ank4. MemberWithError (Events token_amxt e_amxs) r_ank4 => e_amxs -> Sem r_ank4 ()
- Polysemy.Conc: timeout :: forall a b u r. TimeUnit u => Member Race r => a -> u -> Sem r b -> Sem r (Either a b)
+ Polysemy.Conc: timeout :: forall a b u r. TimeUnit u => Member Race r => Sem r a -> u -> Sem r b -> Sem r (Either a b)
- Polysemy.Conc: timeout_ :: TimeUnit u => Member Race r => a -> u -> Sem r a -> Sem r a
+ Polysemy.Conc: timeout_ :: TimeUnit u => Member Race r => Sem r a -> u -> Sem r a -> Sem r a
- Polysemy.Conc.Race: timeout_ :: TimeUnit u => Member Race r => a -> u -> Sem r a -> Sem r a
+ Polysemy.Conc.Race: timeout_ :: TimeUnit u => Member Race r => Sem r a -> u -> Sem r a -> Sem r a
Files
- changelog.md +7/−0
- lib/Polysemy/Conc.hs +11/−7
- lib/Polysemy/Conc/Async.hs +1/−1
- lib/Polysemy/Conc/Critical.hs +0/−32
- lib/Polysemy/Conc/Data/Critical.hs +0/−41
- lib/Polysemy/Conc/Data/Interrupt.hs +0/−44
- lib/Polysemy/Conc/Data/Queue.hs +0/−46
- lib/Polysemy/Conc/Data/Race.hs +0/−35
- lib/Polysemy/Conc/Effect/Critical.hs +41/−0
- lib/Polysemy/Conc/Effect/Interrupt.hs +44/−0
- lib/Polysemy/Conc/Effect/Queue.hs +46/−0
- lib/Polysemy/Conc/Effect/Race.hs +35/−0
- lib/Polysemy/Conc/Effect/Scoped.hs +1/−44
- lib/Polysemy/Conc/Interpreter/Critical.hs +32/−0
- lib/Polysemy/Conc/Interpreter/Events.hs +3/−2
- lib/Polysemy/Conc/Interpreter/Interrupt.hs +195/−0
- lib/Polysemy/Conc/Interpreter/Queue/Pure.hs +2/−2
- lib/Polysemy/Conc/Interpreter/Queue/TB.hs +3/−3
- lib/Polysemy/Conc/Interpreter/Queue/TBM.hs +3/−3
- lib/Polysemy/Conc/Interpreter/Race.hs +35/−0
- lib/Polysemy/Conc/Interpreter/Scoped.hs +51/−0
- lib/Polysemy/Conc/Interpreter/Sync.hs +9/−9
- lib/Polysemy/Conc/Interrupt.hs +0/−195
- lib/Polysemy/Conc/Queue.hs +18/−3
- lib/Polysemy/Conc/Queue/Timeout.hs +3/−3
- lib/Polysemy/Conc/Race.hs +43/−34
- lib/Polysemy/Conc/Retry.hs +3/−3
- lib/Polysemy/Conc/Sync.hs +22/−2
- polysemy-conc.cabal +9/−7
- test/Main.hs +7/−3
- test/Polysemy/Conc/Test/EventsTest.hs +1/−1
- test/Polysemy/Conc/Test/InterruptTest.hs +6/−6
- test/Polysemy/Conc/Test/QueueTest.hs +4/−4
- test/Polysemy/Conc/Test/SyncTest.hs +2/−2
changelog.md view
@@ -1,5 +1,12 @@ # Unreleased +# 0.3.0.0++* Change `Race.timeout` to take a `Sem` for the fallback instead of a pure value.+* Export all `Queue` constructors from `Polysemy.Conc.Queue`.+* Export all `Sync` constructors from `Polysemy.Conc.Sync`.+* Move all interpreters to `Polysemy.Conc.Interpreter`.+ # 0.2.0.0 * Add `read*` constructors for `Sync` * Add `subscribeWhile`, a combinator that consumes events until a condition is met
lib/Polysemy/Conc.hs view
@@ -40,7 +40,10 @@ race_, timeout, timeout_,+ timeoutAs,+ timeoutAs_, timeoutU,+ timeoutMaybe, retrying, retryingWithError, @@ -86,16 +89,17 @@ import Polysemy.Conc.Async (withAsync, withAsyncBlock, withAsync_) import Polysemy.Conc.AtomicState (interpretAtomic)-import Polysemy.Conc.Critical (interpretCritical, interpretCriticalNull)-import Polysemy.Conc.Data.Critical (Critical)-import Polysemy.Conc.Data.Interrupt (Interrupt)-import Polysemy.Conc.Data.Queue (Queue) import Polysemy.Conc.Data.QueueResult (QueueResult)-import Polysemy.Conc.Data.Race (Race, race, timeout)+import Polysemy.Conc.Effect.Critical (Critical) import Polysemy.Conc.Effect.Events (EventToken, Events, consume, publish, subscribe)+import Polysemy.Conc.Effect.Interrupt (Interrupt)+import Polysemy.Conc.Effect.Queue (Queue)+import Polysemy.Conc.Effect.Race (Race, race, timeout) import Polysemy.Conc.Effect.Sync (ScopedSync, Sync) import Polysemy.Conc.Events (subscribeLoop, subscribeWhile)+import Polysemy.Conc.Interpreter.Critical (interpretCritical, interpretCriticalNull) import Polysemy.Conc.Interpreter.Events (ChanConsumer, ChanEvents, EventChan, EventConsumer, interpretEventsChan)+import Polysemy.Conc.Interpreter.Interrupt (interpretInterrupt) import Polysemy.Conc.Interpreter.Queue.Pure ( interpretQueueListReadOnlyAtomic, interpretQueueListReadOnlyAtomicWith,@@ -104,11 +108,11 @@ ) import Polysemy.Conc.Interpreter.Queue.TB (interpretQueueTB) import Polysemy.Conc.Interpreter.Queue.TBM (interpretQueueTBM)+import Polysemy.Conc.Interpreter.Race (interpretRace) import Polysemy.Conc.Interpreter.Sync (interpretScopedSync, interpretScopedSyncAs, interpretSync, interpretSyncAs)-import Polysemy.Conc.Interrupt (interpretInterrupt) import Polysemy.Conc.Queue (loop, loopOr) import Polysemy.Conc.Queue.Result (resultToMaybe)-import Polysemy.Conc.Race (interpretRace, race_, timeoutU, timeout_)+import Polysemy.Conc.Race (race_, timeoutAs, timeoutAs_, timeoutMaybe, timeoutU, timeout_) import Polysemy.Conc.Retry (retrying, retryingWithError) import Polysemy.Conc.Sync (withSync)
lib/Polysemy/Conc/Async.hs view
@@ -5,7 +5,7 @@ import Polysemy.Resource (Resource, bracket) import Polysemy.Time (MilliSeconds (MilliSeconds), TimeUnit) -import Polysemy.Conc.Data.Race (Race)+import Polysemy.Conc.Effect.Race (Race) import qualified Polysemy.Conc.Race as Race -- |Run the first action asynchronously while the second action executes, then cancel the first action.
− lib/Polysemy/Conc/Critical.hs
@@ -1,32 +0,0 @@--- |Description: Critical interpreters-module Polysemy.Conc.Critical where--import qualified Control.Exception as Exception-import Polysemy.Final (getInitialStateS, interpretFinal, runS)--import Polysemy.Conc.Data.Critical (Critical(..))-import Polysemy (interpretH, runT)---- |Interpret 'Critical' in terms of 'Final' 'IO'.-interpretCritical ::- Member (Final IO) r =>- InterpreterFor Critical r-interpretCritical =- interpretFinal @IO \case- Catch ma -> do- s <- getInitialStateS- o <- runS ma- pure (run o s)- where- run ma' s =- Exception.catch (fmap Right <$> ma') \ se -> pure (Left se <$ s)-{-# inline interpretCritical #-}---- |Interpret 'Critical' by doing nothing.-interpretCriticalNull ::- InterpreterFor Critical r-interpretCriticalNull =- interpretH \case- Catch ma ->- fmap (fmap Right) . raise . interpretCriticalNull =<< runT ma-{-# inline interpretCriticalNull #-}
− lib/Polysemy/Conc/Data/Critical.hs
@@ -1,41 +0,0 @@-{-# options_haddock prune #-}--- |Description: Critical effect-module Polysemy.Conc.Data.Critical where---- |An effect that catches exceptions.------ Provides the exact functionality of `Polysemy.Error.fromExceptionSem`, but pushes the dependency on @Final IO@ to the--- interpreter, and makes it optional.-data Critical :: Effect where- -- |Catch all exceptions of type @e@ in this computation.- Catch :: Exception e => m a -> Critical m (Either e a)--makeSem ''Critical---- |Catch exceptions of type @e@ and return a fallback value.-catchAs ::- ∀ e a r .- Exception e =>- Member Critical r =>- a ->- Sem r a ->- Sem r a-catchAs a =- fmap (fromRight a) . catch @_ @e---- |Convenience overload for 'SomeException'.-run ::- Member Critical r =>- Sem r a ->- Sem r (Either SomeException a)-run =- catch---- |Convenience overload for 'SomeException'.-runAs ::- Member Critical r =>- a ->- Sem r a ->- Sem r a-runAs a =- fmap (fromRight a) . run
− lib/Polysemy/Conc/Data/Interrupt.hs
@@ -1,44 +0,0 @@-{-# options_haddock prune #-}--- |Description: Interrupt effect-module Polysemy.Conc.Data.Interrupt where---- |The interrupt handler effect allows three kinds of interaction for interrupt signals:------ - Execute a callback when a signal is received--- - Block a thread until a signal is received--- - Kill a thread when a signal is received------ For documentation on the constructors, see the module "Polysemy.Conc.Data.Interrupt".------ @--- import qualified Polysemy.Conc.Data.Interrupt as Interrupt------ prog = do--- Interrupt.register "task 1" (putStrLn "interrupted")--- Interrupt.killOnQuit $ forever do--- doSomeWork--- @-data Interrupt :: Effect where- -- |Add a computation to be executed on interrupt, using the first argument as a key.- Register :: Text -> IO () -> Interrupt m ()- -- |Remove the previously added handler with the given key.- Unregister :: Text -> Interrupt m ()- -- |Manually trigger the interrupt.- Quit :: Interrupt m ()- -- |Block until an interrupt is triggered.- WaitQuit :: Interrupt m ()- -- |Indicate whether an interrupt was triggered.- Interrupted :: Interrupt m Bool- -- |Execute a computation, waiting for it to finish, killing its thread on interrupt.- KillOnQuit :: Text -> m a -> Interrupt m (Maybe a)--makeSem ''Interrupt---- |Variant of 'killOnQuit' that returns @()@.-killOnQuit_ ::- Member Interrupt r =>- Text ->- Sem r a ->- Sem r ()-killOnQuit_ desc ma =- void (killOnQuit desc ma)
− lib/Polysemy/Conc/Data/Queue.hs
@@ -1,46 +0,0 @@-{-# options_haddock prune #-}--- |Description: Queue effect-module Polysemy.Conc.Data.Queue where--import Polysemy.Time (TimeUnit)-import Polysemy.Conc.Data.QueueResult (QueueResult)---- |Abstracts queues like 'Control.Concurrent.STM.TBQueue'.------ For documentation on the constructors, see the module "Polysemy.Conc.Data.Queue".------ @--- import Polysemy.Conc (Queue, QueueResult)--- import Polysemy.Conc.Data.Queue as Queue------ prog :: Member (Queue Int) r => Sem r (QueueResult Int)--- prog = do--- Queue.write 5--- Queue.write 10--- Queue.read >>= \\case--- QueueResult.Success i -> fmap (i +) \<$> Queue.read--- r -> pure r--- @-data Queue d :: Effect where- -- |Read an element from the queue, blocking until one is available.- Read :: Queue d m (QueueResult d)- -- |Read an element from the queue, immediately returning if none is available.- TryRead :: Queue d m (QueueResult d)- -- |Read an element from the queue, blocking until one is available or the timeout expires.- ReadTimeout :: TimeUnit t => t -> Queue d m (QueueResult d)- -- |Read an element, leaving it in the queue, blocking until one is available.- Peek :: Queue d m (QueueResult d)- -- |Read an element, leaving it in the queue, immediately returning if none is available.- TryPeek :: Queue d m (QueueResult d)- -- |Write an element to the queue, blocking until a slot is available.- Write :: d -> Queue d m ()- -- |Write an element to the queue, immediately returning if no slot is available.- TryWrite :: d -> Queue d m (QueueResult ())- -- |Write an element to the queue, blocking until a slot is available or the timeout expires.- WriteTimeout :: TimeUnit t => t -> d -> Queue d m (QueueResult ())- -- |Indicate whether the queue is closed.- Closed :: Queue d m Bool- -- |Close the queue.- Close :: Queue d m ()--makeSem ''Queue
− lib/Polysemy/Conc/Data/Race.hs
@@ -1,35 +0,0 @@-{-# options_haddock prune #-}--- |Description: Race effect-module Polysemy.Conc.Data.Race where--import Polysemy (makeSem_)--import Polysemy.Time (TimeUnit)---- |Abstract the concept of running two programs concurrently, aborting the other when one terminates.--- 'Timeout' is a simpler variant, where one thread just sleeps for a given interval.-data Race :: Effect where- -- |Run both programs concurrently, returning the result of the faster one.- Race :: m a -> m b -> Race m (Either a b)- -- |Return the fallback value if the given program doesn't finish within the specified interval.- Timeout :: TimeUnit u => a -> u -> m b -> Race m (Either a b)--makeSem_ ''Race---- |Run both programs concurrently, returning the result of the faster one.-race ::- ∀ a b r .- Member Race r =>- Sem r a ->- Sem r b ->- Sem r (Either a b)---- |Return the fallback value if the given program doesn't finish within the specified interval.-timeout ::- ∀ a b u r .- TimeUnit u =>- Member Race r =>- a ->- u ->- Sem r b ->- Sem r (Either a b)
+ lib/Polysemy/Conc/Effect/Critical.hs view
@@ -0,0 +1,41 @@+{-# options_haddock prune #-}+-- |Description: Critical effect+module Polysemy.Conc.Effect.Critical where++-- |An effect that catches exceptions.+--+-- Provides the exact functionality of `Polysemy.Error.fromExceptionSem`, but pushes the dependency on @Final IO@ to the+-- interpreter, and makes it optional.+data Critical :: Effect where+ -- |Catch all exceptions of type @e@ in this computation.+ Catch :: Exception e => m a -> Critical m (Either e a)++makeSem ''Critical++-- |Catch exceptions of type @e@ and return a fallback value.+catchAs ::+ ∀ e a r .+ Exception e =>+ Member Critical r =>+ a ->+ Sem r a ->+ Sem r a+catchAs a =+ fmap (fromRight a) . catch @_ @e++-- |Convenience overload for 'SomeException'.+run ::+ Member Critical r =>+ Sem r a ->+ Sem r (Either SomeException a)+run =+ catch++-- |Convenience overload for 'SomeException'.+runAs ::+ Member Critical r =>+ a ->+ Sem r a ->+ Sem r a+runAs a =+ fmap (fromRight a) . run
+ lib/Polysemy/Conc/Effect/Interrupt.hs view
@@ -0,0 +1,44 @@+{-# options_haddock prune #-}+-- |Description: Interrupt effect+module Polysemy.Conc.Effect.Interrupt where++-- |The interrupt handler effect allows three kinds of interaction for interrupt signals:+--+-- - Execute a callback when a signal is received+-- - Block a thread until a signal is received+-- - Kill a thread when a signal is received+--+-- For documentation on the constructors, see the module "Polysemy.Conc.Effect.Interrupt".+--+-- @+-- import qualified Polysemy.Conc.Effect.Interrupt as Interrupt+--+-- prog = do+-- Interrupt.register "task 1" (putStrLn "interrupted")+-- Interrupt.killOnQuit $ forever do+-- doSomeWork+-- @+data Interrupt :: Effect where+ -- |Add a computation to be executed on interrupt, using the first argument as a key.+ Register :: Text -> IO () -> Interrupt m ()+ -- |Remove the previously added handler with the given key.+ Unregister :: Text -> Interrupt m ()+ -- |Manually trigger the interrupt.+ Quit :: Interrupt m ()+ -- |Block until an interrupt is triggered.+ WaitQuit :: Interrupt m ()+ -- |Indicate whether an interrupt was triggered.+ Interrupted :: Interrupt m Bool+ -- |Execute a computation, waiting for it to finish, killing its thread on interrupt.+ KillOnQuit :: Text -> m a -> Interrupt m (Maybe a)++makeSem ''Interrupt++-- |Variant of 'killOnQuit' that returns @()@.+killOnQuit_ ::+ Member Interrupt r =>+ Text ->+ Sem r a ->+ Sem r ()+killOnQuit_ desc ma =+ void (killOnQuit desc ma)
+ lib/Polysemy/Conc/Effect/Queue.hs view
@@ -0,0 +1,46 @@+{-# options_haddock prune #-}+-- |Description: Queue effect+module Polysemy.Conc.Effect.Queue where++import Polysemy.Time (TimeUnit)+import Polysemy.Conc.Data.QueueResult (QueueResult)++-- |Abstracts queues like 'Control.Concurrent.STM.TBQueue'.+--+-- For documentation on the constructors, see the module "Polysemy.Conc.Data.Queue".+--+-- @+-- import Polysemy.Conc (Queue, QueueResult)+-- import Polysemy.Conc.Effect.Queue as Queue+--+-- prog :: Member (Queue Int) r => Sem r (QueueResult Int)+-- prog = do+-- Queue.write 5+-- Queue.write 10+-- Queue.read >>= \\case+-- QueueResult.Success i -> fmap (i +) \<$> Queue.read+-- r -> pure r+-- @+data Queue d :: Effect where+ -- |Read an element from the queue, blocking until one is available.+ Read :: Queue d m (QueueResult d)+ -- |Read an element from the queue, immediately returning if none is available.+ TryRead :: Queue d m (QueueResult d)+ -- |Read an element from the queue, blocking until one is available or the timeout expires.+ ReadTimeout :: TimeUnit t => t -> Queue d m (QueueResult d)+ -- |Read an element, leaving it in the queue, blocking until one is available.+ Peek :: Queue d m (QueueResult d)+ -- |Read an element, leaving it in the queue, immediately returning if none is available.+ TryPeek :: Queue d m (QueueResult d)+ -- |Write an element to the queue, blocking until a slot is available.+ Write :: d -> Queue d m ()+ -- |Write an element to the queue, immediately returning if no slot is available.+ TryWrite :: d -> Queue d m (QueueResult ())+ -- |Write an element to the queue, blocking until a slot is available or the timeout expires.+ WriteTimeout :: TimeUnit t => t -> d -> Queue d m (QueueResult ())+ -- |Indicate whether the queue is closed.+ Closed :: Queue d m Bool+ -- |Close the queue.+ Close :: Queue d m ()++makeSem ''Queue
+ lib/Polysemy/Conc/Effect/Race.hs view
@@ -0,0 +1,35 @@+{-# options_haddock prune #-}+-- |Description: Race effect+module Polysemy.Conc.Effect.Race where++import Polysemy (makeSem_)++import Polysemy.Time (TimeUnit)++-- |Abstract the concept of running two programs concurrently, aborting the other when one terminates.+-- 'Timeout' is a simpler variant, where one thread just sleeps for a given interval.+data Race :: Effect where+ -- |Run both programs concurrently, returning the result of the faster one.+ Race :: m a -> m b -> Race m (Either a b)+ -- |Run the fallback action if the given program doesn't finish within the specified interval.+ Timeout :: TimeUnit u => m a -> u -> m b -> Race m (Either a b)++makeSem_ ''Race++-- |Run both programs concurrently, returning the result of the faster one.+race ::+ ∀ a b r .+ Member Race r =>+ Sem r a ->+ Sem r b ->+ Sem r (Either a b)++-- |Run the fallback action if the given program doesn't finish within the specified interval.+timeout ::+ ∀ a b u r .+ TimeUnit u =>+ Member Race r =>+ Sem r a ->+ u ->+ Sem r b ->+ Sem r (Either a b)
lib/Polysemy/Conc/Effect/Scoped.hs view
@@ -3,8 +3,7 @@ module Polysemy.Conc.Effect.Scoped where import Polysemy (transform)-import Polysemy.Internal (Sem (Sem, runSem), liftSem, send)-import Polysemy.Internal.Union (Weaving (Weaving), decomp, hoist, injWeaving)+import Polysemy.Internal (send) -- |@Scoped@ transforms a program so that @effect@ is associated with a @resource@ within that program. -- This requires the interpreter for @effect@ to be parameterized by @resource@ and constructed for every program using@@ -22,15 +21,6 @@ Run :: ∀ resource effect m a . resource -> effect m a -> Scoped resource effect m a InScope :: ∀ resource effect m a . (resource -> m a) -> Scoped resource effect m a -interpretH' ::- ∀ e r .- (∀ x . Weaving e (Sem (e : r)) x -> Sem r x) ->- InterpreterFor e r-interpretH' h sem =- Sem \ k -> runSem sem $ decomp >>> \case- Right wav -> runSem (h wav) k- Left g -> k $ hoist (interpretH' h) g- -- |Constructor for 'Scoped', taking a nested program and transforming all instances of @effect@ to -- @Scoped resource effect@. scoped ::@@ -40,36 +30,3 @@ scoped main = send $ InScope @resource @effect \ resource -> transform @effect (Run resource) main---- |Interpreter for 'Scoped', taking a @resource@ allocation function and a parameterized interpreter for the plain--- @effect@.------ @withResource@ is a callback function, allowing the user to acquire the resource for each program from other effects.------ @scopedInterpreter@ is a regular interpreter that is called with the @resource@ argument produced by @scope@.--- /Note/: This function will be called for each action in the program, so if the interpreter allocates any resources,--- they will be scoped to a single action. Move them to @withResource@ instead.-runScoped ::- ∀ resource effect r .- (∀ x . (resource -> Sem r x) -> Sem r x) ->- (resource -> InterpreterFor effect r) ->- InterpreterFor (Scoped resource effect) r-runScoped withResource scopedInterpreter =- run- where- run :: InterpreterFor (Scoped resource effect) r- run =- interpretH' \ (Weaving effect s wv ex ins) -> case effect of- Run resource act ->- scopedInterpreter resource (liftSem $ injWeaving $ Weaving act s (raise . run . wv) ex ins)- InScope main ->- ex <$> withResource \ resource -> run (wv (main resource <$ s))---- |Variant of 'Scoped' in which the resource allocator is a plain action.-runScopedAs ::- ∀ resource effect r .- Sem r resource ->- (resource -> InterpreterFor effect r) ->- InterpreterFor (Scoped resource effect) r-runScopedAs resource =- runScoped \ f -> f =<< resource
+ lib/Polysemy/Conc/Interpreter/Critical.hs view
@@ -0,0 +1,32 @@+-- |Description: Critical interpreters+module Polysemy.Conc.Interpreter.Critical where++import qualified Control.Exception as Exception+import Polysemy (interpretH, runT)+import Polysemy.Final (getInitialStateS, interpretFinal, runS)++import Polysemy.Conc.Effect.Critical (Critical (..))++-- |Interpret 'Critical' in terms of 'Final' 'IO'.+interpretCritical ::+ Member (Final IO) r =>+ InterpreterFor Critical r+interpretCritical =+ interpretFinal @IO \case+ Catch ma -> do+ s <- getInitialStateS+ o <- runS ma+ pure (run o s)+ where+ run ma' s =+ Exception.catch (fmap Right <$> ma') \ se -> pure (Left se <$ s)+{-# inline interpretCritical #-}++-- |Interpret 'Critical' by doing nothing.+interpretCriticalNull ::+ InterpreterFor Critical r+interpretCriticalNull =+ interpretH \case+ Catch ma ->+ fmap (fmap Right) . raise . interpretCriticalNull =<< runT ma+{-# inline interpretCriticalNull #-}
lib/Polysemy/Conc/Interpreter/Events.hs view
@@ -8,10 +8,11 @@ import Polysemy.Resource (Resource) import Polysemy.Conc.Async (withAsync_)-import Polysemy.Conc.Data.Race (Race) import qualified Polysemy.Conc.Effect.Events as Events import Polysemy.Conc.Effect.Events (Consume, EventToken (EventToken), Events)-import Polysemy.Conc.Effect.Scoped (Scoped, runScopedAs)+import Polysemy.Conc.Effect.Race (Race)+import Polysemy.Conc.Effect.Scoped (Scoped)+import Polysemy.Conc.Interpreter.Scoped (runScopedAs) -- |Convenience alias for the default 'Events' that uses an 'OutChan'. type ChanEvents e =
+ lib/Polysemy/Conc/Interpreter/Interrupt.hs view
@@ -0,0 +1,195 @@+{-# options_haddock prune #-}+-- |Description: Interrupt interpreters+module Polysemy.Conc.Interpreter.Interrupt where++import qualified Control.Concurrent.Async as A+import Control.Concurrent.Async (AsyncCancelled)+import qualified Data.Map.Strict as Map+import qualified Data.Set as Set+import qualified Data.Text.IO as Text+import Polysemy (getInspectorT, inspect, interpretH, runT)+import Polysemy.Async (Async, async, await, cancel)+import Polysemy.AtomicState (runAtomicStateTVar)+import Polysemy.Internal.Tactics (liftT)+import Polysemy.Time (Seconds (Seconds))+import System.Posix.Signals (Handler (CatchInfoOnce, CatchOnce), SignalInfo, installHandler, keyboardSignal)++import qualified Polysemy.Conc.Effect.Critical as Critical+import Polysemy.Conc.Effect.Critical (Critical)+import Polysemy.Conc.Effect.Interrupt (Interrupt (..))+import Polysemy.Conc.Effect.Race (Race)+import qualified Polysemy.Conc.Effect.Sync as Sync+import Polysemy.Conc.Interpreter.Sync (interpretSync)+import Polysemy.Conc.Race (race_)++putErr ::+ Member (Embed IO) r =>+ Text ->+ Sem r ()+putErr =+ embed . Text.hPutStrLn stderr++data InterruptState =+ InterruptState {+ quit :: !(MVar ()),+ finished :: !(MVar ()),+ listeners :: !(Set Text),+ original :: !(SignalInfo -> IO ()),+ handlers :: !(Map Text (IO ()))+ }++modListeners :: (Set Text -> Set Text) -> InterruptState -> InterruptState+modListeners f s@InterruptState {listeners} =+ s {listeners = f listeners}++modHandlers :: (Map Text (IO ()) -> Map Text (IO ())) -> InterruptState -> InterruptState+modHandlers f s@InterruptState {handlers} =+ s {handlers = f handlers}++waitQuit ::+ Members [AtomicState InterruptState, Embed IO] r =>+ Sem r ()+waitQuit = do+ mv <- atomicGets quit+ readMVar mv++checkListeners ::+ Members [AtomicState InterruptState, Embed IO] r =>+ Sem r ()+checkListeners =+ whenM (atomicGets (Set.null . listeners)) do+ fin <- atomicGets finished+ void (tryPutMVar fin ())++onQuit ::+ Members [AtomicState InterruptState, Embed IO] r =>+ Text ->+ Sem r a ->+ Sem r a+onQuit name ma = do+ atomicModify' (modListeners (Set.insert name))+ waitQuit+ a <- ma+ atomicModify' (modListeners (Set.delete name))+ checkListeners+ pure a++processHandler ::+ Member (Embed IO) r =>+ Text ->+ IO () ->+ Sem r ()+processHandler name thunk = do+ putErr [qt|processing interrupt handler: #{name}|]+ embed thunk++execInterrupt ::+ Members [AtomicState InterruptState, Embed IO] r =>+ Sem r (SignalInfo -> Sem r ())+execInterrupt = do+ InterruptState quitSignal finishSignal _ orig _ <- atomicGet+ whenM (tryPutMVar quitSignal ()) do+ traverse_ (uncurry processHandler) . Map.toList =<< atomicGets handlers+ checkListeners+ takeMVar finishSignal+ embed . orig <$ putErr "interrupt handlers finished"++registerHandler ::+ Member (AtomicState InterruptState) r =>+ Text ->+ IO () ->+ Sem r ()+registerHandler name handler =+ atomicModify' (modHandlers (Map.insert name handler))++awaitOrKill ::+ Members [AtomicState InterruptState, Critical, Race, Async, Embed IO] r =>+ Text ->+ A.Async (Maybe a) ->+ Sem r (Maybe a)+awaitOrKill desc handle = do+ interpretSync @() do+ race_ (catchCritical (await handle)) kill+ where+ catchCritical =+ maybe waitKill (pure . Just) <=< Critical.catchAs @AsyncCancelled Nothing+ waitKill =+ Nothing <$ Sync.wait @() (Seconds 1)+ kill = do+ onQuit desc do+ putErr [qt|killing #{desc}|]+ cancel handle+ putErr [qt|killed #{desc}|]+ Sync.putBlock ()+ pure Nothing++interpretInterruptState ::+ Members [AtomicState InterruptState, Critical, Race, Async, Embed IO] r =>+ InterpreterFor Interrupt r+interpretInterruptState =+ interpretH \case+ Register name handler ->+ liftT (registerHandler name handler)+ Unregister name ->+ liftT $ atomicModify' \ s@InterruptState {handlers} -> s {handlers = Map.delete name handlers}+ WaitQuit ->+ liftT waitQuit+ Quit ->+ liftT do+ putErr "manual interrupt"+ void execInterrupt+ Interrupted ->+ liftT . fmap isJust . tryReadMVar =<< atomicGets quit+ KillOnQuit desc ma -> do+ maT <- runT ma+ ins <- getInspectorT+ handle <- raise (interpretInterruptState (async maT))+ result <- liftT (awaitOrKill desc handle)+ pure (join . fmap (inspect ins) <$> result)+{-# inline interpretInterruptState #-}++broadcastInterrupt ::+ Members [AtomicState InterruptState, Embed IO] r =>+ SignalInfo ->+ Sem r ()+broadcastInterrupt sig = do+ putErr "caught interrupt signal"+ orig <- execInterrupt+ orig sig++-- The original handler is either the default handler that kills all threads or a handler installed by an environment+-- like ghcid.+-- In the latter case, not calling it results in ghcid misbehaving.+-- To distinguish the two cases, the constructor used by the default is 'Catch', while a custom handler should usually+-- use 'CatchOnce', since you don't want to catch repeated occurences of SIGINT, as it will surely cause problems.+originalHandler :: Handler -> (SignalInfo -> IO ())+originalHandler (CatchOnce thunk) =+ (const thunk)+originalHandler (CatchInfoOnce thunk) =+ thunk+originalHandler _ =+ const pass+{-# inline originalHandler #-}++installSignalHandler ::+ TVar InterruptState ->+ IO Handler+installSignalHandler state =+ installHandler keyboardSignal (CatchInfoOnce handler) Nothing+ where+ handler sig =+ runFinal $ embedToFinal @IO $ runAtomicStateTVar state (broadcastInterrupt sig)++-- |Interpret 'Interrupt' by installing a signal handler.+interpretInterrupt ::+ Members [Critical, Race, Async, Embed IO] r =>+ InterpreterFor Interrupt r+interpretInterrupt sem = do+ quitMVar <- newEmptyMVar+ finishMVar <- newEmptyMVar+ state <- newTVarIO (InterruptState quitMVar finishMVar Set.empty (const pass) Map.empty)+ orig <- embed $ installSignalHandler state+ runAtomicStateTVar state do+ atomicModify' \ s -> s {original = originalHandler orig}+ interpretInterruptState $ raiseUnder sem+{-# inline interpretInterrupt #-}
lib/Polysemy/Conc/Interpreter/Queue/Pure.hs view
@@ -5,8 +5,8 @@ import Polysemy.State (State, evalState, get, gets, put) import Polysemy.Conc.AtomicState (interpretAtomic)-import qualified Polysemy.Conc.Data.Queue as Queue-import Polysemy.Conc.Data.Queue (Queue)+import qualified Polysemy.Conc.Effect.Queue as Queue+import Polysemy.Conc.Effect.Queue (Queue) import qualified Polysemy.Conc.Data.QueueResult as QueueResult import Polysemy.Conc.Data.QueueResult (QueueResult)
lib/Polysemy/Conc/Interpreter/Queue/TB.hs view
@@ -12,10 +12,10 @@ writeTBQueue, ) -import qualified Polysemy.Conc.Data.Queue as Queue-import Polysemy.Conc.Data.Queue (Queue)+import qualified Polysemy.Conc.Effect.Queue as Queue+import Polysemy.Conc.Effect.Queue (Queue) import qualified Polysemy.Conc.Data.QueueResult as QueueResult-import Polysemy.Conc.Data.Race (Race)+import Polysemy.Conc.Effect.Race (Race) import Polysemy.Conc.Queue.Result (naResult) import Polysemy.Conc.Queue.Timeout (withTimeout)
lib/Polysemy/Conc/Interpreter/Queue/TBM.hs view
@@ -15,9 +15,9 @@ ) import Polysemy.Resource (Resource, bracket) -import qualified Polysemy.Conc.Data.Queue as Queue-import Polysemy.Conc.Data.Queue (Queue)-import Polysemy.Conc.Data.Race (Race)+import qualified Polysemy.Conc.Effect.Queue as Queue+import Polysemy.Conc.Effect.Queue (Queue)+import Polysemy.Conc.Effect.Race (Race) import Polysemy.Conc.Queue.Result (closedBoolResult, closedNaResult, closedResult) import Polysemy.Conc.Queue.Timeout (withTimeout)
+ lib/Polysemy/Conc/Interpreter/Race.hs view
@@ -0,0 +1,35 @@+{-# options_haddock prune #-}+-- |Description: Race Interpreters+module Polysemy.Conc.Interpreter.Race where++import qualified Control.Concurrent.Async as Async+import Polysemy.Final (interpretFinal, runS)+import qualified Polysemy.Time as Time+import Polysemy.Time (MicroSeconds (MicroSeconds))+import qualified System.Timeout as System++import qualified Polysemy.Conc.Effect.Race as Race+import Polysemy.Conc.Effect.Race (Race)++biseqEither ::+ Functor f =>+ Either (f a) (f b) ->+ f (Either a b)+biseqEither =+ either (fmap Left) (fmap Right)+{-# inline biseqEither #-}++-- |Interpret 'Race' in terms of 'Async.race' and 'System.timeout'.+-- Since this has to pass higher-order thunks as 'IO' arguments, it is interpreted in terms of 'Final IO'.+interpretRace ::+ Member (Final IO) r =>+ InterpreterFor Race r+interpretRace =+ interpretFinal @IO \case+ Race.Race left right ->+ fmap (fmap biseqEither) . Async.race <$> runS left <*> runS right+ Race.Timeout ma (Time.convert -> MicroSeconds timeout) mb -> do+ maT <- runS ma+ mbT <- runS mb+ pure (maybe (fmap Left <$> maT) (pure . fmap Right) =<< System.timeout (fromIntegral timeout) mbT)+{-# inline interpretRace #-}
+ lib/Polysemy/Conc/Interpreter/Scoped.hs view
@@ -0,0 +1,51 @@+{-# options_haddock prune #-}+-- |Description: Scoped Interpreters, Internal+module Polysemy.Conc.Interpreter.Scoped where++import Polysemy.Internal (Sem (Sem, runSem), liftSem)+import Polysemy.Internal.Union (Weaving (Weaving), decomp, hoist, injWeaving)++import Polysemy.Conc.Effect.Scoped (Scoped (InScope, Run))++interpretH' ::+ ∀ e r .+ (∀ x . Weaving e (Sem (e : r)) x -> Sem r x) ->+ InterpreterFor e r+interpretH' h sem =+ Sem \ k -> runSem sem $ decomp >>> \case+ Right wav -> runSem (h wav) k+ Left g -> k $ hoist (interpretH' h) g+++-- |Interpreter for 'Scoped', taking a @resource@ allocation function and a parameterized interpreter for the plain+-- @effect@.+--+-- @withResource@ is a callback function, allowing the user to acquire the resource for each program from other effects.+--+-- @scopedInterpreter@ is a regular interpreter that is called with the @resource@ argument produced by @scope@.+-- /Note/: This function will be called for each action in the program, so if the interpreter allocates any resources,+-- they will be scoped to a single action. Move them to @withResource@ instead.+runScoped ::+ ∀ resource effect r .+ (∀ x . (resource -> Sem r x) -> Sem r x) ->+ (resource -> InterpreterFor effect r) ->+ InterpreterFor (Scoped resource effect) r+runScoped withResource scopedInterpreter =+ run+ where+ run :: InterpreterFor (Scoped resource effect) r+ run =+ interpretH' \ (Weaving effect s wv ex ins) -> case effect of+ Run resource act ->+ scopedInterpreter resource (liftSem $ injWeaving $ Weaving act s (raise . run . wv) ex ins)+ InScope main ->+ ex <$> withResource \ resource -> run (wv (main resource <$ s))++-- |Variant of 'Scoped' in which the resource allocator is a plain action.+runScopedAs ::+ ∀ resource effect r .+ Sem r resource ->+ (resource -> InterpreterFor effect r) ->+ InterpreterFor (Scoped resource effect) r+runScopedAs resource =+ runScoped \ f -> f =<< resource
lib/Polysemy/Conc/Interpreter/Sync.hs view
@@ -1,15 +1,15 @@--- |Description: Sync interpreters+-- |Description: Sync Interpreters module Polysemy.Conc.Interpreter.Sync where import Control.Concurrent (isEmptyMVar)+import Polysemy.Resource (Resource) -import qualified Polysemy.Conc.Data.Race as Race-import Polysemy.Conc.Data.Race (Race)-import Polysemy.Conc.Effect.Scoped (Scoped, runScopedAs)+import Polysemy.Conc.Effect.Race (Race)+import Polysemy.Conc.Effect.Scoped (Scoped) import qualified Polysemy.Conc.Effect.Sync as Sync import Polysemy.Conc.Effect.Sync (Sync, SyncResources (SyncResources), unSyncResources)+import Polysemy.Conc.Interpreter.Scoped (runScopedAs) import qualified Polysemy.Conc.Race as Race-import Polysemy.Resource (Resource) -- |Interpret 'Sync' with the provided 'MVar'. interpretSyncWith ::@@ -22,25 +22,25 @@ Sync.Block -> readMVar var Sync.Wait interval ->- rightToMaybe <$> Race.timeout () interval (readMVar var)+ rightToMaybe <$> Race.timeoutAs () interval (readMVar var) Sync.Try -> tryReadMVar var Sync.TakeBlock -> takeMVar var Sync.TakeWait interval ->- rightToMaybe <$> Race.timeout () interval (takeMVar var)+ rightToMaybe <$> Race.timeoutAs () interval (takeMVar var) Sync.TakeTry -> tryTakeMVar var Sync.ReadBlock -> readMVar var Sync.ReadWait interval ->- rightToMaybe <$> Race.timeout () interval (readMVar var)+ rightToMaybe <$> Race.timeoutAs () interval (readMVar var) Sync.ReadTry -> tryReadMVar var Sync.PutBlock d -> putMVar var d Sync.PutWait interval d ->- Race.timeout_ False interval (True <$ putMVar var d)+ Race.timeoutAs_ False interval (True <$ putMVar var d) Sync.PutTry d -> tryPutMVar var d Sync.Empty ->
− lib/Polysemy/Conc/Interrupt.hs
@@ -1,195 +0,0 @@-{-# options_haddock prune #-}--- |Description: Interrupt interpreters-module Polysemy.Conc.Interrupt where--import qualified Control.Concurrent.Async as A-import Control.Concurrent.Async (AsyncCancelled)-import qualified Data.Map.Strict as Map-import qualified Data.Set as Set-import qualified Data.Text.IO as Text-import Polysemy (getInspectorT, inspect, interpretH, runT)-import Polysemy.Async (Async, async, await, cancel)-import Polysemy.AtomicState (runAtomicStateTVar)-import Polysemy.Internal.Tactics (liftT)-import Polysemy.Time (Seconds (Seconds))-import System.Posix.Signals (Handler (CatchInfoOnce, CatchOnce), SignalInfo, installHandler, keyboardSignal)--import qualified Polysemy.Conc.Data.Critical as Critical-import Polysemy.Conc.Data.Critical (Critical)-import Polysemy.Conc.Data.Interrupt (Interrupt (..))-import Polysemy.Conc.Data.Race (Race)-import qualified Polysemy.Conc.Effect.Sync as Sync-import Polysemy.Conc.Interpreter.Sync (interpretSync)-import Polysemy.Conc.Race (race_)--putErr ::- Member (Embed IO) r =>- Text ->- Sem r ()-putErr =- embed . Text.hPutStrLn stderr--data InterruptState =- InterruptState {- quit :: !(MVar ()),- finished :: !(MVar ()),- listeners :: !(Set Text),- original :: !(SignalInfo -> IO ()),- handlers :: !(Map Text (IO ()))- }--modListeners :: (Set Text -> Set Text) -> InterruptState -> InterruptState-modListeners f s@InterruptState {listeners} =- s {listeners = f listeners}--modHandlers :: (Map Text (IO ()) -> Map Text (IO ())) -> InterruptState -> InterruptState-modHandlers f s@InterruptState {handlers} =- s {handlers = f handlers}--waitQuit ::- Members [AtomicState InterruptState, Embed IO] r =>- Sem r ()-waitQuit = do- mv <- atomicGets quit- readMVar mv--checkListeners ::- Members [AtomicState InterruptState, Embed IO] r =>- Sem r ()-checkListeners =- whenM (atomicGets (Set.null . listeners)) do- fin <- atomicGets finished- void (tryPutMVar fin ())--onQuit ::- Members [AtomicState InterruptState, Embed IO] r =>- Text ->- Sem r a ->- Sem r a-onQuit name ma = do- atomicModify' (modListeners (Set.insert name))- waitQuit- a <- ma- atomicModify' (modListeners (Set.delete name))- checkListeners- pure a--processHandler ::- Member (Embed IO) r =>- Text ->- IO () ->- Sem r ()-processHandler name thunk = do- putErr [qt|processing interrupt handler: #{name}|]- embed thunk--execInterrupt ::- Members [AtomicState InterruptState, Embed IO] r =>- Sem r (SignalInfo -> Sem r ())-execInterrupt = do- InterruptState quitSignal finishSignal _ orig _ <- atomicGet- whenM (tryPutMVar quitSignal ()) do- traverse_ (uncurry processHandler) . Map.toList =<< atomicGets handlers- checkListeners- takeMVar finishSignal- embed . orig <$ putErr "interrupt handlers finished"--registerHandler ::- Member (AtomicState InterruptState) r =>- Text ->- IO () ->- Sem r ()-registerHandler name handler =- atomicModify' (modHandlers (Map.insert name handler))--awaitOrKill ::- Members [AtomicState InterruptState, Critical, Race, Async, Embed IO] r =>- Text ->- A.Async (Maybe a) ->- Sem r (Maybe a)-awaitOrKill desc handle = do- interpretSync @() do- race_ (catchCritical (await handle)) kill- where- catchCritical =- maybe waitKill (pure . Just) <=< Critical.catchAs @AsyncCancelled Nothing- waitKill =- Nothing <$ Sync.wait @() (Seconds 1)- kill = do- onQuit desc do- putErr [qt|killing #{desc}|]- cancel handle- putErr [qt|killed #{desc}|]- Sync.putBlock ()- pure Nothing--interpretInterruptState ::- Members [AtomicState InterruptState, Critical, Race, Async, Embed IO] r =>- InterpreterFor Interrupt r-interpretInterruptState =- interpretH \case- Register name handler ->- liftT (registerHandler name handler)- Unregister name ->- liftT $ atomicModify' \ s@InterruptState {handlers} -> s {handlers = Map.delete name handlers}- WaitQuit ->- liftT waitQuit- Quit ->- liftT do- putErr "manual interrupt"- void execInterrupt- Interrupted ->- liftT . fmap isJust . tryReadMVar =<< atomicGets quit- KillOnQuit desc ma -> do- maT <- runT ma- ins <- getInspectorT- handle <- raise (interpretInterruptState (async maT))- result <- liftT (awaitOrKill desc handle)- pure (join . fmap (inspect ins) <$> result)-{-# inline interpretInterruptState #-}--broadcastInterrupt ::- Members [AtomicState InterruptState, Embed IO] r =>- SignalInfo ->- Sem r ()-broadcastInterrupt sig = do- putErr "caught interrupt signal"- orig <- execInterrupt- orig sig---- The original handler is either the default handler that kills all threads or a handler installed by an environment--- like ghcid.--- In the latter case, not calling it results in ghcid misbehaving.--- To distinguish the two cases, the constructor used by the default is 'Catch', while a custom handler should usually--- use 'CatchOnce', since you don't want to catch repeated occurences of SIGINT, as it will surely cause problems.-originalHandler :: Handler -> (SignalInfo -> IO ())-originalHandler (CatchOnce thunk) =- (const thunk)-originalHandler (CatchInfoOnce thunk) =- thunk-originalHandler _ =- const pass-{-# inline originalHandler #-}--installSignalHandler ::- TVar InterruptState ->- IO Handler-installSignalHandler state =- installHandler keyboardSignal (CatchInfoOnce handler) Nothing- where- handler sig =- runFinal $ embedToFinal @IO $ runAtomicStateTVar state (broadcastInterrupt sig)---- |Interpret 'Interrupt' by installing a signal handler.-interpretInterrupt ::- Members [Critical, Race, Async, Embed IO] r =>- InterpreterFor Interrupt r-interpretInterrupt sem = do- quitMVar <- newEmptyMVar- finishMVar <- newEmptyMVar- state <- newTVarIO (InterruptState quitMVar finishMVar Set.empty (const pass) Map.empty)- orig <- embed $ installSignalHandler state- runAtomicStateTVar state do- atomicModify' \ s -> s {original = originalHandler orig}- interpretInterruptState $ raiseUnder sem-{-# inline interpretInterrupt #-}
lib/Polysemy/Conc/Queue.hs view
@@ -1,9 +1,24 @@ -- |Description: Queue Combinators-module Polysemy.Conc.Queue where+module Polysemy.Conc.Queue (+ module Polysemy.Conc.Queue,+ module Polysemy.Conc.Effect.Queue,+) where -import qualified Polysemy.Conc.Data.Queue as Queue-import Polysemy.Conc.Data.Queue (Queue) import qualified Polysemy.Conc.Data.QueueResult as QueueResult+import qualified Polysemy.Conc.Effect.Queue as Queue+import Polysemy.Conc.Effect.Queue (+ Queue,+ close,+ closed,+ peek,+ read,+ readTimeout,+ tryPeek,+ tryRead,+ tryWrite,+ write,+ writeTimeout,+ ) -- |Read from a 'Queue' repeatedly until it is closed. --
lib/Polysemy/Conc/Queue/Timeout.hs view
@@ -1,11 +1,11 @@--- |Description: Timeout helper+-- |Description: Timeout Helper module Polysemy.Conc.Queue.Timeout where import Polysemy.Time (TimeUnit) import qualified Polysemy.Conc.Data.QueueResult as QueueResult import Polysemy.Conc.Data.QueueResult (QueueResult)-import Polysemy.Conc.Data.Race (Race)+import Polysemy.Conc.Effect.Race (Race) import qualified Polysemy.Conc.Race as Race -- |Run an 'STM' action atomically with a time limit@@ -16,7 +16,7 @@ STM (Maybe d) -> Sem r (QueueResult d) withTimeout timeout readQ =- Race.timeout_ QueueResult.NotAvailable timeout reader'+ Race.timeoutAs_ QueueResult.NotAvailable timeout reader' where reader' = maybe QueueResult.Closed QueueResult.Success <$> atomically readQ
lib/Polysemy/Conc/Race.hs view
@@ -1,38 +1,10 @@-{-# options_haddock prune #-}--- |Description: Race interpreters+-- |Description: Race Combinators module Polysemy.Conc.Race where -import qualified Control.Concurrent.Async as Async-import Polysemy.Final (getInitialStateS, interpretFinal, runS)-import qualified Polysemy.Time as Time-import Polysemy.Time (MicroSeconds (MicroSeconds), TimeUnit)-import qualified System.Timeout as System--import qualified Polysemy.Conc.Data.Race as Race-import Polysemy.Conc.Data.Race (Race)--biseqEither ::- Functor f =>- Either (f a) (f b) ->- f (Either a b)-biseqEither =- either (fmap Left) (fmap Right)-{-# inline biseqEither #-}+import Polysemy.Time (TimeUnit) --- |Interpret 'Race' in terms of 'Async.race' and 'System.timeout'.--- Since this has to pass higher-order thunks as 'IO' arguments, it is interpreted in terms of 'Final IO'.-interpretRace ::- Member (Final IO) r =>- InterpreterFor Race r-interpretRace =- interpretFinal @IO \case- Race.Race left right ->- fmap (fmap biseqEither) . Async.race <$> runS left <*> runS right- Race.Timeout err (Time.convert -> MicroSeconds timeout) mb -> do- mbT <- runS mb- s <- getInitialStateS- pure (maybe (Left err <$ s) (fmap Right) <$> System.timeout (fromIntegral timeout) mbT)-{-# inline interpretRace #-}+import qualified Polysemy.Conc.Effect.Race as Race+import Polysemy.Conc.Effect.Race (Race) -- |Specialization of 'Race.race' for the case where both thunks return the same type, obviating the need for 'Either'. race_ ::@@ -49,7 +21,7 @@ timeout_ :: TimeUnit u => Member Race r =>- a ->+ Sem r a -> u -> Sem r a -> Sem r a@@ -57,6 +29,31 @@ unify <$> Race.timeout err interval ma {-# inline timeout_ #-} +-- |Version of `Race.timeout` that takes a pure fallback value.+timeoutAs ::+ TimeUnit u =>+ Member Race r =>+ a ->+ u ->+ Sem r b ->+ Sem r (Either a b)+timeoutAs err =+ Race.timeout (pure err)+{-# inline timeoutAs #-}++-- |Specialization of 'timeoutAs' for the case where the thunk return the same type as the fallback, obviating the+-- need for 'Either'.+timeoutAs_ ::+ TimeUnit u =>+ Member Race r =>+ a ->+ u ->+ Sem r a ->+ Sem r a+timeoutAs_ err =+ timeout_ (pure err)+{-# inline timeoutAs_ #-}+ -- |Specialization of 'Race.timeout' for unit actions. timeoutU :: TimeUnit u =>@@ -65,5 +62,17 @@ Sem r () -> Sem r () timeoutU =- timeout_ ()+ timeout_ pass {-# inline timeoutU #-}++-- |Variant of 'timeout' that returns 'Maybe'.+timeoutMaybe ::+ TimeUnit u =>+ Member Race r =>+ u ->+ Sem r a ->+ Sem r (Maybe a)+timeoutMaybe u ma =+ timeoutAs_ Nothing u (Just <$> ma)+{-# inline timeoutMaybe #-}+
lib/Polysemy/Conc/Retry.hs view
@@ -4,7 +4,7 @@ import qualified Polysemy.Time as Time import Polysemy.Time (Time, TimeUnit) -import Polysemy.Conc.Data.Race (Race)+import Polysemy.Conc.Effect.Race (Race) import qualified Polysemy.Conc.Effect.Sync as Sync import Polysemy.Conc.Interpreter.Sync (interpretSync) import qualified Polysemy.Conc.Race as Race@@ -22,7 +22,7 @@ Sem r (Either e a) -> Sem r (Maybe a) retrying timeout interval action =- Race.timeout_ Nothing timeout (Just <$> spin)+ Race.timeoutMaybe timeout spin where spin = action >>= \case@@ -48,7 +48,7 @@ Sem r (Maybe (Either e a)) retryingWithError timeout interval action = interpretSync @e do- Race.timeout_ Nothing timeout (Just <$> spin) >>= \case+ Race.timeoutMaybe timeout spin >>= \case Just a -> pure (Just (Right a)) Nothing -> fmap Left <$> Sync.takeTry where
lib/Polysemy/Conc/Sync.hs view
@@ -1,12 +1,32 @@ -- |Description: Sync Combinators-module Polysemy.Conc.Sync where+module Polysemy.Conc.Sync (+ module Polysemy.Conc.Sync,+ module Polysemy.Conc.Effect.Sync+) where import qualified Polysemy.Time as Time import Polysemy.Time (Time, TimeUnit) import Polysemy.Conc.Effect.Scoped (Scoped, scoped) import qualified Polysemy.Conc.Effect.Sync as Sync-import Polysemy.Conc.Effect.Sync (Sync, SyncResources)+import Polysemy.Conc.Effect.Sync (+ ScopedSync,+ Sync,+ SyncResources,+ block,+ empty,+ putBlock,+ putTry,+ putWait,+ readBlock,+ readTry,+ readWait,+ takeBlock,+ takeTry,+ takeWait,+ try,+ wait,+ ) -- |Run an action repeatedly until the 'Sync' variable is available. whileEmpty ::
polysemy-conc.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack name: polysemy-conc-version: 0.2.0.0+version: 0.3.0.0 synopsis: Polysemy Effects for Concurrency description: See <https://hackage.haskell.org/package/polysemy-conc/docs/Polysemy-Conc.html> category: Concurrency@@ -30,22 +30,24 @@ Polysemy.Conc Polysemy.Conc.Async Polysemy.Conc.AtomicState- Polysemy.Conc.Critical- Polysemy.Conc.Data.Critical- Polysemy.Conc.Data.Interrupt- Polysemy.Conc.Data.Queue Polysemy.Conc.Data.QueueResult- Polysemy.Conc.Data.Race+ Polysemy.Conc.Effect.Critical Polysemy.Conc.Effect.Events+ Polysemy.Conc.Effect.Interrupt+ Polysemy.Conc.Effect.Queue+ Polysemy.Conc.Effect.Race Polysemy.Conc.Effect.Scoped Polysemy.Conc.Effect.Sync Polysemy.Conc.Events+ Polysemy.Conc.Interpreter.Critical Polysemy.Conc.Interpreter.Events+ Polysemy.Conc.Interpreter.Interrupt Polysemy.Conc.Interpreter.Queue.Pure Polysemy.Conc.Interpreter.Queue.TB Polysemy.Conc.Interpreter.Queue.TBM+ Polysemy.Conc.Interpreter.Race+ Polysemy.Conc.Interpreter.Scoped Polysemy.Conc.Interpreter.Sync- Polysemy.Conc.Interrupt Polysemy.Conc.Prelude Polysemy.Conc.Queue Polysemy.Conc.Queue.Result
test/Main.hs view
@@ -1,5 +1,6 @@ module Main where +import Polysemy.Conc.Test.EventsTest (test_events) import Polysemy.Conc.Test.InterruptTest (test_interrupt) import Polysemy.Conc.Test.QueueTest ( test_queueBlockTB,@@ -23,14 +24,17 @@ unitTest "TBM block" test_queueBlockTBM, unitTest "TB success" test_queueTB, unitTest "TB block" test_queueBlockTB- ],+ ],+ testGroup "events" [+ unitTest "events" test_events+ ], testGroup "sync" [ unitTest "sync" test_sync- ],+ ], testGroup "interrupt" [ unitTest "interrupt" test_interrupt- ] ]+ ] main :: IO () main =
test/Polysemy/Conc/Test/EventsTest.hs view
@@ -7,8 +7,8 @@ import qualified Polysemy.Conc.Effect.Events as Events import qualified Polysemy.Conc.Effect.Sync as Sync import Polysemy.Conc.Interpreter.Events (interpretEventsChan)+import Polysemy.Conc.Interpreter.Race (interpretRace) import Polysemy.Conc.Interpreter.Sync (interpretSync)-import Polysemy.Conc.Race (interpretRace) test_events :: UnitTest test_events =
test/Polysemy/Conc/Test/InterruptTest.hs view
@@ -1,14 +1,14 @@ module Polysemy.Conc.Test.InterruptTest where +import Control.Concurrent.STM (modifyTVar) import Polysemy.Async (asyncToIOFinal)-import Polysemy.Test (UnitTest, runTestAuto, assertEq)+import Polysemy.Test (UnitTest, assertEq, runTestAuto) import System.Posix (Handler (CatchInfoOnce), SignalInfo, installHandler, keyboardSignal, raiseSignal) -import Polysemy.Conc.Critical (interpretCritical)-import qualified Polysemy.Conc.Data.Interrupt as Interrupt-import Polysemy.Conc.Interrupt (interpretInterrupt)-import Polysemy.Conc.Race (interpretRace)-import Control.Concurrent.STM (modifyTVar)+import qualified Polysemy.Conc.Effect.Interrupt as Interrupt+import Polysemy.Conc.Interpreter.Critical (interpretCritical)+import Polysemy.Conc.Interpreter.Interrupt (interpretInterrupt)+import Polysemy.Conc.Interpreter.Race (interpretRace) handler :: MVar () -> TVar Int -> SignalInfo -> IO () handler mv tv _ = do
test/Polysemy/Conc/Test/QueueTest.hs view
@@ -5,15 +5,15 @@ import Polysemy.Time (MilliSeconds (MilliSeconds), interpretTimeGhc) import Polysemy.Time.Ghc (GhcTime) -import qualified Polysemy.Conc.Data.Queue as Queue-import Polysemy.Conc.Data.Queue (Queue) import qualified Polysemy.Conc.Data.QueueResult as QueueResult import Polysemy.Conc.Data.QueueResult (QueueResult)-import Polysemy.Conc.Data.Race (Race)+import qualified Polysemy.Conc.Effect.Queue as Queue+import Polysemy.Conc.Effect.Queue (Queue)+import Polysemy.Conc.Effect.Race (Race) import Polysemy.Conc.Interpreter.Queue.TB (interpretQueueTB) import Polysemy.Conc.Interpreter.Queue.TBM (interpretQueueTBM)+import Polysemy.Conc.Interpreter.Race (interpretRace) import Polysemy.Conc.Queue.Result (resultToMaybe)-import Polysemy.Conc.Race (interpretRace) progSuccess :: Member (Queue Int) r =>
test/Polysemy/Conc/Test/SyncTest.hs view
@@ -3,11 +3,11 @@ import Polysemy.Async (Async, asyncToIOFinal, sequenceConcurrently) import Polysemy.Test (UnitTest, assertEq, runTestAuto) -import Polysemy.Conc.Data.Race (Race)+import Polysemy.Conc.Effect.Race (Race) import qualified Polysemy.Conc.Effect.Sync as Sync import Polysemy.Conc.Effect.Sync (Sync)+import Polysemy.Conc.Interpreter.Race (interpretRace) import Polysemy.Conc.Interpreter.Sync (interpretSync)-import Polysemy.Conc.Race (interpretRace) data Thread1 = Thread1 data Thread2 = Thread2