@@ -39,9 +39,9 @@ module Troupe.Process
3939 send ,
4040 sendLazy ,
4141 receive ,
42- receiveTimeout ,
4342 Match ,
4443 matchIf ,
44+ after ,
4545 )
4646where
4747
@@ -69,6 +69,7 @@ import Control.Concurrent.STM
6969 readTMVar ,
7070 readTQueue ,
7171 readTVar ,
72+ registerDelay ,
7273 throwSTM ,
7374 tryReadTMVar ,
7475 writeTMVar ,
@@ -79,7 +80,7 @@ import Control.DeepSeq (NFData, deepseq, ($!!))
7980import Control.Distributed.Process.Internal.CQueue
8081 ( BlockSpec (.. ),
8182 CQueue ,
82- MatchOn (MatchMsg ),
83+ MatchOn (.. ),
8384 dequeue ,
8485 enqueueSTM ,
8586 newCQueue ,
@@ -99,7 +100,7 @@ import Control.Exception.Safe
99100 uninterruptibleMask_ ,
100101 withException ,
101102 )
102- import Control.Monad (MonadPlus , unless , void , when )
103+ import Control.Monad (MonadPlus , forM , unless , when )
103104import Control.Monad.Error.Class (MonadError )
104105import Control.Monad.Fix (MonadFix )
105106import Control.Monad.IO.Class (MonadIO , liftIO )
@@ -126,7 +127,6 @@ import qualified Control.Monad.Trans.Writer.CPS as CPS (WriterT)
126127import qualified Control.Monad.Trans.Writer.Lazy as Lazy (WriterT )
127128import qualified Control.Monad.Trans.Writer.Strict as Strict (WriterT )
128129import Data.Dynamic (Dynamic , fromDynamic , toDyn )
129- import Data.Functor.Identity (Identity (.. ), runIdentity )
130130import Data.Maybe (isJust )
131131import Data.Typeable (Typeable )
132132import DeferredFolds.UnfoldlM (forM_ )
@@ -488,11 +488,10 @@ demonitor :: (MonadProcess r m, MonadIO m) => [DemonitorOption] -> MonitorRef ->
488488demonitor ! options ! ref = do
489489 liftMonadProcess id $ demonitorSTM ref
490490 when (DemonitorFlush `elem` options) $ do
491- void $
492- receiveTimeout
493- 0
494- [ matchIf (\ d -> downMonitorRef d == ref) (\ _ -> pure () )
495- ]
491+ receive
492+ [ matchIf (\ d -> downMonitorRef d == ref) (\ _ -> pure () ),
493+ after 0 (pure () )
494+ ]
496495{-# SPECIALIZE demonitor :: [DemonitorOption] -> MonitorRef -> Process r () #-}
497496
498497exitSTM :: (Exception e ) => ProcessId -> Maybe e -> ReaderT (ProcessEnv r ) STM ()
@@ -631,85 +630,95 @@ sendLazy = sendWithOptions SendOptions
631630{-# INLINE sendLazy #-}
632631{-# SPECIALIZE sendLazy :: (Typeable a) => ProcessId -> a -> Process r () #-}
633632
634- data ReceiveMethod f where
635- ReceiveBlocking :: ReceiveMethod Identity
636- ReceiveNonBlocking :: ReceiveMethod Maybe
637- ReceiveTimeout :: Int -> ReceiveMethod Maybe
638-
639- {- HLINT ignore ReceiveOptions "Use newtype instead of data" -}
640- data ReceiveOptions f = ReceiveOptions
641- { receiveOptionsMethod :: ! (ReceiveMethod f )
642- }
633+ data ReceiveOptions = ReceiveOptions
643634
644635-- | Matching clause for a value of type @a@ in monadic context @m@.
645- newtype Match m a
636+ data Match m a
646637 = MatchMessage (Dynamic -> Maybe (m a ))
638+ | MatchAfter Int (m a )
647639 deriving (Functor )
648640
649- receiveWithOptions :: (MonadProcess r m , MonadIO m ) => ReceiveOptions f -> [Match m a ] -> m ( f a )
650- receiveWithOptions ! options ! matches = do
641+ receiveWithOptions :: (MonadProcess r m , MonadIO m ) => ReceiveOptions -> [Match m a ] -> m a
642+ receiveWithOptions ReceiveOptions ! matches = do
651643 queue <- processContextQueue . processEnvProcessContext <$> getProcessEnv
652- let bs = case receiveOptionsMethod options of
653- ReceiveBlocking -> Blocking
654- ReceiveNonBlocking -> NonBlocking
655- ReceiveTimeout t -> Timeout t
656- matches' = map (\ (MatchMessage fn) -> MatchMsg fn) matches
657- p <- liftIO $ dequeue queue bs matches'
644+
645+ p <- liftIO $ do
646+ matches' <- forM matches $ \ case
647+ MatchMessage fn -> pure (MatchMsg fn)
648+ MatchAfter t ma -> case t of
649+ 0 -> pure $ MatchChan $ pure ma
650+ t' -> do
651+ tv <- registerDelay t'
652+ pure $ MatchChan $ do
653+ v <- readTVar tv
654+ check v
655+ pure ma
656+
657+ dequeue queue Blocking matches'
658658
659659 ensureSignalsDelivered
660660
661661 case p of
662- Nothing -> case receiveOptionsMethod options of
663- ReceiveBlocking -> error " receiveWithOptions: dequeue returned Nothing in Blocking call"
664- ReceiveNonBlocking -> pure Nothing
665- ReceiveTimeout _ -> pure Nothing
666- Just a -> case receiveOptionsMethod options of
667- ReceiveBlocking -> Identity <$> a
668- ReceiveNonBlocking -> Just <$> a
669- ReceiveTimeout _ -> Just <$> a
662+ Nothing -> error " receiveWithOptions: dequeue returned Nothing"
663+ Just ma -> ma
670664 where
671665 ensureSignalsDelivered = do
672666 exceptions <- processContextExceptions . processEnvProcessContext <$> getProcessEnv
673667 liftIO $ atomically $ do
674668 e <- isEmptyTQueue exceptions
675669 check e
676- {-# SPECIALIZE receiveWithOptions :: ReceiveOptions f -> [Match (Process r) a] -> Process r (f a) #-}
670+ {-# SPECIALIZE receiveWithOptions :: ReceiveOptions -> [Match (Process r) a] -> Process r a #-}
677671
678672-- | Receive some message from the process mailbox, blocking.
679673receive :: (MonadProcess r m , MonadIO m ) => [Match m a ] -> m a
680- receive ! matches = runIdentity <$> receiveWithOptions options matches
681- where
682- options =
683- ReceiveOptions
684- { receiveOptionsMethod = ReceiveBlocking
685- }
674+ receive ! matches = receiveWithOptions ReceiveOptions matches
686675{-# INLINE receive #-}
687676{-# SPECIALIZE receive :: [Match (Process r) a] -> Process r a #-}
688677
689- -- | Receive some message from the process mailbox.
690- --
691- -- If the given timeout is @0@, this works in a non-blocking way. Otherwise,
692- -- the call will time out after the given number of microseconds.
693- --
694- -- If no message is matched within the timeout period, 'Nothing' is returned,
695- -- otherwise @'Just' a@.
696- receiveTimeout :: (MonadProcess r m , MonadIO m ) => Int -> [Match m a ] -> m (Maybe a )
697- receiveTimeout ! t = receiveWithOptions options
698- where
699- options =
700- ReceiveOptions
701- { receiveOptionsMethod = if t == 0 then ReceiveNonBlocking else ReceiveTimeout t
702- }
703- {-# INLINE receiveTimeout #-}
704- {-# SPECIALIZE receiveTimeout :: Int -> [Match (Process r) a] -> Process r (Maybe a) #-}
705-
706678-- | Match any message meeting some predicate of a specific type.
707679matchIf :: (Typeable a ) => (a -> Bool ) -> (a -> m b ) -> Match m b
708680matchIf predicate handle = MatchMessage $ \ dyn -> case fromDynamic dyn of
709681 Nothing -> Nothing
710682 Just a -> if predicate a then Just (handle a) else Nothing
711683{-# INLINE matchIf #-}
712684
685+ -- | A 'Match' which doesn't receive any messages, but fires after a given
686+ -- amount of time.
687+ --
688+ -- Instead of looking for a message in the process' mailbox, an 'after' clause
689+ -- in a call to 'receive' will fire after a given number of microseconds,
690+ -- yielding the provided monadic value. This can be used to implement receiving
691+ -- messages with a timeout.
692+ --
693+ -- When the given timeout is @0@, the 'receive' call will be non-blocking.
694+ -- Note, however, the order of matches is important, so
695+ --
696+ -- @
697+ -- s <- self
698+ -- send s ()
699+ -- receive [after 0 (pure "timeout"), match (\() -> pure "message")]
700+ -- @
701+ --
702+ -- will always return @"timeout"@, whilst
703+ --
704+ -- @
705+ -- s <- self
706+ -- send s ()
707+ -- receive [match (\() -> pure "message"), after 0 (pure "timeout")]
708+ -- @
709+ --
710+ -- will always return @"message"@.
711+ --
712+ -- In general, @'after'@ should be the last 'Match' passed to 'receive'.
713+ after ::
714+ -- | Timeout in microseconds. Use @0@ for a non-blocking 'receive'.
715+ Int ->
716+ -- | Action to call when the timeout expired.
717+ m a ->
718+ Match m a
719+ after = MatchAfter
720+ {-# INLINE after #-}
721+
713722spawnImpl :: (MonadProcess r m , MonadIO m ) => ThreadAffinity -> (ProcessId -> ReaderT (ProcessEnv r ) STM t ) -> Process r a -> m t
714723spawnImpl affinity cb process = do
715724 currentEnv <- getProcessEnv
0 commit comments