fix: set correct node id on incoming messages

This commit is contained in:
2026-06-09 19:51:29 +02:00
parent b6e5e12770
commit 25cadc04ad
3 changed files with 70 additions and 38 deletions
+41 -18
View File
@@ -5,6 +5,7 @@ module Control.Actor.Network
( spawnConnTree ( spawnConnTree
, handleNewConn , handleNewConn
, getOrCreateConn , getOrCreateConn
, connect
, findByUUID , findByUUID
, routeRemoteDeath , routeRemoteDeath
, validateAndDispatch , validateAndDispatch
@@ -59,10 +60,10 @@ connDeathFn peer _ = do
chClose ch chClose ch
return Stop return Stop
routerActorFn :: ByteString -> Actor () () routerActorFn :: NodeAddr -> ByteString -> Actor () ()
routerActorFn raw = do routerActorFn senderAddr raw = do
let nm = decode raw :: NetworkMessage let nm = decode raw :: NetworkMessage
valid <- liftRuntime $ validateAndDispatch nm valid <- liftRuntime $ validateAndDispatch senderAddr nm
unless valid $ liftIO $ putStrLn "router: dropping invalid message" unless valid $ liftIO $ putStrLn "router: dropping invalid message"
return (Nothing, ()) return (Nothing, ())
@@ -74,7 +75,7 @@ spawnConnTree peer ch = do
routerCell <- liftIO newEmptyTMVarIO routerCell <- liftIO newEmptyTMVarIO
connCell <- liftIO newEmptyTMVarIO connCell <- liftIO newEmptyTMVarIO
supervise' OneForAll supervise' OneForAll
[ childWithRef routerActorFn stopOnDeath () routerCell [ childWithRef (routerActorFn peer) stopOnDeath () routerCell
, ChildSpec , ChildSpec
{ csRun = \target -> do { csRun = \target -> do
ref <- spawnActor connActorFn (connDeathFn peer) ch ref <- spawnActor connActorFn (connDeathFn peer) ch
@@ -157,28 +158,33 @@ findByUUID uuid actors =
[] -> Nothing [] -> Nothing
(_, (_, r)):_ -> Just r (_, (_, r)):_ -> Just r
routeRemoteDeath :: ActorId -> RemoteExitReason -> RuntimeM () routeRemoteDeath :: NodeAddr -> ActorId -> RemoteExitReason -> RuntimeM ()
routeRemoteDeath deadId reason = do routeRemoteDeath senderAddr (ActorId _ uuid) reason = do
rt <- ask rt <- ask
liftIO $ do liftIO $ do
actors <- readTVarIO (rtActors rt) localNodeId <- atomically $ do
let exitReason = case reason of table <- readTVar (rtNodeTable rt)
return $ Map.foldrWithKey (\k v acc -> if v == senderAddr then Just k else acc) Nothing table
let deadId = ActorId (case localNodeId of { Just n -> n; Nothing -> 0 }) uuid
exitReason = case reason of
RNormal -> Normal RNormal -> Normal
RKilled -> Killed RKilled -> Killed
RException s -> Exception (error s) RException s -> Exception (error s)
dm = DeathMessage deadId exitReason dm = DeathMessage deadId exitReason
actors <- readTVarIO (rtActors rt)
forM_ (Map.elems actors) $ \(_, SomeActorRef ref) -> forM_ (Map.elems actors) $ \(_, SomeActorRef ref) ->
case ref of case ref of
LocalRef {arDeathQ, arState} -> do LocalRef {arDeathQ, arState} -> do
links <- readTVarIO (asLinks arState) links <- readTVarIO (asLinks arState)
forM_ links $ \case forM_ links $ \case
RemoteTarget rid _ | rid == deadId -> RemoteTarget (ActorId _ uid) peerAddr
atomically $ writeTQueue arDeathQ dm | uid == uuid && peerAddr == senderAddr ->
atomically $ writeTQueue arDeathQ dm
_else -> return () _else -> return ()
RemoteRef _ -> return () RemoteRef _ -> return ()
validateAndDispatch :: NetworkMessage -> RuntimeM Bool validateAndDispatch :: NodeAddr -> NetworkMessage -> RuntimeM Bool
validateAndDispatch nm = case nm of validateAndDispatch senderAddr nm = case nm of
NMHandshake _ -> return False NMHandshake _ -> return False
@@ -231,23 +237,40 @@ validateAndDispatch nm = case nm of
return False return False
NMDeath deadId reason -> do NMDeath deadId reason -> do
routeRemoteDeath deadId reason routeRemoteDeath senderAddr deadId reason
return True return True
-- System event handler -- System event handler
sysHandlerFn :: NetworkMessage -> Actor () () sysHandlerFn :: NetworkMessage -> Actor () ()
sysHandlerFn (NMDeath deadId reason) = do
liftRuntime $ routeRemoteDeath deadId reason
return (Nothing, ())
sysHandlerFn _ = return (Nothing, ()) sysHandlerFn _ = return (Nothing, ())
-- Node connection
-- | Connect to a remote node and return its locally-assigned NodeId.
-- NodeId 0 is always self; remote nodes get ids starting from 1.
-- A suggested id is honoured if it is free (non-zero, not already in use).
connect :: Maybe NodeId -> NodeAddr -> RuntimeM NodeId
connect suggestedId peer = do
rt <- ask
nodeId <- liftIO $ atomically $ do
table <- readTVar (rtNodeTable rt)
nid <- readTVar (rtNextNodeId rt)
let assigned = case suggestedId of
Just n | n /= 0 && not (Map.member n table) -> n
_else -> nid
writeTVar (rtNextNodeId rt) (max (assigned + 1) nid)
modifyTVar (rtNodeTable rt) (Map.insert assigned peer)
return assigned
void $ getOrCreateConn peer
return nodeId
-- Runtime initialization -- Runtime initialization
initRuntime :: NodeAddr -> IO Runtime initRuntime :: NodeAddr -> IO Runtime
initRuntime myAddr = do initRuntime myAddr = do
transport <- createTCPTransport myAddr (transport, actualAddr) <- createTCPTransport myAddr
rt0 <- newRuntime myAddr transport rt0 <- newRuntime actualAddr transport
let rt = rt0 { rtSendRemote = \addr nm -> getOrCreateConn addr >>= cast' nm } let rt = rt0 { rtSendRemote = \addr nm -> getOrCreateConn addr >>= cast' nm }
withRuntime rt $ void $ spawnActor sysHandlerFn stopOnDeath () withRuntime rt $ void $ spawnActor sysHandlerFn stopOnDeath ()
tListen transport $ \ch -> do tListen transport $ \ch -> do
+3
View File
@@ -21,6 +21,7 @@ import Data.Map qualified as Map
data Runtime = Runtime data Runtime = Runtime
{ rtNodeId :: NodeAddr { rtNodeId :: NodeAddr
, rtNextNodeId :: TVar NodeId
, rtActors :: TVar (Map.Map ActorId (ThreadId, SomeActorRef)) , rtActors :: TVar (Map.Map ActorId (ThreadId, SomeActorRef))
, rtPending :: TVar (Map.Map CorrelationId (MVar ByteString)) , rtPending :: TVar (Map.Map CorrelationId (MVar ByteString))
, rtNextCorr :: TVar CorrelationId , rtNextCorr :: TVar CorrelationId
@@ -41,10 +42,12 @@ newRuntime myAddr transport = do
pending <- newTVarIO Map.empty pending <- newTVarIO Map.empty
nextCorr <- newTVarIO (0 :: Integer) nextCorr <- newTVarIO (0 :: Integer)
nodeTable <- newTVarIO Map.empty nodeTable <- newTVarIO Map.empty
nextNid <- newTVarIO (1 :: NodeId)
conns <- newTVarIO Map.empty conns <- newTVarIO Map.empty
promises <- newTVarIO Map.empty promises <- newTVarIO Map.empty
return Runtime return Runtime
{ rtNodeId = myAddr { rtNodeId = myAddr
, rtNextNodeId = nextNid
, rtActors = actors , rtActors = actors
, rtPending = pending , rtPending = pending
, rtNextCorr = nextCorr , rtNextCorr = nextCorr
+26 -20
View File
@@ -28,6 +28,7 @@ import Network.Socket
, listen , listen
, setSocketOption , setSocketOption
, socket , socket
, socketPort
) )
import Network.Socket.ByteString.Lazy (recv, sendAll) import Network.Socket.ByteString.Lazy (recv, sendAll)
@@ -72,7 +73,7 @@ connectTcp (NodeAddr host port) = do
connect sock (addrAddress a) connect sock (addrAddress a)
return sock return sock
listenTcp :: NodeAddr -> IO Socket listenTcp :: NodeAddr -> IO (Socket, NodeAddr)
listenTcp (NodeAddr host port) = do listenTcp (NodeAddr host port) = do
let hints = defaultHints {addrFlags = [AI_PASSIVE], addrSocketType = Stream} let hints = defaultHints {addrFlags = [AI_PASSIVE], addrSocketType = Stream}
addrs <- getAddrInfo (Just hints) (Just host) (Just (show port)) addrs <- getAddrInfo (Just hints) (Just host) (Just (show port))
@@ -83,24 +84,29 @@ listenTcp (NodeAddr host port) = do
setSocketOption sock ReuseAddr 1 setSocketOption sock ReuseAddr 1
bind sock (addrAddress a) bind sock (addrAddress a)
listen sock 128 listen sock 128
return sock actualPort <- fromIntegral <$> socketPort sock
return (sock, NodeAddr host actualPort)
createTCPTransport :: NodeAddr -> IO Transport -- | Create a TCP transport bound to the given address.
-- Pass port 0 to let the OS pick a free port.
-- Returns the transport and the address actually bound (with the real port).
createTCPTransport :: NodeAddr -> IO (Transport, NodeAddr)
createTCPTransport myAddr = do createTCPTransport myAddr = do
lsock <- listenTcp myAddr (lsock, actualAddr) <- listenTcp myAddr
return Transport let transport = Transport
{ tConnect = \peer -> do { tConnect = \peer -> do
sock <- connectTcp peer sock <- connectTcp peer
return ConnHandle return ConnHandle
{ chSend = sendFramed sock { chSend = sendFramed sock
, chRecv = recvFramed sock , chRecv = recvFramed sock
, chClose = close sock , chClose = close sock
} }
, tListen = \callback -> void $ forkIO $ forever $ do , tListen = \callback -> void $ forkIO $ forever $ do
(csock, _) <- accept lsock (csock, _) <- accept lsock
void $ forkIO $ callback ConnHandle void $ forkIO $ callback ConnHandle
{ chSend = sendFramed csock { chSend = sendFramed csock
, chRecv = recvFramed csock , chRecv = recvFramed csock
, chClose = close csock , chClose = close csock
} }
} }
return (transport, actualAddr)