pub async fn reserve_runner_core(
runners: &Arc<RwLock<HashMap<String, AgentRunner>>>,
senders: &Arc<RwLock<HashMap<String, Sender<AgentEvent>>>>,
session_id: &str,
event_sender: &Sender<AgentEvent>,
) -> ReserveOutcomeExpand description
Shared runner-reservation core used by BOTH the server’s reserve_runner
and the engine’s try_reserve_runner, so the idle-eviction sender
re-assert can never drift between the two paths again (#346).
While holding the runners write lock it:
- short-circuits with
ReserveOutcome::AlreadyRunningif aRunningrunner already exists; - otherwise replaces any stale runner with a fresh
Runningone whoseevent_senderisevent_sender; - re-asserts
event_senderintosenders(entry().or_insert_with), STILL holding therunnerswrite lock.
Step 3 closes the idle-sweep TOCTOU: a caller obtains the sender via
get_or_create_event_sender and then reserves, but the 60s idle sweep can
evict that session’s sender in between (it drops a terminal runner and its
sender together under the same runners ⊃ senders lock order). Without the
re-assert, a resumed / re-executed run would publish to a channel no longer
registered in senders, and a later subscriber’s get_or_create_event_sender
would mint a different channel and silently miss every event of that run.
Because both this re-assert and the sweep acquire runners first, they are
mutually exclusive, and a freshly-inserted Running runner is never swept —
so the re-asserted sender is durable for the run.