Skip to content

fix(openai): cancel timed-out generation before releasing its lane - #1150

Merged
i386 merged 5 commits into
mainfrom
agent/cancel-timed-out-generation
Aug 5, 2026
Merged

fix(openai): cancel timed-out generation before releasing its lane#1150
i386 merged 5 commits into
mainfrom
agent/cancel-timed-out-generation

Conversation

@i386

@i386 i386 commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator

Problem

When an OpenAI request timed out, dropping the async request future did not stop the blocking generation worker. The request could return and release its concurrency lane while that worker was still using the model runtime.

A subsequent request could then acquire the same lane and overlap with stale work from the timed-out request, breaking the lane's isolation and making timeout behavior dependent on worker timing.

Fix

Cancellation is propagated through the OpenAI and Skippy layers, and the timed-out worker is allowed to stop before its lane permit is released.

This is necessary because a blocking worker continues after its async wrapper is dropped; releasing the lane immediately would only hide the work, not cancel it. Tying lane release to confirmed worker termination preserves the existing concurrency contract.

Validation

OpenAI tests pass (154/154) and Skippy Server tests pass (353/353), including the single-lane timeout regression.

Summary by CodeRabbit

  • New Features

    • Added cancellation support for chat, completion, response, and streaming generation requests.
    • Requests can now stop promptly when cancelled, timed out, or abandoned.
    • Cancellation context is preserved through retries, hooks, guardrails, and local generation.
  • Bug Fixes

    • Prevented cancelled requests from holding generation capacity longer than necessary.
    • Improved cancellation handling during token generation and proposal execution.
    • Improved session admission and batch-size handling for generation workloads.
  • Tests

    • Added coverage for cancellation during active and non-streaming generation.

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 02d018a5-05aa-48a0-85aa-ec54321f4e2e

📥 Commits

Reviewing files that changed from the base of the PR and between b536993 and b95c08d.

📒 Files selected for processing (4)
  • crates/skippy-server/src/frontend/generation_flow/text_generation.rs
  • crates/skippy-server/src/frontend/linear_proposal/execution.rs
  • crates/skippy-server/src/frontend/local_generation/linear_decode.rs
  • crates/skippy-server/src/frontend/local_generation/token_generation.rs
🚧 Files skipped from review as they are similar to previous changes (4)
  • crates/skippy-server/src/frontend/local_generation/token_generation.rs
  • crates/skippy-server/src/frontend/local_generation/linear_decode.rs
  • crates/skippy-server/src/frontend/generation_flow/text_generation.rs
  • crates/skippy-server/src/frontend/linear_proposal/execution.rs

📝 Walkthrough

Walkthrough

The change adds asynchronous cancellation state, propagates request contexts through OpenAI backends and routes, and stops Skippy generation during admission, execution, and token emission. It also separates admitted and active session batch-size queries.

Changes

Request cancellation flow

Layer / File(s) Summary
Cancellation and context contracts
crates/openai-frontend/src/backend.rs, crates/openai-frontend/src/guardrails/*, crates/openai-frontend/src/hooks.rs
CancellationToken supports asynchronous waiting. Backend wrappers propagate OpenAiRequestContext through chat and completion calls.
Router cancellation handling
crates/openai-frontend/src/router.rs
Chat, Responses, and completion routes cancel backend work on timeout or dropped futures. Tests cover both cases.
Generation admission and worker cancellation
crates/skippy-server/src/frontend/admission.rs, crates/skippy-server/src/frontend/backend.rs
Generation admission and blocking workers respond to cancellation while preserving permit handling.
Generation pipeline cancellation checks
crates/skippy-server/src/frontend/generation_flow/*, crates/skippy-server/src/frontend/linear_proposal/*, crates/skippy-server/src/frontend/local_generation/*
Text generation and linear-proposal execution stop before processing and during token emission when cancellation occurs.
Session queries and cancellation validation
crates/skippy-server/src/runtime_state/frame_operations.rs, crates/skippy-server/src/kv_integration/resident_prefix.rs, crates/skippy-server/src/frontend/tests/generation.rs, crates/skippy-server/src/frontend/local_generation/tests.rs
Session batch-size APIs distinguish admission from active-session queries. Tests cover cancellation ordering and multi-token prefill behavior.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Router
  participant OpenAiRequestContext
  participant SkippyBackend
  participant GenerationTokenBudget
  participant GenerationWorker
  Router->>SkippyBackend: Start context-aware request
  SkippyBackend->>GenerationTokenBudget: Acquire generation permit
  GenerationTokenBudget-->>SkippyBackend: Return permit or cancellation
  SkippyBackend->>GenerationWorker: Run generation with cancellation token
  Router->>OpenAiRequestContext: Cancel on timeout or drop
  OpenAiRequestContext-->>GenerationWorker: Notify cancellation
  GenerationWorker-->>SkippyBackend: Return result or cancellation
  SkippyBackend-->>Router: Return backend response or error
Loading

Possibly related PRs

Suggested reviewers: michaelneale, ndizazzo

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 27.38% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes cancellation of timed-out generation before releasing the concurrency lane, which is the pull request's main change.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch agent/cancel-timed-out-generation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@i386
i386 marked this pull request as ready for review August 2, 2026 06:28
@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

This pull request is currently a draft. Reviews will not take place until the PR is marked as ready for review.

@github-actions
github-actions Bot requested a review from michaelneale August 2, 2026 06:28
@i386
i386 force-pushed the agent/cancel-timed-out-generation branch from e0f0fae to 787f8e6 Compare August 2, 2026 07:16
@i386
i386 force-pushed the agent/cancel-timed-out-generation branch from 787f8e6 to cbcc9c2 Compare August 2, 2026 07:37
@i386

i386 commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator Author

@michaelneale @ndizazzo impactful enough bug that I'd like to do a release after we review and merge

@michaelneale michaelneale left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Oh that is a nasty one. I bet it bites often and mysteriously.

@ndizazzo
ndizazzo self-requested a review August 2, 2026 18:20

@ndizazzo ndizazzo left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Checks out as clean to me

@ndizazzo

ndizazzo commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
crates/skippy-server/src/frontend/linear_proposal.rs (1)

658-680: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Cancellation on the first token produces a misleading error and skips the repair path.

The new check at lines 659-662 breaks before committed_tokens.push(token). If cancellation is observed on the first iteration, committed_tokens stays empty. Control then reaches line 676 and returns "linear proposal classifier committed no target prediction", which discards the callback_error you just set and misreports the cause.

The early return also skips finish_linear_proposal_after_repair at line 689. verify_tokens_sampled has already advanced the session to position_after_verification, so the verified rows are never trimmed back on this path.

Return the recorded callback_error when one exists, so cancellation surfaces as "request cancelled" and the empty-commit error stays reserved for the genuine classifier case.

🐛 Proposed fix
         if committed_tokens.is_empty() {
+            if let Some(error) = callback_error {
+                return Err(error);
+            }
             return Err(OpenAiError::backend(
                 "linear proposal classifier committed no target prediction",
             ));
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/skippy-server/src/frontend/linear_proposal.rs` around lines 658 - 680,
Update the post-loop handling in the linear proposal flow around the
committed_tokens check to return the recorded callback_error when present,
including cancellation observed before the first token is committed. Preserve
the existing empty-commit error for genuine classifier results, while ensuring
the callback-error path continues through finish_linear_proposal_after_repair so
verified session rows are trimmed correctly.
crates/skippy-server/src/frontend/local_generation.rs (1)

648-658: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Cancellation is handled two different ways inside the same loop iteration.

The loop-top check at lines 648-654 treats cancellation as a graceful stop. It sets receipt_cancelled = true and breaks, so finalize_generation_receipt marks the observation cancelled and delivers a receipt.

The new checks at lines 658 and 712 treat the same condition as a hard error. They return Err("request cancelled"), so generation_succeeded is false, receipt_cancelled stays false, and the sink receives an abort instead of a cancelled receipt.

These two paths are a few lines apart and can both observe the same cancellation. Make the proposal-path checks break out of the loop like the loop-top check, so receipt reporting stays consistent.

🐛 Proposed fix
                 if let Some(config) = self.linear_proposal_ingress.as_ref()
                     && linear_proposals_enabled
                 {
-                    ensure_request_active(request.cancellation)?;
+                    if request
+                        .cancellation
+                        .is_some_and(openai_frontend::CancellationToken::is_cancelled)
+                    {
+                        receipt_cancelled = true;
+                        break;
+                    }

Apply the same change at line 712.

Also applies to: 712-712

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/skippy-server/src/frontend/local_generation.rs` around lines 648 -
658, Update the cancellation handling in the proposal paths guarded by
linear_proposals_enabled, including both the visible check and the corresponding
check near line 712, to follow the loop-top behavior: set receipt_cancelled and
break rather than propagating ensure_request_active errors. Keep cancellation
receipt finalization consistent across all checks within the generation loop.
🧹 Nitpick comments (3)
crates/skippy-server/src/frontend/local_generation.rs (2)

364-375: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Document why the suffix prefill is chunked here.

RuntimeState::prefill already chunks internally through session.prefill_chunked. The outer loop exists to create cancellation checkpoints between batches, not to satisfy a batch-size constraint. Without a comment, a later change may collapse this back into a single call and remove the cancellation granularity.

The .max(1) guard is correct and required, because slice::chunks panics on a zero chunk size.

📝 Proposed comment
                     let suffix = &prefill_tokens[restored_prefill_tokens..];
+                    // `RuntimeState::prefill` chunks internally. Chunk again at
+                    // the session batch size so cancellation is observed between
+                    // batches instead of only after the whole suffix completes.
                     let batch_size = runtime
                         .session_batch_size(&session_id)
                         .map_err(openai_backend_error)?
                         .max(1);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/skippy-server/src/frontend/local_generation.rs` around lines 364 -
375, Add a concise comment above the suffix chunking loop in the prefill
restoration flow, near `RuntimeState::prefill`, explaining that the outer chunks
provide cancellation checkpoints while `prefill` handles internal chunking;
explicitly retain the `.max(1)` guard because `slice::chunks` rejects a zero
size.

42-50: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

The cancellation-check predicate is duplicated three times under crates/skippy-server/src/frontend/. This PR introduces two identical ensure_request_active definitions plus two inline copies of the same body. The shared root cause is that no module owns this predicate. Define it once in the frontend module and import it at each site.

  • crates/skippy-server/src/frontend/local_generation.rs#L42-L50: keep this definition but move it to the shared frontend module and re-import it here.
  • crates/skippy-server/src/frontend/linear_proposal.rs#L772-L780: delete this identical definition and import the shared helper.
  • crates/skippy-server/src/frontend/generation_flow.rs#L61-L63: replace the inline condition with ensure_request_active(cancellation)?, and apply the same replacement to the second inline copy at lines 90-92.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/skippy-server/src/frontend/local_generation.rs` around lines 42 - 50,
Centralize the cancellation predicate by moving ensure_request_active from
local_generation.rs into the shared frontend module, then import and reuse it in
crates/skippy-server/src/frontend/local_generation.rs:42-50, removing the local
definition there. Delete the duplicate helper and import the shared one in
crates/skippy-server/src/frontend/linear_proposal.rs:772-780. In
crates/skippy-server/src/frontend/generation_flow.rs:61-63, replace both inline
cancellation checks, including the second copy at lines 90-92, with
ensure_request_active(cancellation)?.
crates/skippy-server/src/frontend/backend.rs (1)

578-579: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider reusing run_blocking_generation_worker in the streaming path.

run_generation_stream spawns the blocking worker directly and holds the permit with let _permit = permit;. This repeats the pattern that run_blocking_generation_worker now encapsulates. The helper is generic over the return type, so a () closure fits.

The current code is behaviorally correct. This is a consolidation only. Note that the helper awaits the join handle, while this path deliberately does not await, so the helper would need a spawn-only variant or the returned future would need to be spawned.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/skippy-server/src/frontend/backend.rs` around lines 578 - 579,
Consolidate the streaming worker setup in run_generation_stream by reusing the
permit-lifetime logic from run_blocking_generation_worker where possible.
Preserve the current fire-and-forget behavior: do not await the blocking task
directly; add or use a spawn-only variant, or spawn the helper future, while
retaining the existing () closure behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/skippy-server/src/runtime_state.rs`:
- Around line 254-256: Rename session_batch_size to admit_session_batch_size and
update its existing call sites, distinguishing batch-size lookups that
intentionally admit a session from eviction-time queries. In
evict_resident_prefix_for_decode_batch, avoid invoking the admitting method
before prefill; use a non-admitting lookup or explicitly ensure the session is
active before querying its batch size so capacity errors are not misinterpreted
as a read failure.

---

Outside diff comments:
In `@crates/skippy-server/src/frontend/linear_proposal.rs`:
- Around line 658-680: Update the post-loop handling in the linear proposal flow
around the committed_tokens check to return the recorded callback_error when
present, including cancellation observed before the first token is committed.
Preserve the existing empty-commit error for genuine classifier results, while
ensuring the callback-error path continues through
finish_linear_proposal_after_repair so verified session rows are trimmed
correctly.

In `@crates/skippy-server/src/frontend/local_generation.rs`:
- Around line 648-658: Update the cancellation handling in the proposal paths
guarded by linear_proposals_enabled, including both the visible check and the
corresponding check near line 712, to follow the loop-top behavior: set
receipt_cancelled and break rather than propagating ensure_request_active
errors. Keep cancellation receipt finalization consistent across all checks
within the generation loop.

---

Nitpick comments:
In `@crates/skippy-server/src/frontend/backend.rs`:
- Around line 578-579: Consolidate the streaming worker setup in
run_generation_stream by reusing the permit-lifetime logic from
run_blocking_generation_worker where possible. Preserve the current
fire-and-forget behavior: do not await the blocking task directly; add or use a
spawn-only variant, or spawn the helper future, while retaining the existing ()
closure behavior.

In `@crates/skippy-server/src/frontend/local_generation.rs`:
- Around line 364-375: Add a concise comment above the suffix chunking loop in
the prefill restoration flow, near `RuntimeState::prefill`, explaining that the
outer chunks provide cancellation checkpoints while `prefill` handles internal
chunking; explicitly retain the `.max(1)` guard because `slice::chunks` rejects
a zero size.
- Around line 42-50: Centralize the cancellation predicate by moving
ensure_request_active from local_generation.rs into the shared frontend module,
then import and reuse it in
crates/skippy-server/src/frontend/local_generation.rs:42-50, removing the local
definition there. Delete the duplicate helper and import the shared one in
crates/skippy-server/src/frontend/linear_proposal.rs:772-780. In
crates/skippy-server/src/frontend/generation_flow.rs:61-63, replace both inline
cancellation checks, including the second copy at lines 90-92, with
ensure_request_active(cancellation)?.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 12b51c0c-13a7-42fe-b78e-ba0d75d25b0e

📥 Commits

Reviewing files that changed from the base of the PR and between 1d004fa and 7f119d1.

📒 Files selected for processing (12)
  • crates/openai-frontend/src/backend.rs
  • crates/openai-frontend/src/guardrails/compact.rs
  • crates/openai-frontend/src/guardrails/mod.rs
  • crates/openai-frontend/src/hooks.rs
  • crates/openai-frontend/src/router.rs
  • crates/skippy-server/src/frontend/admission.rs
  • crates/skippy-server/src/frontend/backend.rs
  • crates/skippy-server/src/frontend/generation_flow.rs
  • crates/skippy-server/src/frontend/linear_proposal.rs
  • crates/skippy-server/src/frontend/local_generation.rs
  • crates/skippy-server/src/frontend/tests/generation.rs
  • crates/skippy-server/src/runtime_state.rs

Comment thread crates/skippy-server/src/runtime_state.rs Outdated
@ndizazzo
ndizazzo force-pushed the agent/cancel-timed-out-generation branch from f6a5b81 to 436ebe8 Compare August 4, 2026 18:59
@ndizazzo
ndizazzo force-pushed the agent/cancel-timed-out-generation branch from 436ebe8 to f119153 Compare August 4, 2026 19:49
Base automatically changed from agent/generation-lifecycle-events to main August 4, 2026 21:13
@ndizazzo
ndizazzo force-pushed the agent/cancel-timed-out-generation branch from f119153 to b536993 Compare August 4, 2026 21:36
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/skippy-server/src/frontend/generation_flow/text_generation.rs`:
- Around line 74-77: Update the generation flow around reserve_cancellable and
generate_local_tokens to check cancellation after admission succeeds and before
prefill begins. Add a second cancellation guard in the lower generation layer
immediately before prompt_prefill_sample emission, ensuring cancelled requests
return without stale prefill work or token output while preserving normal
generation behavior.

In `@crates/skippy-server/src/frontend/linear_proposal/execution.rs`:
- Around line 200-203: Update the cancellation handling around the
token-processing loop and the no-committed-tokens branch to preserve the
existing “request cancelled” callback_error when committed_tokens is empty.
Ensure cancellation still runs the required finish_linear_proposal_after_repair
repair or retirement path before returning, rather than replacing the
cancellation error with the generic classifier error.

In `@crates/skippy-server/src/frontend/local_generation/linear_decode.rs`:
- Around line 90-95: Update the no-proposal handling in the linear decode flow
around query_linear_proposal so cancellation is checked again when it returns
NoProposal or DeadlineExceeded. Before returning NotUsed or proceeding to normal
decoding via run_decode_loop and decode_one_token, inspect request.cancellation
and return the existing cancellation error if cancelled.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 84ef8111-e9d8-496d-a225-0c8a00b18178

📥 Commits

Reviewing files that changed from the base of the PR and between 4bd1453 and b536993.

📒 Files selected for processing (15)
  • crates/openai-frontend/src/backend.rs
  • crates/openai-frontend/src/guardrails/compact.rs
  • crates/openai-frontend/src/guardrails/mod.rs
  • crates/openai-frontend/src/hooks.rs
  • crates/openai-frontend/src/router.rs
  • crates/skippy-server/src/frontend/admission.rs
  • crates/skippy-server/src/frontend/backend.rs
  • crates/skippy-server/src/frontend/generation_flow/text_generation.rs
  • crates/skippy-server/src/frontend/linear_proposal/execution.rs
  • crates/skippy-server/src/frontend/local_generation/linear_decode.rs
  • crates/skippy-server/src/frontend/local_generation/tests.rs
  • crates/skippy-server/src/frontend/local_generation/token_generation.rs
  • crates/skippy-server/src/frontend/tests/generation.rs
  • crates/skippy-server/src/kv_integration/resident_prefix.rs
  • crates/skippy-server/src/runtime_state/frame_operations.rs
🚧 Files skipped from review as they are similar to previous changes (8)
  • crates/skippy-server/src/frontend/tests/generation.rs
  • crates/openai-frontend/src/guardrails/compact.rs
  • crates/openai-frontend/src/router.rs
  • crates/skippy-server/src/frontend/backend.rs
  • crates/skippy-server/src/frontend/admission.rs
  • crates/openai-frontend/src/backend.rs
  • crates/openai-frontend/src/guardrails/mod.rs
  • crates/openai-frontend/src/hooks.rs

Comment on lines +74 to +77
let token_budget_reservation = self.generation_token_budget.reserve_cancellable(
GenerationTokenBudgetRequest::new(prompt_token_ids.len(), max_tokens),
GENERATION_ADMISSION_TIMEOUT,
cancellation,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Check cancellation before prefill starts.

reserve_cancellable can return successfully, then the request can be cancelled before generate_local_tokens starts. The local path runs prefill and prepare_decode_state before run_decode_loop checks cancellation. prepare_decode_state can emit prompt_prefill_sample during this window. A timed-out request can therefore perform stale work and emit a token after cancellation. Add cancellation checks before prefill and before prompt-prefill emission in the lower generation layer.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/skippy-server/src/frontend/generation_flow/text_generation.rs` around
lines 74 - 77, Update the generation flow around reserve_cancellable and
generate_local_tokens to check cancellation after admission succeeds and before
prefill begins. Add a second cancellation guard in the lower generation layer
immediately before prompt_prefill_sample emission, ensuring cancelled requests
return without stale prefill work or token output while preserving normal
generation behavior.

Comment thread crates/skippy-server/src/frontend/linear_proposal/execution.rs
Comment thread crates/skippy-server/src/frontend/local_generation/linear_decode.rs
@i386
i386 merged commit 8eb33fd into main Aug 5, 2026
58 checks passed
@i386
i386 deleted the agent/cancel-timed-out-generation branch August 5, 2026 01:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants