Skip to content

fix(streaming): replace sleep gate with _started event for cancel tests#1146

Merged
planetf1 merged 4 commits into
generative-computing:mainfrom
planetf1:worktree-fix-1132
May 27, 2026
Merged

fix(streaming): replace sleep gate with _started event for cancel tests#1146
planetf1 merged 4 commits into
generative-computing:mainfrom
planetf1:worktree-fix-1132

Conversation

@planetf1
Copy link
Copy Markdown
Contributor

@planetf1 planetf1 commented May 22, 2026

Misc PR

Type of PR

  • Bug Fix
  • New Feature
  • Documentation
  • Other

Description

Two streaming cancellation tests gated on await asyncio.sleep(0.01) before calling cancel(). On a fast runner, StreamingMockBackend (only sleep(0) between tokens) can drain the whole 200-word stream inside that window — cancel() then no-ops on an already-done task and no CancelledError is raised.

Adds _orchestration_started (asyncio.Event) to StreamChunkingResult, set synchronously at the top of _orchestrate_streaming before any await. The two tests now wait on this event instead of sleeping: once it fires the task is suspended at its first real I/O point, so cancel() is guaranteed to land on a live task. An assert not task.done() guard follows the wait to catch any future regression to vacuous-pass behaviour.

Testing

  • Tests added to the respective file if code was changed
  • New code has 100% coverage if code as added
  • Ensure existing tests and github automation passes (a maintainer will kick off the github automation when the rest of the PR is populated)

Attribution

  • AI coding assistants used

@github-actions github-actions Bot added the bug Something isn't working label May 22, 2026
test_external_task_cancellation_releases_consumers and
test_external_cancellation_acomplete_raise_once both slept 10 ms before
calling cancel() on the orchestration task.  On a fast runner the mock
backend (sleep(0) between tokens) can drain the entire stream within that
window, making cancel() a no-op on an already-done task — no
CancelledError is ever raised and the test fails.

Add StreamChunkingResult._started (asyncio.Event), set at the very top of
_orchestrate_streaming before its first suspension point.  Tests now await
_started before cancelling: after _started fires the orchestration is live
but suspended, and cancel() is called without yielding back to the event
loop, so the task cannot complete between the two calls.

Fixes generative-computing#1132

Assisted-by: Claude Code
Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>
@planetf1 planetf1 force-pushed the worktree-fix-1132 branch from fa5713d to 767d0b9 Compare May 22, 2026 10:14
…ne() guard

Three improvements from code review:

- Rename _started to _orchestration_started for consistency with the
  existing _orchestration_task and _orchestration_exception fields.
- Expand the attribute comment to make explicit that the event is set
  synchronously before any await, and that the object is single-use.
- Add `assert not result._orchestration_task.done()` after the
  _orchestration_started.wait() in both cancellation tests as a
  regression guard: if a future change makes the orchestration
  synchronous to its first yield, the tests would silently regress to
  vacuous-pass without this assertion.

Assisted-by: Claude Code
Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>
@planetf1 planetf1 marked this pull request as ready for review May 26, 2026 10:17
@planetf1 planetf1 requested a review from a team as a code owner May 26, 2026 10:17
Copy link
Copy Markdown
Contributor Author

@planetf1 planetf1 left a comment

Choose a reason for hiding this comment

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

Clean fix — deterministic event coordination is strictly better than a wall-clock sleep for this class of test. Two minor notes below; neither blocks merge.

The third cancellation test (test_cancelled_task_sets_completed_false, line ~1699) still uses await asyncio.sleep(0) with BlockingBackend. That's correct — a never-draining backend makes the race impossible — but the pattern now diverges silently from the two tests you updated, which could confuse a future reader or get copy-pasted as a template. Consider either migrating it to await asyncio.wait_for(result._orchestration_started.wait(), timeout=2.0) (strictly stronger, also satisfies the coro.send(None) requirement the docstring mentions), or adding one line: # Deliberately uses sleep(0), not _orchestration_started.wait(): BlockingBackend blocks indefinitely so there is no race. Happy to leave this as a follow-up issue rather than hold up this PR.

Comment thread test/stdlib/test_streaming.py
Comment thread test/stdlib/test_streaming.py
Add failure messages to the two `assert not done()` regression guards
so a future failure is self-explanatory rather than a bare AssertionError.

Replace the BlockingBackend test's original comment with one that
explicitly notes the deliberate divergence from the _orchestration_started
pattern: the backend blocks indefinitely so there is no stream-drain race,
making sleep(0) both correct and sufficient.

Assisted-by: Claude Code
Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>
Copy link
Copy Markdown
Member

@psschwei psschwei left a comment

Choose a reason for hiding this comment

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

LGTM

@planetf1 planetf1 enabled auto-merge May 27, 2026 08:09
@planetf1 planetf1 force-pushed the worktree-fix-1132 branch from aa03481 to 06c65b0 Compare May 27, 2026 10:07
@planetf1 planetf1 added this pull request to the merge queue May 27, 2026
Assisted-by: Claude Code
Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>
Merged via the queue into generative-computing:main with commit 3a6a068 May 27, 2026
3 checks passed
@planetf1 planetf1 deleted the worktree-fix-1132 branch May 27, 2026 11:09
akihikokuroda pushed a commit to akihikokuroda/mellea that referenced this pull request May 27, 2026
…ts (generative-computing#1146)

* fix(streaming): replace sleep gate with _started event for cancel tests

test_external_task_cancellation_releases_consumers and
test_external_cancellation_acomplete_raise_once both slept 10 ms before
calling cancel() on the orchestration task.  On a fast runner the mock
backend (sleep(0) between tokens) can drain the entire stream within that
window, making cancel() a no-op on an already-done task — no
CancelledError is ever raised and the test fails.

Add StreamChunkingResult._started (asyncio.Event), set at the very top of
_orchestrate_streaming before its first suspension point.  Tests now await
_started before cancelling: after _started fires the orchestration is live
but suspended, and cancel() is called without yielding back to the event
loop, so the task cannot complete between the two calls.

Fixes generative-computing#1132

Assisted-by: Claude Code
Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>

* refactor(streaming): rename _started → _orchestration_started; add done() guard

Three improvements from code review:

- Rename _started to _orchestration_started for consistency with the
  existing _orchestration_task and _orchestration_exception fields.
- Expand the attribute comment to make explicit that the event is set
  synchronously before any await, and that the object is single-use.
- Add `assert not result._orchestration_task.done()` after the
  _orchestration_started.wait() in both cancellation tests as a
  regression guard: if a future change makes the orchestration
  synchronous to its first yield, the tests would silently regress to
  vacuous-pass without this assertion.

Assisted-by: Claude Code
Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>

* test(streaming): improve assert messages and clarify sleep(0) test

Add failure messages to the two `assert not done()` regression guards
so a future failure is self-explanatory rather than a bare AssertionError.

Replace the BlockingBackend test's original comment with one that
explicitly notes the deliberate divergence from the _orchestration_started
pattern: the backend blocks indefinitely so there is no stream-drain race,
making sleep(0) both correct and sufficient.

Assisted-by: Claude Code
Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>

---------

Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

flaky: test_external_cancellation_acomplete_raise_once failed once in merge-queue CI (3.13)

2 participants