Skip to content

fix(cancel): stop a cancel from stranding the job that replaced it - #65

Merged
StuartCameronCode merged 2 commits into
mainfrom
fix/cancel-does-not-strand-next-job
Aug 8, 2026
Merged

fix(cancel): stop a cancel from stranding the job that replaced it#65
StuartCameronCode merged 2 commits into
mainfrom
fix/cancel-does-not-strand-next-job

Conversation

@StuartCameronCode

Copy link
Copy Markdown
Owner

Regression from #63. Cancelling a conversion and starting another left the
progress dialog spinning with no updates — the worker was healthy and the encode
was progressing, but nothing was listening to it.

Cause

cancel() now waits for the worker to genuinely exit, which can take seconds.
Cancellation emits a completion, the queue acts on that by starting the next job,
and the tail of the cancelling call then ran unconditionally — so its
_cleanup() nulled _process and cancelled the stdout/stderr subscriptions
belonging to the new job. No progress events could arrive after that, and
nothing errored, because from the worker's side everything was fine.

The exit handler registered in startJob had the same problem on its own
schedule, and additionally won the race to report: it is registered on exitCode
before cancel() awaits it, so a deliberate cancellation surfaced as
Worker exited with code 143 rather than as a cancellation.

Fix

Both are scoped to the job they belong to via a generation counter, checked
before any teardown or completion. The exit handler also consults
_cancelRequested, so a cancelled job reports itself as cancelled.

Tests

They read the source rather than driving a real queue. The failure mode is an
ordering bug between two asynchronous continuations, and a test that spawned real
workers would reproduce it only intermittently — a flaky guard against a hang is
worse than none. They assert the guards exist and that they precede
_cleanup()
, since a guard placed after the teardown would satisfy a plain
contains check while fixing nothing.

All four fail against the previous version (+0 -4) and pass against this one.

Regression from the cancel fix. Cancelling a conversion and starting another one
left the progress dialog spinning with no updates: the worker was healthy and
the encode was progressing, but nothing was listening to it.

cancel() now waits for the worker to genuinely exit, which can take seconds.
Cancellation emits a completion, the queue acts on that by starting the next
job, and the tail of the *cancelling* call then ran unconditionally -- so its
_cleanup() nulled _process and cancelled the stdout/stderr subscriptions
belonging to the new job. No progress events could ever arrive after that, and
nothing errored, because from the worker's side everything was fine.

The exit handler registered in startJob had the same problem on its own
schedule, and additionally won the race to report: it is registered on exitCode
before cancel() awaits it, so a deliberate cancellation surfaced as "Worker
exited with code 143" rather than as a cancellation.

Both are now scoped to the job they belong to via a generation counter, checked
before any teardown or completion. The exit handler also consults
_cancelRequested so a cancelled job reports itself as cancelled.

The tests read the source rather than driving a real queue: the failure mode is
an ordering bug between two asynchronous continuations, and a test that spawned
real workers would reproduce it only intermittently. They assert the guards
exist and, specifically, that they precede _cleanup() -- a guard after the
teardown would pass a "contains" check while fixing nothing. All four fail
against the previous version.

One trap worth recording: cancel()'s own comments mention _cleanup(), so the
first draft of the ordering assertion was comparing against prose and proved
nothing. It matches the call now.
These tests read worker_manager.dart and match against "\n", but git checks that
file out CRLF on Windows -- so `contains('_cancelRequested\n')` and the scan for
'\n  }\n' both failed there and nowhere else.

Exactly the trap that broke test_92 in filter_integration_test.rs two days ago,
walked into again. The comment now says so, next to the fix, so the next
assertion added here does not repeat it.

Verified by converting the source to CRLF locally and running both ways.
@StuartCameronCode
StuartCameronCode merged commit 2db2193 into main Aug 8, 2026
4 checks passed
@StuartCameronCode
StuartCameronCode deleted the fix/cancel-does-not-strand-next-job branch August 8, 2026 16:59
StuartCameronCode added a commit that referenced this pull request Aug 8, 2026
The artifacts on the draft carry the stuck-progress regression fixed in #65, so
they are being replaced again. Version string stays at 0.9.12; build number
29 -> 30.
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.

1 participant