ARCPRuntime.register(_:) appends to registeredHandlers and then iterates the current jobManagers to fan the handler out at Sources/ARCP/Runtime/ARCPRuntime.swift:78. ARCPRuntime.acceptSession(over:) first calls await runHandshake(...), then constructs the session's JobManager, iterates the snapshot of registeredHandlers with for handler in registeredHandlers { await jobManager.register(handler) }, and only later assigns jobManagers[info.sessionId] = jobManager at Sources/ARCP/Runtime/ARCPRuntime.swift:118. Because both methods are isolated to the same actor but acceptSession is full of await suspension points, a concurrent runtime.register(newHandler) can interleave between any iteration of that loop: it appends newHandler to registeredHandlers and then iterates jobManagers.values, which does not yet contain the in-flight session. The for loop in acceptSession was already iterating the value-type snapshot of registeredHandlers, so it never picks up newHandler either. The session ends up missing a handler that every other open session received, and the omission persists until the session closes and reopens.
Fix prompt: Eliminate the race by either holding off any session bookkeeping until after both registeredHandlers and jobManagers are updated atomically, or by re-syncing the new session's JobManager from the latest registeredHandlers immediately after the dictionary insertion. A straightforward fix is to insert jobManagers[info.sessionId] = jobManager first and then iterate the current registeredHandlers again from inside acceptSession, since both steps run on the runtime actor and a concurrent register call cannot observe jobManagers without also reaching the new entry. Add a concurrency test that interleaves runtime.register(newHandler) between handshake completion and the registration loop and asserts the new handler is callable through the new session, plus a regression test confirming previously open sessions still receive the handler.
ARCPRuntime.register(_:)appends toregisteredHandlersand then iterates the currentjobManagersto fan the handler out atSources/ARCP/Runtime/ARCPRuntime.swift:78.ARCPRuntime.acceptSession(over:)first callsawait runHandshake(...), then constructs the session'sJobManager, iterates the snapshot ofregisteredHandlerswithfor handler in registeredHandlers { await jobManager.register(handler) }, and only later assignsjobManagers[info.sessionId] = jobManageratSources/ARCP/Runtime/ARCPRuntime.swift:118. Because both methods are isolated to the same actor butacceptSessionis full ofawaitsuspension points, a concurrentruntime.register(newHandler)can interleave between any iteration of that loop: it appendsnewHandlertoregisteredHandlersand then iteratesjobManagers.values, which does not yet contain the in-flight session. Theforloop inacceptSessionwas already iterating the value-type snapshot ofregisteredHandlers, so it never picks upnewHandlereither. The session ends up missing a handler that every other open session received, and the omission persists until the session closes and reopens.Fix prompt: Eliminate the race by either holding off any session bookkeeping until after both
registeredHandlersandjobManagersare updated atomically, or by re-syncing the new session'sJobManagerfrom the latestregisteredHandlersimmediately after the dictionary insertion. A straightforward fix is to insertjobManagers[info.sessionId] = jobManagerfirst and then iterate the currentregisteredHandlersagain from insideacceptSession, since both steps run on the runtime actor and a concurrentregistercall cannot observejobManagerswithout also reaching the new entry. Add a concurrency test that interleavesruntime.register(newHandler)between handshake completion and the registration loop and asserts the new handler is callable through the new session, plus a regression test confirming previously open sessions still receive the handler.