Skip to content

feat: allow cancelling stalled downloads - #525

Merged
wkentaro merged 1 commit into
mainfrom
feat/prompt-download-cancellation
Sep 17, 2026
Merged

wkentaro merged 1 commit into
mainfrom
feat/prompt-download-cancellation

Conversation

@wkentaro

@wkentaro wkentaro commented Sep 17, 2026

Copy link
Copy Markdown
Owner

Applications need to cancel a stalled download before its network timeout expires. Progress callbacks cannot run while a read is blocked, and lowering the timeout rejects slow but valid transfers.

Closes #524. Provides the upstream support for wkentaro/osam#100.

The reader polls socket readiness while preserving the original timeout. Cross-thread shutdown did not wake stalled direct reads on Windows, and short socket timeouts poison buffered reads; keeping cancellation inside the read avoids both problems. This relies on urllib3 connection pools and the standard-library response class, which is the main maintenance cost.

Prompt interruption covers response headers, bodies, retry backoff, and speed-limit waits. DNS/connect/TLS setup, proxy negotiation, filesystem operations, and caller callbacks remain outside that guarantee. HTTPS-through-HTTPS-proxy and pyOpenSSL-injected sockets have not been verified.

Validation: just lint and 217 non-network tests pass in an isolated worktree at the final commit. All 46 cancellation tests were also verified with urllib3 1.26.20 and 2.0.7. Local-server tests cover stalled reads, timeout preservation, redirects/reconnects, HTTP/SOCKS proxies, cleanup, concurrent isolation, and publication boundaries. Mutation checks verify the proxy-body tests and cache guards and show why the retry and per-chunk guards are needed.

Separate media is omitted because the timed local-server tests directly demonstrate cancellation before the network timeout.

@wkentaro wkentaro self-assigned this Sep 17, 2026
@wkentaro
wkentaro force-pushed the feat/prompt-download-cancellation branch from 34d749f to ff77e20 Compare September 17, 2026 06:41
@wkentaro
wkentaro marked this pull request as ready for review September 17, 2026 06:42
@wkentaro
wkentaro marked this pull request as draft September 17, 2026 06:45
@wkentaro
wkentaro marked this pull request as ready for review September 17, 2026 06:55
@wkentaro
wkentaro force-pushed the feat/prompt-download-cancellation branch from 7b5418f to cb1523a Compare September 17, 2026 07:49
@wkentaro

Copy link
Copy Markdown
Owner Author

This was generated by AI during review.

Verdict: recommend-merge at 1354223. No defects found. Two optional trims and one test nit below; none block merging.

Worth adding

Yes. A progress callback cannot run while a read is blocked, so stopping a stalled read is a capability callers do not have today, and osam#100 needs it. The feature is opt-in: with cancel=None no adapter is mounted and downloads take the existing path.

Correctness

  • DownloadCancelled derives from Exception, which none of the retry except clauses in download.py catch, and the local tests exercise that path through urllib3 and requests. Cancellation is never retried or converted into a DownloadError.
  • With an unset event, real HTTPS keep-alive downloads are byte-identical to downloads without one (raw.githubusercontent, a GitHub tarball redirect, a Google Drive file).
  • A 400 MB loopback download shows no measurable throughput overhead over HTTP or HTTPS, including HTTP/1.1 keep-alive, which the HTTP/1.0 test server does not exercise.
  • After 10 cancelled downloads with GC disabled and ResourceWarning raised as an error, descriptor and thread counts return to baseline once the in-process server handlers exit.
  • 46/46 cancellation tests passed in four consecutive local runs, about 4 s each.

Minimality

The polling reader and the three nested classes are justified: they are the only way to reach the header read through requests. Optional trims:

  • gdown/cached_download.py:107: the post-hash check goes beyond feat: allow downloads to be cancelled before the network timeout #524 and cancels a cache hit that is already verified and harmless to return. Dropping it removes about 25 lines, including test_cancel_during_cache_hit_hash_check, which monkeypatches a private function to hit the window.
  • gdown/download.py:326: the loop-top check survived mutation. The retry wait and the reader already cover it; it saves at most one request send.

Tests

Meaningful and integrated: they go through the public API against real local HTTP, HTTPS, HTTP-proxy, and SOCKS5 servers. The patched seams are backoff length, shutil.move, cache_root, FileLock, and one private hash function. The FileLock patch is justified because a lock wait can be long.

Mutation check: 13 of 17 mutants killed. Survivors:

  • The loop-top check and the download() entry check each cover the pre-set-event test for the other; together they are covered.
  • The SSLWantWriteError branch is not practically triggerable in a test.
  • Forwarding close() is correct (see the leak check above), but no test pins it, so the "resource cleanup covered" criterion in feat: allow downloads to be cancelled before the network timeout #524 is only partly met.

Nit: test_cancel_interrupts_retry_and_speed_waits[complete] uses speed=1. If the speed wait ever regresses, a worker sleeps about 6 days and CI hangs instead of failing. speed=CHUNK_SIZE / 30 would turn that into a failure.

Not verified

Windows behaviour (relied on CI), the urllib3 1.26.20 and 2.0.7 claim, HTTPS through an HTTPS proxy, and pyOpenSSL-injected sockets.

The 8 commits include fixups of code that never shipped; consider squashing or reshaping history at merge.

@wkentaro wkentaro added the recommend-merge pr: Agent finalized and endorses it: review and merge label Sep 17, 2026
Accept a caller-owned event to stop response reads and download waits
without shortening network timeouts. Preserve partial-file cleanup and
raise DownloadCancelled separately from download failures.

Use cancellable buffered reads across HTTP, HTTPS, and proxy connections.
Cover timeout preservation, publication boundaries, and cancellation
cleanup with local-server tests, and record the API decision.
@wkentaro
wkentaro force-pushed the feat/prompt-download-cancellation branch from da3c353 to 474f4d4 Compare September 17, 2026 08:45
@wkentaro wkentaro added the maintainer-approved pr: Maintainer reviewed this head and approves merging after required checks pass label Sep 17, 2026
@wkentaro
wkentaro merged commit bb18fac into main Sep 17, 2026
17 checks passed
@wkentaro
wkentaro deleted the feat/prompt-download-cancellation branch September 17, 2026 08:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

maintainer-approved pr: Maintainer reviewed this head and approves merging after required checks pass recommend-merge pr: Agent finalized and endorses it: review and merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: allow downloads to be cancelled before the network timeout

1 participant