Skip to content

Cancel-deadline escalation Task is detached and unowned; it leaks past job completion and is never cancelled (§7.4) #96

@nficano

Description

@nficano

Category: bug Severity: minor
Location: Sources/ARCP/Runtime/JobManager.swift:346-349
Spec: n/a

What

handleCancel spawns a detached Task that sleeps for deadlineMs then calls escalateCancelIfNeeded. This task is not stored in the JobRecord, so shutdown() (which cancels runTask/heartbeatTask) cannot cancel it. If the session ends or the job completes normally before the deadline, the task survives and fires escalateCancelIfNeeded after the fact; the guard !record.state.isTerminal saves correctness, but with a long deadlineMs (client-controlled) the runtime holds the closure and a strong-ish reference for that whole duration regardless of session lifetime. A client can submit many cancels with huge deadlineMs to accumulate sleeping tasks.

Evidence

Task { [weak self, deadline = payload.deadlineMs] in
    try? await Task.sleep(for: .milliseconds(deadline))
    await self?.escalateCancelIfNeeded(jobId: jobId, reason: "deadline elapsed")
}

No handle retained; shutdown() never cancels it; deadline is taken straight from untrusted payload.deadlineMs with no upper bound.

Proposed fix

  1. Store the escalation task in the JobRecord and cancel it in transition(to: terminalState) and shutdown().
  2. Clamp payload.deadlineMs to a sane maximum (and reject negative values — Task.sleep(for: .milliseconds(negative)) is also a concern).

Acceptance criteria

  • Escalation tasks are cancelled on job termination and session shutdown.
  • deadlineMs is bounded.

Metadata

Metadata

Assignees

No one assigned

    Labels

    audit/bugAudit: bug / inefficiency / oversized filesev/minorSeverity: minor

    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