Category: bug Severity: major
Location: lib/src/main/kotlin/dev/arcp/runtime/ARCPRuntime.kt:143-175
What
A session entry is inserted into the sessions ConcurrentHashMap once authenticated, but it is only ever removed by the explicit SessionClose branch of handleEnvelope. When the transport simply drops (the dispatch loop completes or throws and is swallowed at line 173) the entry is never removed; neither does evict() (lines 769-782) remove it. A long-lived runtime accepting many short or dropped connections accumulates Authenticated session state without bound — the same retention defect class as the closed #60 (JobInventory) but on the session map.
Evidence
public fun accept(transport: Transport): Job = scope.launch {
val opener =
try {
transport.receive().first()
} catch (e: CancellationException) {
throw e
} catch (e: Exception) {
log.warn(e) { "transport closed before session.open" }
return@launch
}
val outcome = handleHandshake(opener)
transport.send(outcome.reply)
if (outcome.session is SessionState.Authenticated) {
sessions[outcome.session.sessionId] = outcome.session
runDispatchLoop(transport)
} else {
transport.close()
}
}
private suspend fun runDispatchLoop(transport: Transport) {
try {
transport.receive().collect { env ->
dispatchOne(env, transport)
}
} catch (e: CancellationException) {
throw e
} catch (e: Exception) {
log.info(e) { "session ended" }
}
}
Proposed fix
Remove the session in a finally around runDispatchLoop (and in evict()) keyed on the accepted sessionId, e.g. wrap runDispatchLoop in try { ... } finally { sessions.remove(sessionId) }, so the map entry is released whenever the connection ends regardless of cause.
Acceptance criteria
Category: bug Severity: major
Location:
lib/src/main/kotlin/dev/arcp/runtime/ARCPRuntime.kt:143-175What
A session entry is inserted into the
sessionsConcurrentHashMap once authenticated, but it is only ever removed by the explicit SessionClose branch of handleEnvelope. When the transport simply drops (the dispatch loop completes or throws and is swallowed at line 173) the entry is never removed; neither does evict() (lines 769-782) remove it. A long-lived runtime accepting many short or dropped connections accumulates Authenticated session state without bound — the same retention defect class as the closed #60 (JobInventory) but on the session map.Evidence
Proposed fix
Remove the session in a finally around runDispatchLoop (and in evict()) keyed on the accepted sessionId, e.g. wrap runDispatchLoop in try { ... } finally { sessions.remove(sessionId) }, so the map entry is released whenever the connection ends regardless of cause.
Acceptance criteria