Skip to content

Fix race where ARCPRuntime.register can skip a session in mid-handshake #61

@nficano

Description

@nficano

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingseverity:mediumMedium severity issue

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions