@@ -98,14 +98,15 @@ import Data.Map (Map)
98import qualified Data.Map as Map (empty, lookup, insert, delete, keys, toList)
98import qualified Data.Map as Map (empty, lookup, insert, delete, keys, toList)
99import qualified Data.List as List (delete)
99import qualified Data.List as List (delete)
100import Data.Set (Set)
100import Data.Set (Set)
101−import qualified Data.Set as Set (empty, insert)
101+import qualified Data.Set as Set (empty, insert, delete, member)
102import Data.Int (Int32)
102import Data.Int (Int32)
103import Data.Typeable (Typeable)
103import Data.Typeable (Typeable)
104import Data.Foldable (forM_)
104import Data.Foldable (forM_)
105−import Data.Maybe (isNothing)
105+import Data.Maybe (isJust)
106−import Control.Monad (void, liftM2, liftM3, when)
106+import Control.Monad (void, liftM2, liftM3, when, unless, forever)
107import Control.Monad.Reader (MonadReader(..), ReaderT, runReaderT)
107import Control.Monad.Reader (MonadReader(..), ReaderT, runReaderT)
108import Control.Monad.IO.Class (MonadIO, liftIO)
108import Control.Monad.IO.Class (MonadIO, liftIO)
109+import Control.Monad.State (MonadState, StateT, evalStateT, gets, modify)
109import Control.Applicative ((<$>))
110import Control.Applicative ((<$>))
110import Control.Category ((>>>))
111import Control.Category ((>>>))
111import Control.Exception (Exception, throwIO, catch)
112import Control.Exception (Exception, throwIO, catch)
@@ -304,10 +305,10 @@ data NodeCtrlMsg = NodeCtrlMsg
306data NodeCtrlState = NodeCtrlState
307data NodeCtrlState = NodeCtrlState
307− { -- Mapping from remote processes to linked local processes
308+ { -- Mapping from remote processes to linked local processes
308 _nodeCtrlLinks :: Map NodeId (Map LocalProcessId (Set ProcessId))
309 _nodeCtrlLinks :: Map NodeId (Map LocalProcessId (Set ProcessId))
309 -- Mapping from remote processes to monitoring local processes
310 -- Mapping from remote processes to monitoring local processes
310− , _nodeCtrlMons :: Map NodeId (Map LocalProcessId (Map ProcessId (Set (MonitorRef))))
311+ , _nodeCtrlMons :: Map NodeId (Map LocalProcessId (Map ProcessId (Set MonitorRef)))
311 -- The node controller maintains its own set of connections
312 -- The node controller maintains its own set of connections
312 -- TODO: still not convinced that this is correct
313 -- TODO: still not convinced that this is correct
313 , _nodeCtrlConns :: Map Identifier NT.Connection
314 , _nodeCtrlConns :: Map Identifier NT.Connection
@@ -325,118 +326,147 @@ initNodeCtrlState = NodeCtrlState
325 , _nodeCtrlConns = Map.empty
326 , _nodeCtrlConns = Map.empty
328−nodeController :: LocalNode -> IO ()
329+newtype NodeCtrl a = NodeCtrl { unNodeCtrl :: ReaderT LocalNode (StateT NodeCtrlState IO) a }
330+ deriving (Functor, Monad, MonadIO, MonadState NodeCtrlState, MonadReader LocalNode)
329−nodeController node = go initNodeCtrlState
332+nodeController :: NodeCtrl ()
333+nodeController = forever $ do
335+ msg <- liftIO $ readChan (localCtrlChan node)
337+ -- Forward the message if appropriate
338+ case destNid (ctrlMsgSignal msg) of
339+ Just nid' | nid' /= localNodeId node ->
340+ ctrlSendTo (Right nid') . BSL.toChunks . encode $ msg
346+ctrlSendTo :: Identifier -> [BSS.ByteString] -> NodeCtrl ()
347+ctrlSendTo them payload = do
348+ mConn <- ctrlConnTo them
349+ didSend <- case mConn of
351+ didSend <- liftIO $ NT.send conn payload
353+ Left _ -> return False
354+ Right () -> return True
358+ -- [Unified: Table 9, rule node_disconnect]
360+ liftIO . writeChan (localCtrlChan node) $ NodeCtrlMsg
361+ { ctrlMsgSender = them
362+ , ctrlMsgSignal = Died them DiedDisconnect
365+ctrlConnTo :: Identifier -> NodeCtrl (Maybe NT.Connection)
367+ mConn <- gets (^. nodeCtrlConnFor them)
369+ Just conn -> return (Just conn)
370+ Nothing -> createCtrlConnTo them
372+createCtrlConnTo :: Identifier -> NodeCtrl (Maybe NT.Connection)
373+createCtrlConnTo them = do
375+ mConn <- liftIO $ NT.connect (localEndPoint node)
378+ NT.defaultConnectHints
381+ didSend <- liftIO $ NT.send conn firstMsg
386+ modify $ nodeCtrlConnFor them ^= Just conn
331− -- [Unified: Table 7]
332− go :: NodeCtrlState -> IO ()
334− msg <- readChan ourChan
335− st' <- case destNid (ctrlMsgSignal msg) of
336− Just nid' | nid' /= localNodeId node -> do
337− (st', mConn) <- connTo st (Right nid')
338− didSend <- case mConn of
340− didSend <- NT.send conn (BSL.toChunks . encode $ msg)
342− Left _ -> return False
343− Right () -> return True
350− writeChan ourChan $ NodeCtrlMsg
351− { ctrlMsgSender = Right nid'
352− , ctrlMsgSignal = Died (Right nid') DiedDisconnect
356− -- No need to forward
358− st'' <- processCtrlMsg node st' msg
361− ourChan = localCtrlChan node
362− ourEndPoint = localEndPoint node
364− connTo :: NodeCtrlState
366− -> IO (NodeCtrlState, Maybe NT.Connection)
368− case st ^. nodeCtrlConnFor them of
370− (addr, firstMsg) <- return $ case them of
391+ (addr, firstMsg) = case them of
371− Left pid -> ( nodeAddress (processNodeId pid)
392+ Left pid -> ( nodeAddress (processNodeId pid)
372− , idToPayload (Just $ processLocalId pid)
393+ , idToPayload (Just $ processLocalId pid)
374− Right nid -> ( nodeAddress nid
395+ Right nid -> ( nodeAddress nid
375− , idToPayload Nothing
396+ , idToPayload Nothing
377− mConn <- NT.connect ourEndPoint
398+
380− NT.defaultConnectHints
383− didSend <- NT.send conn firstMsg
386− return (st, Nothing)
388− return (nodeCtrlConnFor them ^= Just conn $ st, Just conn)
390− return (st, Nothing)
392− return (st, Just conn)
394-- [Unified: ncEffect]
400-- [Unified: ncEffect]
395−processCtrlMsg :: LocalNode
401+processCtrlMsg :: NodeCtrlMsg -> NodeCtrl ()
396− -> NodeCtrlState
402+
398− -> IO (NodeCtrlState)
399-- [Unified: Table 10]
403-- [Unified: Table 10]
400−processCtrlMsg node st (NodeCtrlMsg from (Monitor them ref)) = do
404+processCtrlMsg (NodeCtrlMsg from (Monitor them ref)) = do
401 shouldLink <-
406 shouldLink <-
402 if processNodeId them /= localNodeId node
407 if processNodeId them /= localNodeId node
403 then return True
408 then return True
404− else withMVar (localState node) $
409+ else liftIO . withMVar (localState node) $
405− return . isNothing . (^. localProcessWithId (processLocalId them))
410+ return . isJust . (^. localProcessWithId (processLocalId them))
406 if shouldLink
411 if shouldLink
408− return (nodeCtrlMonsFor them (fromLeft from) ^: Set.insert ref $ st)
413+ modify $ nodeCtrlMonsFor them (fromLeft from) ^: Set.insert ref
410 fail "processCtrlMsg: TODO"
415 fail "processCtrlMsg: TODO"
412−-- [Unified Table 9 and Table 12]
417+-- [Unified: Table 12, bottom rule]
413−processCtrlMsg node st (NodeCtrlMsg _from (Died (Right nid) reason)) = do
418+processCtrlMsg (NodeCtrlMsg _from (Died (Right nid) reason)) = do
414− let localLinks :: Map LocalProcessId (Set ProcessId)
419+ node <- ask
415− localLinks = st ^. nodeCtrlLinksForNode nid
420+ links <- gets (^. nodeCtrlLinksForNode nid)
421+ mons <- gets (^. nodeCtrlMonsForNode nid)
417− localMons :: Map LocalProcessId (Map ProcessId (Set MonitorRef))
423+ forM_ (Map.toList links) $ \(_them, _uss) -> do
418− localMons = st ^. nodeCtrlMonsForNode nid
424+ fail "processCtrlMsg: linking not implemented"
420− forM_ (Map.toList localLinks) $ \(_them, _uss) -> do
426+ forM_ (Map.toList mons) $ \(them, uss) -> do
421− fail "disconnected: linking not implemented"
423− forM_ (Map.toList localMons) $ \(them, uss) -> do
424 forM_ (Map.toList uss) $ \(us, refs) -> do
427 forM_ (Map.toList uss) $ \(us, refs) -> do
425 -- We only need to notify local processes
428 -- We only need to notify local processes
426− when (processNodeId us == localNodeId node) $ do
429+ when (processNodeId us == localNodeId node) . liftIO $ do
427 let lpid = processLocalId us
430 let lpid = processLocalId us
428 rpid = ProcessId nid them
431 rpid = ProcessId nid them
429 forM_ refs $ \ref -> do
432 forM_ refs $ \ref -> do
430 mProc <- withMVar (localState node) $
433 mProc <- withMVar (localState node) $
431 return . (^. localProcessWithId lpid)
434 return . (^. localProcessWithId lpid)
432− forM_ mProc $ \proc ->
435+ forM_ mProc $ \proc ->
433 enqueue (processQueue proc) . createMessage $
436 enqueue (processQueue proc) . createMessage $
434 ProcessDied ref rpid reason
437 ProcessDied ref rpid reason
436− return . (nodeCtrlLinksForNode nid ^= Map.empty)
439+ modify $ (nodeCtrlLinksForNode nid ^= Map.empty)
437 . (nodeCtrlMonsForNode nid ^= Map.empty)
440 . (nodeCtrlMonsForNode nid ^= Map.empty)
442+-- [Unified: Table 12, top rule]
443+processCtrlMsg (NodeCtrlMsg _from (Died (Left pid) reason)) = do
445+ links <- gets (^. nodeCtrlLinksForProcess pid)
446+ mons <- gets (^. nodeCtrlMonsForProcess pid)
448+ forM_ links $ \_us ->
449+ fail "processCtrlMsg: linking not implemented"
451+ forM_ (Map.toList mons) $ \(us, refs) ->
452+ forM_ refs $ \ref -> do
453+ let msg = createMessage $ ProcessDied ref pid reason
454+ if processNodeId us == localNodeId node
456+ let lpid = processLocalId us
457+ mProc <- withMVar (localState node) $
458+ return . (^. localProcessWithId lpid)
459+ forM_ mProc $ \proc ->
460+ enqueue (processQueue proc) msg
462+ ctrlSendTo (Right . processNodeId $ us) . BSL.toChunks . encode $
464+ { ctrlMsgSender = Right (localNodeId node) -- TODO: why the change in sender? How does that affect 'reconnect' semantics?
465+ , ctrlMsgSignal = Died (Left pid) reason
468+ modify $ (nodeCtrlLinksForProcess pid ^= Set.empty)
469+ . (nodeCtrlMonsForProcess pid ^= Map.empty)
441--------------------------------------------------------------------------------
471--------------------------------------------------------------------------------
442-- Initialization --
472-- Initialization --
@@ -462,7 +492,8 @@ newLocalNode transport = do
462 , localState = state
492 , localState = state
463 , localCtrlChan = ctrlChan
493 , localCtrlChan = ctrlChan
465− void . forkIO $ nodeController node
495+ void . forkIO $ evalStateT (runReaderT (unNodeCtrl nodeController) node)
466 void . forkIO $ handleIncomingMessages node
497 void . forkIO $ handleIncomingMessages node
467 return node
498 return node
@@ -474,59 +505,77 @@ runProcess node proc = do
475forkProcess :: LocalNode -> Process () -> IO ProcessId
506forkProcess :: LocalNode -> Process () -> IO ProcessId
476forkProcess node proc = do
507forkProcess node proc = do
477− queue <- newCQueue
508+ queue <- newCQueue
478− lproc <- modifyMVar (localState node) $ \st -> do
509+ lproc <- modifyMVar (localState node) $ \st -> do
479− let lpid = LocalProcessId { lpidCounter = st ^. localPidCounter
510+ let lpid = LocalProcessId { lpidCounter = st ^. localPidCounter
480− , lpidUnique = st ^. localPidUnique
511+ , lpidUnique = st ^. localPidUnique
513+ let pid = ProcessId { processNodeId = localNodeId node
514+ , processLocalId = lpid
516+ pst <- newMVar LocalProcessState { _connections = Map.empty
518+ let lproc = LocalProcess { processQueue = queue
519+ , processNode = node
521+ , processState = pst
482− let pid = ProcessId { processNodeId = localNodeId node
483− , processLocalId = lpid
485− pst <- newMVar LocalProcessState { _connections = Map.empty
487− let lproc = LocalProcess { processQueue = queue
488− , processNode = node
490− , processState = pst
492− -- TODO: if the counter overflows we should pick a new unique
523+ -- TODO: if the counter overflows we should pick a new unique
493− return ( (localProcessWithId lpid ^= Just lproc)
524+ return ( (localProcessWithId lpid ^= Just lproc)
494− . (localPidCounter ^: (+ 1))
525+ . (localPidCounter ^: (+ 1))
496− , lproc
527+ , lproc
498− void . forkIO $ runLocalProcess lproc proc
530+ runLocalProcess lproc proc
531+ -- [Unified: Table 4]
532+ let pid = processId lproc
533+ writeChan (localCtrlChan node) $ NodeCtrlMsg
534+ { ctrlMsgSender = Left pid
535+ , ctrlMsgSignal = Died (Left pid) DiedNormal
499− return (processId lproc)
537+ return (processId lproc)
501handleIncomingMessages :: LocalNode -> IO ()
539handleIncomingMessages :: LocalNode -> IO ()
502−handleIncomingMessages node = go [] Map.empty
540+handleIncomingMessages node = go [] Map.empty Set.empty
542+ go :: [NT.ConnectionId] -- ^ Connections whose purpose we don't yet know
504− go :: [NT.ConnectionId] -> Map NT.ConnectionId LocalProcess -> IO ()
543+ -> Map NT.ConnectionId LocalProcess -- ^ Connections to local processes
505− go halfOpenConns openConns = do
544+ -> Set NT.ConnectionId -- ^ Connections to our controller
546+ go uninitConns procConns ctrlConns = do
506 event <- NT.receive endpoint
547 event <- NT.receive endpoint
507 case event of
548 case event of
508 NT.ConnectionOpened cid _rel _theirAddr ->
549 NT.ConnectionOpened cid _rel _theirAddr ->
509− go (cid : halfOpenConns) openConns
550+ go (cid : uninitConns) procConns ctrlConns
510 NT.Received cid payload ->
551 NT.Received cid payload ->
511− case Map.lookup cid openConns of
552+ case Map.lookup cid procConns of
512 Just proc -> do
553 Just proc -> do
513 let msg = payloadToMessage payload
554 let msg = payloadToMessage payload
514 enqueue (processQueue proc) msg
555 enqueue (processQueue proc) msg
515− go halfOpenConns openConns
556+ go uninitConns procConns ctrlConns
516− Nothing -> if cid `elem` halfOpenConns
557+ Nothing -> if cid `Set.member` ctrlConns
517− then do
558+ then writeChan ctrlChan (decode . BSL.fromChunks $ payload)
559+ else if cid `elem` uninitConns
518− let Just lpid = payloadToId payload
560+ then case payloadToId payload of
519− mProc <- withMVar state $ return . (^. localProcessWithId lpid)
562+ mProc <- withMVar state $ return . (^. localProcessWithId lpid)
520− case mProc of
563+ case mProc of
521− Just proc ->
564+ Just proc ->
522− go (List.delete cid halfOpenConns)
565+ go (List.delete cid uninitConns)
523− (Map.insert cid proc openConns)
566+ (Map.insert cid proc procConns)
569+ fail "handleIncomingMessages: TODO 1"
524 Nothing ->
570 Nothing ->
525− fail "handleIncomingMessages: TODO 1"
571+ go (List.delete cid uninitConns)
573+ (Set.insert cid ctrlConns)
527− fail "handleIncomingMessages: TODO 2"
574+ else fail "handleIncomingMessages: TODO 2"
528 NT.ConnectionClosed cid ->
575 NT.ConnectionClosed cid ->
529− go (List.delete cid halfOpenConns) (Map.delete cid openConns)
576+ go (List.delete cid uninitConns)
577+ (Map.delete cid procConns)
578+ (Set.delete cid ctrlConns)
530 NT.ErrorEvent (NT.TransportError (NT.EventConnectionLost theirAddr _) _) -> do
579 NT.ErrorEvent (NT.TransportError (NT.EventConnectionLost theirAddr _) _) -> do
531 -- TODO: we could cache this information
580 -- TODO: we could cache this information
532 pids <- withMVar state $ \st ->
581 pids <- withMVar state $ \st ->
@@ -543,6 +592,7 @@ handleIncomingMessages node = go [] Map.empty
544 state = localState node
593 state = localState node
545 endpoint = localEndPoint node
594 endpoint = localEndPoint node
595+ ctrlChan = localCtrlChan node
547--------------------------------------------------------------------------------
597--------------------------------------------------------------------------------
548-- Auxiliary functions --
598-- Auxiliary functions --
@@ -598,8 +648,10 @@ sendBinary them conn payload = do
598 liftIO $ remoteProcessFailed node them
648 liftIO $ remoteProcessFailed node them
600sendMessage :: ProcessId -> NT.Connection -> Message -> Process ()
650sendMessage :: ProcessId -> NT.Connection -> Message -> Process ()
601−sendMessage them conn (Message fp enc) =
651+sendMessage them conn = sendBinary them conn . messageToPayload
653+messageToPayload :: Message -> [BSS.ByteString]
602− sendBinary them conn $ encodeFingerprint fp : BSL.toChunks enc
654+messageToPayload (Message fp enc) = encodeFingerprint fp : BSL.toChunks enc
604payloadToMessage :: [BSS.ByteString] -> Message
656payloadToMessage :: [BSS.ByteString] -> Message
605payloadToMessage payload = Message fp msg
657payloadToMessage payload = Message fp msg
@@ -696,6 +748,7 @@ instance Binary ProcessSignal where
696 header <- getWord8
748 header <- getWord8
697 case header of
749 case header of
698 0 -> liftM2 Monitor get get
750 0 -> liftM2 Monitor get get
751+ 1 -> liftM2 Died get get
699 _ -> fail "ProcessSignal.get: invalid"
752 _ -> fail "ProcessSignal.get: invalid"
701instance Binary DiedReason where
754instance Binary DiedReason where