Skip to content

[Repo Assist] fix: honor CancellationToken in MoveNextAsync (closes #179)#311

Merged
dsyme merged 2 commits intomainfrom
repo-assist/fix-cancellation-token-179-2026-03-65d8a31d8f19eaa2
Mar 8, 2026
Merged

[Repo Assist] fix: honor CancellationToken in MoveNextAsync (closes #179)#311
dsyme merged 2 commits intomainfrom
repo-assist/fix-cancellation-token-179-2026-03-65d8a31d8f19eaa2

Conversation

@github-actions
Copy link
Contributor

@github-actions github-actions bot commented Mar 8, 2026

🤖 This is an automated PR from Repo Assist.

Closes #179

Summary

When a CancellationToken is passed to GetAsyncEnumerator(ct), it was stored in the state machine data but never checked during iteration. This meant that infinite or long-running taskSeq sequences could not be cancelled via the enumerator's token — the cancel request was silently ignored.

Root Cause

In TaskSeqBuilder.fs, MoveNextAsync() called data.builder.MoveNext() without first checking whether data.cancellationToken had been cancelled. The token was correctly stored and passed to async bindings (e.g., Async<'T> computations and nested IAsyncEnumerable<'T> sources), but a pure synchronous loop like while true do yield 1 never reached an async checkpoint and therefore never saw the cancellation.

Fix

Added a single ThrowIfCancellationRequested() call at the start of the active iteration path in MoveNextAsync():

else
    let data = this._machine.Data
    // Honor the cancellation token passed to GetAsyncEnumerator (fixes #179).
    // ThrowIfCancellationRequested() is a no-op for CancellationToken.None.
    data.cancellationToken.ThrowIfCancellationRequested()
    data.promiseOfValueOrEnd.Reset()
    ...

ThrowIfCancellationRequested() is a no-op for CancellationToken.None (the common case), so there is no overhead beyond a single volatile boolean read per MoveNextAsync call.

Behaviour

  • When the token is cancelled, the next call to MoveNextAsync() throws OperationCanceledException — cooperative, not preemptive.
  • A sequence that has already completed (completed = true) returns false normally even if the token is cancelled, since the enumeration is already done.
  • All existing behaviour is unchanged for CancellationToken.None.

Tests

Added TaskSeq.CancellationToken.Tests.fs with 8 new tests:

Test Description
Pre-cancelled token on infinite seq Throws on first MoveNextAsync
Pre-cancelled token on finite seq Throws on first MoveNextAsync
Normal iteration (non-cancelled) All 5 items produced correctly
CancellationToken.None All 5 items produced correctly
Token cancelled after partial iteration Throws on next MoveNextAsync
Pre-cancelled token, body not entered Body doesn't run before cancel
Token cancelled mid-infinite iteration Throws on next call after cancel
Multiple enumerators, independent tokens Only cancelled enumerator throws

Test Status

✅ Build succeeded (Release)
✅ All 3858 tests pass (8 new + 3850 existing)
dotnet fantomas . --check passes

Note: Tests were run using SDK 10.0.102 (closest available in this environment); CI will validate against 10.0.103.

Generated by Repo Assist ·

To install this agentic workflow, run

gh aw add githubnext/agentics/workflows/repo-assist.md@4ee2ca4faa30612b9ed0d207472068f5efc9ecf3

Add ThrowIfCancellationRequested() check at the start of each
MoveNextAsync call (in the active iteration path). This ensures that
when a CancellationToken passed to GetAsyncEnumerator is cancelled,
the next call to MoveNextAsync will throw OperationCanceledException.

The check is a no-op for CancellationToken.None, so the common case
of no cancellation token has no overhead beyond a single field read.

Adds 8 tests covering:
- Pre-cancelled token throws on first MoveNextAsync
- Token cancelled mid-iteration throws on next MoveNextAsync
- Normal iteration is unaffected (no token, CancellationToken.None)
- Multiple independent enumerators respect independent tokens

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dsyme dsyme marked this pull request as ready for review March 8, 2026 01:42
@dsyme dsyme merged commit c4fbcf6 into main Mar 8, 2026
4 checks passed
@dsyme dsyme deleted the repo-assist/fix-cancellation-token-179-2026-03-65d8a31d8f19eaa2 branch March 8, 2026 01:43
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.

CancellationToken not used/registered

1 participant