----------------------------------------------------------------------------- {-# LANGUAGE ScopedTypeVariables #-} ----------------------------------------------------------------------------- -- | -- Module : Miso.Concurrent -- Copyright : (C) 2016-2026 David M. Johnson -- License : BSD3-style (see the file LICENSE) -- Maintainer : David M. Johnson <code@dmj.io> -- Stability : experimental -- Portability : non-portable -- -- = Overview -- -- "Miso.Concurrent" provides 'Waiter', a lightweight synchronization -- primitive built on 'Control.Concurrent.MVar.MVar' that the miso runtime -- uses to coordinate its event loop with subscription threads. -- -- Two constructors are available with different wakeup semantics: -- -- * 'waiter' — __many-to-one__. Multiple threads may call 'notify' -- concurrently; only one pending notification is retained at a time -- ('Control.Concurrent.MVar.tryPutMVar' is used, so rapid notifications -- coalesce). The single consumer calls 'wait' to block until at least one -- notification arrives. -- -- * 'oneshot' — __one-to-many__. One thread calls 'notify' to permanently -- unblock /all/ threads currently (or subsequently) calling 'wait'. -- Implemented with 'Control.Concurrent.MVar.readMVar', so the stored value -- is never consumed. -- -- = See also -- -- * "Miso.Effect" — 'Miso.Effect.Sub' subscriptions that use 'notify' to -- wake the event loop -- * "Miso.Runtime" — the event loop that calls 'wait' ----------------------------------------------------------------------------- module Miso.Concurrent ( -- ** Synchronization primitives Waiter (..) , waiter , oneshot ) where ----------------------------------------------------------------------------- import Control.Concurrent ----------------------------------------------------------------------------- -- | Synchronization primitive for event loop data Waiter = Waiter { Waiter -> IO () wait :: IO () -- ^ Blocks on MVar , Waiter -> IO () notify :: IO () -- ^ Unblocks threads waiting on MVar } ----------------------------------------------------------------------------- -- | Creates a new @Waiter@ -- -- Useful for multiple threads to wake-up / notify a single thread running in an -- infinite loop, waiting for work (e.g. to process an event queue). -- waiter :: IO Waiter waiter :: IO Waiter waiter = do mvar <- IO (MVar ()) forall a. IO (MVar a) newEmptyMVar pure Waiter { wait = takeMVar mvar , notify = do _ <- tryPutMVar mvar () pure () } ----------------------------------------------------------------------------- -- | Creates a new @Waiter@ -- -- Useful for a single thread to wake-up multiple threads that are waiting -- to run a oneshot task (e.g. like forking a thread). -- oneshot :: IO Waiter oneshot :: IO Waiter oneshot = do mvar <- IO (MVar ()) forall a. IO (MVar a) newEmptyMVar pure Waiter { wait = readMVar mvar , notify = putMVar mvar () } -----------------------------------------------------------------------------