Skip to content

fix(EXC): Time out stop_canister requests without a call ID - #11430

Open
mraszyk wants to merge 2 commits into
masterfrom
mraszyk/stop-canister-timeout-without-call-id
Open

fix(EXC): Time out stop_canister requests without a call ID#11430
mraszyk wants to merge 2 commits into
masterfrom
mraszyk/stop-canister-timeout-without-call-id

Conversation

@mraszyk

@mraszyk mraszyk commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Problem

A stop_canister request that predates stop-canister call IDs can never time out.

ExecutionEnvironment::process_stopping_canisters() determines whether a stop context has expired by looking its creation time up in the subnet call context manager, keyed by call ID. A stop context whose call_id is None has no such entry, so the predicate returns false unconditionally:

// Should only happen for old stop requests that existed
// before call ids were added.
None => false,

Call IDs were introduced in 6260fc5 (2023-08-18) and the timeout itself in 4ee1803 (2023-11-22), so every such stop context has been pending for over two years — far beyond stop_canister_timeout_duration (5 minutes) — with no way to ever be responded to.

These still exist on mainnet. On subnet yinp6-35cfo-wgcd2-oc4ty-2kqpf-t4dul-rfk33-fsq3r-mfmua-m2ngh-jqe, a replica reports:

scheduler_stop_canister_calls_without_call_id             6
replicated_state_subnet_call_contexts{type="stop_canister"} 0

i.e. all six pending stops on that subnet are call-ID-less ones, tracked only on the canisters themselves.

Change

Expire stop contexts without a call ID unconditionally. Nothing else was needed: try_stop_canister() and reply_to_stop_context() already handle both StopCanisterContext variants, so an ingress-originated request gets ErrorCode::StopCanisterRequestTimeout and a canister-originated one gets a SysTransient reject, exactly as for a request with a call ID.

Also adds can_timeout_stop_canister_requests_without_call_id, which pins that a call-ID-less stop context expires in the next round without advancing the time, while a stop context with a call ID that has not yet reached the timeout is left alone.

Effect on mainnet

In the first round after this is rolled out, every dormant stop context reaches its originator. For ingress-originated ones the reply goes to an ingress history entry nobody is waiting on. For canister-originated ones, a reject is delivered to a callback that has been dormant for years, but it is a behavior change for canisters whose controllers are not expecting one.

Stop contexts predating call IDs (introduced in 6260fc5, 2023-08) can
never be timed out: `process_stopping_canisters()` looks up the request
time via `get_time_for_stop_canister_call(call_id)`, so the `call_id:
None` arm of its `is_expired` predicate returns `false` unconditionally,
no matter how much time has passed.

Since no such stop request can be younger than two years, and all of
them are therefore long past `stop_canister_timeout_duration`, expire
them unconditionally instead.

There are still such stop contexts on mainnet, e.g. 6 of them on subnet
yinp6-35cfo-wgcd2-oc4ty-2kqpf-t4dul-rfk33-fsq3r-mfmua-m2ngh-jqe, as
reported by `scheduler_stop_canister_calls_without_call_id`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Coverage should verify replies for both ingress- and canister-originated legacy contexts.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Expires legacy stop_canister contexts without call IDs to unblock permanently pending requests.

Changes:

  • Immediately times out call-ID-less stop contexts.
  • Adds regression coverage for legacy ingress contexts.
File summaries
File Description
execution_environment.rs Marks legacy contexts as expired.
scheduler/tests.rs Tests expiration without advancing time.
Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread rs/execution_environment/src/scheduler/tests.rs
Address review feedback: the test only asserted that the stop contexts
without a call ID were dropped, and covered only the ingress originated
one.

Assert that the user is told their request failed with
`StopCanisterRequestTimeout`; and cover the canister originated case,
checking that the queued response is a `SysTransient` reject on the
callback of the original stop request, with its cycles refunded.

The canister stop context is obtained by clearing the call ID of an
actual stop request, rather than by adding a synthetic stop context:
responding to it requires the output queue slot that was reserved when
that request was inducted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The focused behavior change is correct and comprehensively covered by the regression test.

Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@mraszyk
mraszyk marked this pull request as ready for review September 3, 2026 11:51
@mraszyk
mraszyk requested a review from a team as a code owner September 3, 2026 11:51
@zeropath-ai

zeropath-ai Bot commented Sep 3, 2026

Copy link
Copy Markdown

No security or compliance issues detected. Reviewed everything up to 9af914a.

Security Overview
Detected Code Changes
Change Type Relevant files
Bug Fix ► rs/execution_environment/src/execution_environment.rs
    Expire old stop requests unconditionally when no call id is present (with TODO: remove optional call id)
Enhancement ► rs/execution_environment/src/scheduler/tests.rs
    Add tests for stopping canisters without call ids and corresponding timeout behavior

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants