Skip to content

Desktop: fix quit hang when a stream fetch is cancelled - #239

Merged
ingoau merged 3 commits into
mainfrom
t3/fix-quit-hang-cancelled-fetch
Nov 11, 2026
Merged

ingoau merged 3 commits into
mainfrom
t3/fix-quit-hang-cancelled-fetch

Conversation

@ingoau

@ingoau ingoau commented Nov 10, 2026 •

Copy link
Copy Markdown
Owner

Fixes #214.

Cause

When the core stopped, the downloader task was cancelled: reader handles dropped, fetch returned cancelled, and the task was dropped mid-backoff or by runtime shutdown. Nothing set closed, and cancelled went through record_fetch_error as if transient — so the decoder blocked in HttpSource::read logged "waiting through fetch error" every 200 ms forever, and shutdown blocked on the decode thread.

Fix (crates/hocket-core/src/audio/native/http.rs)

  • CloseGuard: a drop guard in downloader sets closed and notifies data_ready whenever the task exits, however it exits — error return, task cancellation (engine stop), or runtime shutdown. Any decoder blocked in read wakes and surfaces an error.
  • cancelled is final: is_final_fetch_error treats FetchError::Cancelled as non-retryable. The downloader returns instead of backing off and retrying, and read surfaces the error ("cancelled") instead of waiting through it. A cancelled body-stream error stays retryable (the stream reader also cancels handles it evicts as idle after a long pause); the refetch then fails as cancelled after a real stop and ends the downloader.

Tests

  • cancelled_fetch_surfaces_from_read_instead_of_waiting: body cancelled after the first chunk → read returns an error containing "cancelled" promptly.
  • runtime_shutdown_wakes_a_blocked_decoder: fetch stalls forever, decoder blocked in read_to_end on another thread; dropping the runtime (engine stop) wakes it and the thread exits.
  • Both verified to fail/hang against the old behavior.

Generated by GLM-5.3-Flash in OpenCode

ingoau and others added 3 commits November 10, 2026 22:27
Fixes #214. When the core stopped, the downloader task was cancelled but
nothing set `closed`, and the resulting `cancelled` fetch error went
through `record_fetch_error` as if transient — so a decoder blocked in
`HttpSource::read` waited through it forever and shutdown hung.

- Add a drop guard in `downloader` that sets `closed` and notifies
  `data_ready` whenever the task ends, however it ends (error return,
  task cancellation, runtime shutdown).
- Treat `FetchError::Cancelled` as final: the downloader stops instead
  of retrying, and `read` surfaces the error instead of waiting through
  it. A cancelled body stream is final too.
- Tests: a cancelled fetch surfaces from `read` promptly, and dropping
  the runtime wakes a decoder blocked on a stalled fetch.

Generated by GLM-5.3-Flash in [OpenCode](https://opencode.ai)

Co-authored-by: GLM-5.3-Flash <opencode-agent[bot]@users.noreply.github.com>
- CancelFetcher reuses the Arc<Mutex<Vec<u64>>> request log and the test
  pins that the downloader stops after the first cancelled body (exactly
  one request, no retries).
- The runtime-shutdown test asserts the read error is "source closed"
  rather than just any error.

Generated by GLM-5.3-Flash in [OpenCode](https://opencode.ai)

Co-authored-by: GLM-5.3-Flash <opencode-agent[bot]@users.noreply.github.com>
The stream reader also cancels handles it evicts as idle (after a long
pause), and refetching from the gap recovers from that. A cancelled
fetch stays final, so a stopped reader still ends the downloader after
one refetch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ingoau
ingoau merged commit 7c70b6a into main Nov 11, 2026
13 checks passed
@ingoau
ingoau deleted the t3/fix-quit-hang-cancelled-fetch branch November 11, 2026 00:26
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.

Desktop: app hangs on quit when a stream fetch is cancelled (decoder waits forever)

1 participant