packages feed

polysemy-conc 0.3.0.0 → 0.4.0.0

raw patch · 7 files changed

+143/−13 lines, 7 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Polysemy.Conc.Interpreter.Queue.TBM: withTBMQueue :: forall d r a. Members [Resource, Embed IO] r => Int -> (TBMQueue d -> Sem r a) -> Sem r a
- 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: 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.Sync: block :: forall d_agY6 r_ahpX. MemberWithError (Sync d_agY6) r_ahpX => Sem r_ahpX d_agY6
- 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: 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: interpretInterruptOnce :: Members [Critical, Race, Async, Embed IO] r => InterpreterFor Interrupt r
+ Polysemy.Conc: lock :: forall l r a. Members [Sync l, Resource] r => l -> Sem r a -> Sem r a
+ Polysemy.Conc: scheduleAsync :: forall res b r a. Members [ScopedSync res (), Async, Race] r => Sem r b -> (Async (Maybe b) -> Sem (Sync () : r) () -> Sem (Sync () : r) a) -> Sem r a
+ Polysemy.Conc: scheduleAsyncIO :: forall b r a. Members [Resource, Async, Race, Embed IO] r => Sem r b -> (Async (Maybe b) -> Sem (Sync () : r) () -> Sem (Sync () : r) a) -> Sem r a
+ Polysemy.Conc.Async: scheduleAsync :: forall res b r a. Members [ScopedSync res (), Async, Race] r => Sem r b -> (Async (Maybe b) -> Sem (Sync () : r) () -> Sem (Sync () : r) a) -> Sem r a
+ Polysemy.Conc.Async: scheduleAsyncIO :: forall b r a. Members [Resource, Async, Race, Embed IO] r => Sem r b -> (Async (Maybe b) -> Sem (Sync () : r) () -> Sem (Sync () : r) a) -> Sem r a
+ Polysemy.Conc.Effect.Events: consume :: forall e r. Member (Consume e) r => Sem r e
+ Polysemy.Conc.Effect.Events: publish :: forall e token r. Member (Events token e) r => e -> Sem r ()
+ Polysemy.Conc.Effect.Sync: type ScopedSync res a = Scoped (SyncResources res) (Sync a)
+ Polysemy.Conc.Interpreter.Interrupt: interpretInterruptOnce :: Members [Critical, Race, Async, Embed IO] r => InterpreterFor Interrupt r
+ Polysemy.Conc.Interpreter.Interrupt: interpretInterruptWith :: Members [Critical, Race, Async, Embed IO] r => ((SignalInfo -> IO ()) -> Handler) -> InterpreterFor Interrupt r
+ Polysemy.Conc.Sync: lock :: forall l r a. Members [Sync l, Resource] r => l -> Sem r a -> Sem r a
- Polysemy.Conc: consume :: forall e_amwE r_amxj. MemberWithError (Consume e_amwE) r_amxj => Sem r_amxj e_amwE
+ Polysemy.Conc: consume :: forall e r. Member (Consume e) r => Sem r e
- 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: publish :: forall e token r. Member (Events token e) r => e -> Sem r ()
- Polysemy.Conc: withSync :: forall d res r. Member (Scoped (SyncResources res) (Sync d)) r => InterpreterFor (Sync d) r
+ Polysemy.Conc: withSync :: forall d res r. Member (ScopedSync res d) r => InterpreterFor (Sync d) r
- Polysemy.Conc.Sync: withSync :: forall d res r. Member (Scoped (SyncResources res) (Sync d)) r => InterpreterFor (Sync d) r
+ Polysemy.Conc.Sync: withSync :: forall d res r. Member (ScopedSync res d) r => InterpreterFor (Sync d) r

Files

changelog.md view
@@ -1,5 +1,12 @@ # Unreleased +# 0.4.0.0++* Add `lock`, a combinator for protecting a region with a mutex.+* Add `scheduleAsync`, a combinator for running an action async that allows the handle to be used before the thread+  starts+* Change the default signal handler for `Interrupt` to `CatchInfo`, catching repeated signals.+ # 0.3.0.0  * Change `Race.timeout` to take a `Sem` for the fallback instead of a pure value.
lib/Polysemy/Conc.hs view
@@ -30,6 +30,7 @@   interpretSync,   interpretSyncAs,   withSync,+  lock,   interpretScopedSync,   interpretScopedSyncAs, @@ -56,6 +57,7 @@    -- ** Interpreters   interpretInterrupt,+  interpretInterruptOnce,    -- * Event Channels   Events,@@ -85,9 +87,17 @@   withAsyncBlock,   withAsync,   withAsync_,+  scheduleAsync,+  scheduleAsyncIO, ) where -import Polysemy.Conc.Async (withAsync, withAsyncBlock, withAsync_)+import Polysemy.Conc.Async (+  scheduleAsync,+  scheduleAsyncIO,+  withAsync,+  withAsyncBlock,+  withAsync_,+  ) import Polysemy.Conc.AtomicState (interpretAtomic) import Polysemy.Conc.Data.QueueResult (QueueResult) import Polysemy.Conc.Effect.Critical (Critical)@@ -99,7 +109,7 @@ 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.Interrupt (interpretInterrupt, interpretInterruptOnce) import Polysemy.Conc.Interpreter.Queue.Pure (   interpretQueueListReadOnlyAtomic,   interpretQueueListReadOnlyAtomicWith,@@ -114,7 +124,7 @@ import Polysemy.Conc.Queue.Result (resultToMaybe) import Polysemy.Conc.Race (race_, timeoutAs, timeoutAs_, timeoutMaybe, timeoutU, timeout_) import Polysemy.Conc.Retry (retrying, retryingWithError)-import Polysemy.Conc.Sync (withSync)+import Polysemy.Conc.Sync (lock, withSync)  -- $intro -- This library provides an assortment of tools for concurrency-related tasks:
lib/Polysemy/Conc/Async.hs view
@@ -6,7 +6,11 @@ import Polysemy.Time (MilliSeconds (MilliSeconds), TimeUnit)  import Polysemy.Conc.Effect.Race (Race)+import qualified Polysemy.Conc.Effect.Sync as Sync import qualified Polysemy.Conc.Race as Race+import Polysemy.Conc.Interpreter.Sync (interpretSync)+import Polysemy.Conc.Effect.Sync (Sync, ScopedSync)+import Polysemy.Conc.Sync (withSync)  -- |Run the first action asynchronously while the second action executes, then cancel the first action. -- Passes the handle into the action to allow it to await its result.@@ -57,3 +61,46 @@   Sem r a withAsync_ mb =   withAsync mb . const++-- |Run an action with 'async', but don't start it right away, so the thread handle can be processed before the action+-- executes.+--+-- Takes a callback function that is invoked after spawning the thread.+-- The callback receives the 'Base.Async' handle and a unit action that starts the computation.+--+-- This is helpful if the 'Base.Async' has to be stored in state and the same state is written when the action finishes.+-- In that case, the race condition causes the handle to be written over the finished state.+--+-- @+-- makeRequest = put Nothing+--+-- main = scheduleAsync makeRequest \ handle start -> do+--   put (Just handle)+--   start -- now makeRequest is executed+-- @+scheduleAsync ::+  ∀ res b r a .+  Members [ScopedSync res (), Async, Race] r =>+  Sem r b ->+  (Base.Async (Maybe b) -> Sem (Sync () : r) () -> Sem (Sync () : r) a) ->+  Sem r a+scheduleAsync mb f =+  withSync @() @res do+    h <- async do+      Sync.readBlock @()+      raise mb+    f h (Sync.putBlock ())++-- |Variant of 'scheduleAsync' that directly interprets the 'MVar' used for signalling.+scheduleAsyncIO ::+  ∀ b r a .+  Members [Resource, Async, Race, Embed IO] r =>+  Sem r b ->+  (Base.Async (Maybe b) -> Sem (Sync () : r) () -> Sem (Sync () : r) a) ->+  Sem r a+scheduleAsyncIO mb f =+  interpretSync @() do+    h <- async do+      Sync.readBlock @()+      raise mb+    f h (Sync.putBlock ())
lib/Polysemy/Conc/Interpreter/Interrupt.hs view
@@ -12,7 +12,7 @@ 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 System.Posix.Signals (Handler (CatchInfoOnce, CatchOnce, CatchInfo), SignalInfo, installHandler, keyboardSignal)  import qualified Polysemy.Conc.Effect.Critical as Critical import Polysemy.Conc.Effect.Critical (Critical)@@ -173,23 +173,44 @@  installSignalHandler ::   TVar InterruptState ->+  ((SignalInfo -> IO ()) -> Handler) ->   IO Handler-installSignalHandler state =-  installHandler keyboardSignal (CatchInfoOnce handler) Nothing+installSignalHandler state consHandler =+  installHandler keyboardSignal (consHandler handler) Nothing   where     handler sig =       runFinal $ embedToFinal @IO $ runAtomicStateTVar state (broadcastInterrupt sig)  -- |Interpret 'Interrupt' by installing a signal handler.-interpretInterrupt ::+--+-- Takes a constructor for 'Handler'.+interpretInterruptWith ::   Members [Critical, Race, Async, Embed IO] r =>+  ((SignalInfo -> IO ()) -> Handler) ->   InterpreterFor Interrupt r-interpretInterrupt sem = do+interpretInterruptWith consHandler sem = do   quitMVar <- newEmptyMVar   finishMVar <- newEmptyMVar   state <- newTVarIO (InterruptState quitMVar finishMVar Set.empty (const pass) Map.empty)-  orig <- embed $ installSignalHandler state+  orig <- embed $ installSignalHandler state consHandler   runAtomicStateTVar state do     atomicModify' \ s -> s {original = originalHandler orig}     interpretInterruptState $ raiseUnder sem-{-# inline interpretInterrupt #-}++-- |Interpret 'Interrupt' by installing a signal handler.+--+-- Catches repeat invocations of SIGINT.+interpretInterrupt ::+  Members [Critical, Race, Async, Embed IO] r =>+  InterpreterFor Interrupt r+interpretInterrupt =+  interpretInterruptWith CatchInfo++-- |Interpret 'Interrupt' by installing a signal handler.+--+-- Catches only the first invocation of SIGINT.+interpretInterruptOnce ::+  Members [Critical, Race, Async, Embed IO] r =>+  InterpreterFor Interrupt r+interpretInterruptOnce =+  interpretInterruptWith CatchInfoOnce
lib/Polysemy/Conc/Sync.hs view
@@ -7,7 +7,7 @@ import qualified Polysemy.Time as Time import Polysemy.Time (Time, TimeUnit) -import Polysemy.Conc.Effect.Scoped (Scoped, scoped)+import Polysemy.Conc.Effect.Scoped (scoped) import qualified Polysemy.Conc.Effect.Sync as Sync import Polysemy.Conc.Effect.Sync (   ScopedSync,@@ -27,6 +27,7 @@   try,   wait,   )+import Polysemy.Resource (finally, Resource)  -- |Run an action repeatedly until the 'Sync' variable is available. whileEmpty ::@@ -59,7 +60,22 @@ -- |Run an action with a locally scoped 'Sync' variable. withSync ::   ∀ d res r .-  Member (Scoped (SyncResources res) (Sync d)) r =>+  Member (ScopedSync res d) r =>   InterpreterFor (Sync d) r withSync =   scoped @(SyncResources res)++-- |Run the action @ma@ with an exclusive lock (mutex).+-- When multiple threads call the action concurrently, only one is allowed to execute it at a time.+-- The value @l@ is used to disambiguate the 'Sync' from other uses of the combinator.+-- You can pass in something like @Proxy @"db-write"@.+--+-- /Note:/ The 'Sync' must be interpreted with an initially full @MVar@, e.g. using 'Polysemy.Conc.interpretSyncAs'.+lock ::+  ∀ l r a .+  Members [Sync l, Resource] r =>+  l ->+  Sem r a ->+  Sem r a+lock l ma =+  finally (takeBlock @l *> ma) (putTry l)
polysemy-conc.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack  name:           polysemy-conc-version:        0.3.0.0+version:        0.4.0.0 synopsis:       Polysemy Effects for Concurrency description:    See <https://hackage.haskell.org/package/polysemy-conc/docs/Polysemy-Conc.html> category:       Concurrency@@ -146,6 +146,7 @@   other-modules:       Polysemy.Conc.Test.EventsTest       Polysemy.Conc.Test.InterruptTest+      Polysemy.Conc.Test.LockTest       Polysemy.Conc.Test.QueueTest       Polysemy.Conc.Test.SyncTest       Paths_polysemy_conc@@ -218,6 +219,7 @@     , polysemy-time >=0.1.2.1     , stm     , tasty+    , time     , unagi-chan >=0.4     , unix   mixins:
+ test/Polysemy/Conc/Test/LockTest.hs view
@@ -0,0 +1,27 @@+module Polysemy.Conc.Test.LockTest where++import Data.Time (Day, UTCTime)+import Polysemy.Async (asyncToIOFinal, sequenceConcurrently)+import Polysemy.Test (UnitTest, assertEq, runTestAuto)+import qualified Polysemy.Time as Time+import Polysemy.Time (MicroSeconds (MicroSeconds), interpretTimeGhc)++import Polysemy.Conc.AtomicState (interpretAtomic)+import Polysemy.Conc.Interpreter.Race (interpretRace)+import Polysemy.Conc.Interpreter.Sync (interpretSyncAs)+import Polysemy.Conc.Sync (lock)++test_lock :: UnitTest+test_lock =+  runTestAuto $+  interpretTimeGhc $+  asyncToIOFinal $+  interpretRace $+  interpretSyncAs () $+  interpretAtomic (0 :: Int) do+    void $ sequenceConcurrently @[] $ [(1 :: Int)..100] $> do+      lock () do+        cur <- atomicGet @Int+        Time.sleep @UTCTime @Day (MicroSeconds 100)+        atomicPut (cur + 1)+    assertEq @Int @IO 100 =<< atomicGet