@@ -5,6 +5,7 @@
55{-# LANGUAGE BangPatterns #-}
66{-# LANGUAGE GeneralizedNewtypeDeriving #-}
77{-# LANGUAGE RecordWildCards #-}
8+{-# LANGUAGE MagicHash #-}
89
910-- | Local nodes
1011--
@@ -28,6 +29,7 @@ import System.IO (fixIO, hPutStrLn, stderr)
2829import System.Mem.Weak (Weak, deRefWeak)
2930import qualified Data.ByteString.Lazy as BSL (fromChunks)
3031import Data.Binary (decode)
32+import Data.Function (fix)
3133import Data.Map (Map)
3234import qualified Data.Map as Map
3335 ( empty
@@ -65,14 +67,24 @@ import Control.Exception
6567 , Exception
6668 , throwTo
6769 , uninterruptibleMask_
70+ , getMaskingState
71+ , MaskingState(..)
6872 )
6973import qualified Control.Exception as Exception
7074 ( Handler(..)
7175 , catch
7276 , catches
7377 , finally
78+ , bracket
79+ , mask_
80+ )
81+import Control.Concurrent
82+ ( forkIO
83+ , forkIOWithUnmask
84+ , killThread
85+ , myThreadId
86+ , ThreadId
7487 )
75−import Control.Concurrent (forkIO, forkIOWithUnmask, killThread, myThreadId)
7688import Control.Distributed.Process.Internal.StrictMVar
7789 ( newMVar
7890 , withMVar
@@ -83,8 +95,18 @@ import Control.Distributed.Process.Internal.StrictMVar
8395 , putMVar
8496 , takeMVar
8597 )
98+import Control.Distributed.Process.Internal.ThreadPool
99+ ( newThreadPool
100+ , submitTask
101+ , lookupWorker
102+ , ThreadPool
103+ )
86104import Control.Concurrent.Chan (newChan, writeChan, readChan)
87−import qualified Control.Concurrent.MVar as MVar (newEmptyMVar, takeMVar)
105+import qualified Control.Concurrent.MVar as MVar
106+ ( newEmptyMVar
107+ , takeMVar
108+ , putMVar
109+ )
88110import Control.Concurrent.STM
89111 ( atomically
90112 )
@@ -218,8 +240,19 @@ import qualified Control.Distributed.Process.Internal.StrictContainerAccessors a
218240 ( mapMaybe
219241 , mapDefault
220242 )
243+import GHC.IO (IO(..), unsafeUnmask)
244+import GHC.Base ( maskAsyncExceptions# )
245+
246+import System.Timeout
221247import Unsafe.Coerce
222248
249+-- Remove these when this is fixed:
250+-- https://ghc.haskell.org/trac/ghc/ticket/10149
251+block :: IO a -> IO a
252+block (IO io) = IO $ maskAsyncExceptions# io
253+unblock :: IO a -> IO a
254+unblock = unsafeUnmask
255+
223256--------------------------------------------------------------------------------
224257-- Initialization --
225258--------------------------------------------------------------------------------
@@ -249,12 +282,14 @@ createBareLocalNode endPoint rtable = do
249282 , _localConnections = Map.empty
250283 }
251284 ctrlChan <- newChan
285+ sendPool <- newThreadPool
252286 let node = LocalNode { localNodeId = NodeId $ NT.address endPoint
253287 , localEndPoint = endPoint
254288 , localState = state
255289 , localCtrlChan = ctrlChan
256290 , localEventBus = MxEventBusInitialising
257291 , remoteTable = rtable
292+ , localSendPool = sendPool
258293 }
259294 tracedNode <- startMxAgent node
260295
@@ -361,11 +396,14 @@ runProcess node proc = do
361396
362397-- | Spawn a new process on a local node
363398forkProcess :: LocalNode -> Process () -> IO ProcessId
364−forkProcess node proc =
365− modifyMVarMasked (localState node) startProcess
399+forkProcess node proc = do
400+ ms <- getMaskingState
401+ modifyMVarMasked (localState node) (startProcess ms)
366402 where
367− startProcess :: LocalNodeState -> IO (LocalNodeState, ProcessId)
368− startProcess (LocalNodeValid vst) = do
403+ startProcess :: MaskingState
404+ -> LocalNodeState
405+ -> IO (LocalNodeState, ProcessId)
406+ startProcess ms (LocalNodeValid vst) = do
369407 let lpid = LocalProcessId { lpidCounter = vst ^. localPidCounter
370408 , lpidUnique = vst ^. localPidUnique
371409 }
@@ -387,7 +425,13 @@ forkProcess node proc =
387425 , processThread = tid
388426 , processNode = node
389427 }
390− tid' <- uninterruptibleMask_ $ forkIOWithUnmask $ \unmask -> do
428+ -- Rewrite this code when this is fixed:
429+ -- https://ghc.haskell.org/trac/ghc/ticket/10149
430+ let unmask = case ms of
431+ Unmasked -> unblock
432+ MaskedInterruptible -> block
433+ MaskedUninterruptible -> id
434+ tid' <- uninterruptibleMask_ $ forkIO $ do
391435 reason <- Exception.catches
392436 (unmask $ runLocalProcess lproc proc >> return DiedNormal)
393437 [ (Exception.Handler (\ex@(ProcessExitException from msg) -> do
@@ -433,7 +477,7 @@ forkProcess node proc =
433477 $ vst
434478 , pid
435479 )
436− startProcess LocalNodeClosed =
480+ startProcess _ LocalNodeClosed =
437481 throwIO $ NodeClosedException $ localNodeId node
438482
439483 cleanupProcess :: ProcessId
@@ -491,24 +535,78 @@ incomingFrom addr = aux >>> DAC.mapDefault Set.empty addr
491535 where
492536 aux = accessor _incomingFrom (\fr st -> st { _incomingFrom = fr })
493537
538+-- | And 'EventMonitor' monitors the handling of events reporting when an event
539+-- is not handled promptly. It monitors events which are handled in sequence.
540+data EventMonitor a = EventMonitor
541+ { -- | Notifies that an event was received. Takes the event as argument.
542+ eventMonitorGotEvent :: a -> IO ()
543+ -- | Notifies that the last received event was handled.
544+ --
545+ -- Might be called even when there is no event, in which case it will have
546+ -- no effect.
547+ , eventMonitorHandledEvent :: IO ()
548+ -- | Stops the event monitor.
549+ , eventMonitorStop :: IO ()
550+ }
551+
552+-- | `eventMon <- monitorThread t action`
553+--
554+-- Creates an event monitor that executes `action` when an event hasn't been
555+-- handled withing `t` microseconds. `action` takes as argument the event that
556+-- wasn't handled timely.
557+--
558+monitorThread :: Int -> (a -> IO ()) -> IO (EventMonitor a)
559+monitorThread tmicrosecs action = do
560+ mv <- MVar.newEmptyMVar
561+ tid <- forkIO $ forever' $ do
562+ -- loop until some event is reported
563+ ev <- fix $ \loop -> MVar.takeMVar mv >>= maybe loop return
564+ -- wait until the event is handled
565+ Exception.mask_ (timeout tmicrosecs $ MVar.takeMVar mv) >>=
566+ maybe (do action ev -- report the unhandled event
567+ -- wait for the slow event handler
568+ void $ MVar.takeMVar mv
569+ )
570+ (const $ return ())
571+ return EventMonitor
572+ { eventMonitorGotEvent = MVar.putMVar mv . Just
573+ , eventMonitorHandledEvent = MVar.putMVar mv Nothing
574+ , eventMonitorStop = killThread tid
575+ }
576+
494577handleIncomingMessages :: LocalNode -> IO ()
495−handleIncomingMessages node = go initConnectionState
496− `Exception.catch` \(NodeClosedException _) -> return ()
578+handleIncomingMessages node =
579+ Exception.bracket
580+ (monitorThread 1000000 $ \ev ->
581+ hPutStrLn stderr $
582+ "handleIncomingMessages blocked" ++ show (localNodeId node) ++
583+ " on " ++ show ev
584+ )
585+ eventMonitorStop $ \eventMon ->
586+ (go eventMon initConnectionState
587+ `Exception.catch` \(NodeClosedException _) -> return ()
588+ ) `Exception.catch` \e -> do
589+ hPutStrLn stderr $
590+ "handleIncomingMessages failed: " ++
591+ show (localNodeId node, e :: SomeException)
592+ throwIO e
497593 where
498− go :: ConnectionState -> IO ()
499− go !st = do
594+ go :: EventMonitor NT.Event -> ConnectionState -> IO ()
595+ go eventMon !st = do
596+ eventMonitorHandledEvent eventMon
500597 event <- NT.receive endpoint
598+ eventMonitorGotEvent eventMon event
501599 case event of
502600 NT.ConnectionOpened cid rel theirAddr ->
503601 if rel == NT.ReliableOrdered
504602 then
505603 trace node (MxConnected cid theirAddr)
506− >> go (
604+ >> go eventMon (
507605 (incomingAt cid ^= Just (theirAddr, Uninit))
508606 . (incomingFrom theirAddr ^: Set.insert cid)
509607 $ st
510608 )
511− else invalidRequest cid st
609+ else invalidRequest eventMon cid st
512610 NT.Received cid payload ->
513611 case st ^. incomingAt cid of
514612 Just (_, ToProc pid weakQueue) -> do
@@ -519,7 +617,7 @@ handleIncomingMessages node = go initConnectionState
519617 let msg = payloadToMessage payload
520618 enqueue queue msg -- 'enqueue' is strict
521619 trace node (MxReceived pid msg)
522− go st
620+ go eventMon st
523621 Just (_, ToChan (TypedChannel chan')) -> do
524622 mChan <- deRefWeak chan'
525623 -- If mChan is Nothing, the process has given up the read end of
@@ -527,11 +625,11 @@ handleIncomingMessages node = go initConnectionState
527625 forM_ mChan $ \chan -> atomically $
528626 -- We make sure the message is fully decoded when it is enqueued
529627 writeTQueue chan $! decode (BSL.fromChunks payload)
530− go st
628+ go eventMon st
531629 Just (_, ToNode) -> do
532630 let ctrlMsg = decode . BSL.fromChunks $ payload
533631 writeChan ctrlChan $! ctrlMsg
534− go st
632+ go eventMon st
535633 Just (src, Uninit) ->
536634 case decode (BSL.fromChunks payload) of
537635 ProcessIdentifier pid -> do
@@ -539,9 +637,9 @@ handleIncomingMessages node = go initConnectionState
539637 mProc <- withValidLocalState node $ return . (^. localProcessWithId lpid)
540638 case mProc of
541639 Just proc ->
542− go (incomingAt cid ^= Just (src, ToProc pid (processWeakQ proc)) $ st)
640+ go eventMon (incomingAt cid ^= Just (src, ToProc pid (processWeakQ proc)) $ st)
543641 Nothing ->
544− invalidRequest cid st
642+ invalidRequest eventMon cid st
545643 SendPortIdentifier chId -> do
546644 let lcid = sendPortLocalId chId
547645 lpid = processLocalId (sendPortProcessId chId)
@@ -551,24 +649,25 @@ handleIncomingMessages node = go initConnectionState
551649 mChannel <- withMVar (processState proc) $ return . (^. typedChannelWithId lcid)
552650 case mChannel of
553651 Just channel ->
554− go (incomingAt cid ^= Just (src, ToChan channel) $ st)
652+ go eventMon
653+ (incomingAt cid ^= Just (src, ToChan channel) $ st)
555654 Nothing ->
556− invalidRequest cid st
655+ invalidRequest eventMon cid st
557656 Nothing ->
558− invalidRequest cid st
657+ invalidRequest eventMon cid st
559658 NodeIdentifier nid ->
560659 if nid == localNodeId node
561− then go (incomingAt cid ^= Just (src, ToNode) $ st)
562− else invalidRequest cid st
660+ then go eventMon (incomingAt cid ^= Just (src, ToNode) $ st)
661+ else invalidRequest eventMon cid st
563662 Nothing ->
564− invalidRequest cid st
663+ invalidRequest eventMon cid st
565664 NT.ConnectionClosed cid ->
566665 case st ^. incomingAt cid of
567666 Nothing ->
568− invalidRequest cid st
667+ invalidRequest eventMon cid st
569668 Just (src, _) -> do
570669 trace node (MxDisconnected cid src)
571− go ( (incomingAt cid ^= Nothing)
670+ go eventMon ( (incomingAt cid ^= Nothing)
572671 . (incomingFrom src ^: Set.delete cid)
573672 $ st
574673 )
@@ -580,8 +679,10 @@ handleIncomingMessages node = go initConnectionState
580679 , ctrlMsgSignal = Died nid DiedDisconnect
581680 }
582681 let notLost k = not (k `Set.member` (st ^. incomingFrom theirAddr))
682+ liftIO $ lookupWorker (localSendPool node) (NodeId theirAddr)
683+ >>= maybe (return ()) killThread
583684 closeImplicitReconnections node nid
584− go ( (incomingFrom theirAddr ^= Set.empty)
685+ go eventMon ( (incomingFrom theirAddr ^= Set.empty)
585686 . (incoming ^: Map.filterWithKey (const . notLost))
586687 $ st
587688 )
@@ -596,15 +697,17 @@ handleIncomingMessages node = go initConnectionState
596697 -- and we just give up
597698 fail "Cloud Haskell fatal error: received unexpected multicast"
598699
599− invalidRequest :: NT.ConnectionId -> ConnectionState -> IO ()
600− invalidRequest cid st = do
700+ invalidRequest :: EventMonitor NT.Event
701+ -> NT.ConnectionId
702+ -> ConnectionState -> IO ()
703+ invalidRequest eventMon cid st = do
601704 -- TODO: We should treat this as a fatal error on the part of the remote
602705 -- node. That is, we should report the remote node as having died, and we
603706 -- should close incoming connections (this requires a Transport layer
604707 -- extension).
605708 traceEventFmtIO node "" [(TraceStr " [network] invalid request: "),
606709 (Trace cid)]
607− go ( incomingAt cid ^= Nothing
710+ go eventMon ( incomingAt cid ^= Nothing
608711 $ st
609712 )
610713
@@ -616,9 +719,21 @@ handleIncomingMessages node = go initConnectionState
616719--------------------------------------------------------------------------------
617720
618721runNodeController :: LocalNode -> IO ()
619−runNodeController node =
620− runReaderT (evalStateT (unNC nodeController) initNCState) node
621− `Exception.catch` \(NodeClosedException _) -> return ()
722+runNodeController node = do
723+ Exception.bracket
724+ (monitorThread 1000000 $ \ev ->
725+ hPutStrLn stderr $
726+ "nodeController blocked" ++ show (localNodeId node) ++
727+ " on " ++ show ev
728+ )
729+ eventMonitorStop $ \eventMon ->
730+ (runReaderT (evalStateT (unNC (nodeController eventMon)) initNCState) node
731+ `Exception.catch` \(NodeClosedException _) -> return ()
732+ ) `Exception.catch` \e -> do
733+ hPutStrLn stderr $
734+ "nodeController failed: " ++
735+ show (localNodeId node, e :: SomeException)
736+ throwIO e
622737
623738--------------------------------------------------------------------------------
624739-- Internal data types --
@@ -634,6 +749,12 @@ data NCState = NCState
634749 , _registeredOnNodes :: !(Map ProcessId [(NodeId,Int)])
635750 }
636751
752+submitSendPool :: LocalNode -> NodeId -> IO () -> IO ()
753+submitSendPool node nid task = submitTask (localSendPool node) fork nid task
754+ where
755+ fork io =
756+ forkIO $ io `Exception.catch` \(NodeClosedException _) -> return ()
757+
637758newtype NC a = NC { unNC :: StateT NCState (ReaderT LocalNode IO) a }
638759 deriving ( Applicative
639760 , Functor
@@ -644,11 +765,12 @@ newtype NC a = NC { unNC :: StateT NCState (ReaderT LocalNode IO) a }
644765 )
645766
646767initNCState :: NCState
647−initNCState = NCState { _links = Map.empty
648− , _monitors = Map.empty
649− , _registeredHere = Map.empty
650− , _registeredOnNodes = Map.empty
651− }
768+initNCState = NCState
769+ { _links = Map.empty
770+ , _monitors = Map.empty
771+ , _registeredHere = Map.empty
772+ , _registeredOnNodes = Map.empty
773+ }
652774
653775-- | Thrown in response to the user invoking 'kill' (see Primitives.hs). This
654776-- type is deliberately not exported so it cannot be caught explicitly.
@@ -666,7 +788,7 @@ ncSendToProcess pid msg = do
666788 node <- ask
667789 if processNodeId pid == localNodeId node
668790 then ncEffectLocalSend node pid msg
669− else liftIO $ sendBinary node
791+ else liftIO $ submitSendPool node (processNodeId pid) $ sendBinary node
670792 (NodeIdentifier $ localNodeId node)
671793 (NodeIdentifier $ processNodeId pid)
672794 WithImplicitReconnect
@@ -679,7 +801,7 @@ ncSendToNode to msg = do
679801 node <- ask
680802 liftIO $ if to == localNodeId node
681803 then writeChan (localCtrlChan node) $! msg
682− else sendBinary node
804+ else submitSendPool node to $ sendBinary node
683805 (NodeIdentifier $ localNodeId node)
684806 (NodeIdentifier to)
685807 WithImplicitReconnect
@@ -718,11 +840,13 @@ withLocalTracer node act = act (localEventBus node)
718840--------------------------------------------------------------------------------
719841
720842-- [Unified: Table 7]
721−nodeController :: NC ()
722−nodeController = do
843+nodeController :: EventMonitor NCMsg -> NC ()
844+nodeController eventMon = do
723845 node <- ask
724846 forever' $ do
847+ liftIO $ eventMonitorHandledEvent eventMon
725848 msg <- liftIO $ readChan (localCtrlChan node)
849+ liftIO $ eventMonitorGotEvent eventMon msg
726850
727851 -- [Unified: Table 7, rule nc_forward]
728852 case destNid (ctrlMsgSignal msg) of