Replies: 1 comment
|
Closing the loop on this proposal: it is implemented on What shipped
The open questions
What changed from the draft's contractThe draft's The default fails because the result is a stream the caller appends to what it keeps: starting over without saying so would append the whole resource after a partial one. For reference, Out of scope (v1)The library still stores no bytes and no partial file. What it does now is hand back the pieces, a |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Status
Early draft — posted here mainly so it's tracked against 4.5.0, not a finished design. The contract below is still being argued about internally; expect it to change before implementation starts. Feedback and alternative shapes welcome.
Overview
Goal: let a caller pause and resume a streaming download (
DownloadTask'sTaskResult<AsyncBytes>) using HTTPRange/If-Range, without adding a new concrete task type (BackgroundDownloadTaskalready covers OS-level background transfers on Darwin — this is a same-process, any-platform complement, not a replacement).The mechanism piggybacks on infrastructure that already exists:
RawTask._result(environment:)(Sources/RequestDL/Tasks/Sources/Raw Task/Raw/RawTask.swift) is the one place that resolves aPropertytree into aRequestConfigurationand callsclient.execute(...). It already mutates the resolved configuration in place for cross-cutting concerns (tracer header injection) and already runs environment-queued hooks (taskDescriptorContextHooks, backing.description(_:enabled:onDescribe:)) at that exact point. A resume hint (offset + validator) would be read the same way, right beforeclient.execute.Internals.CacheControl.getUpdatedHeadersForCache(Sources/RequestDL/Tasks/Sources/Raw Task/Cache Control/Internals.CacheControl.swift) already does the closest analogue to what this needs: reissue a request with conditional headers (If-None-Match/If-Modified-Since), inspect the status code that comes back (304 vs. not), and branch. The resumable case is the same shape withRange/If-Rangeand 206/200/416 instead of 304.So the proposal is: an extension on
RequestTask where Element == TaskResult<AsyncBytes>returning aResumableTaskwrapper, not a new task type users declare their request under.Proposed contract (rough)
offset/validatorare supplied by the caller on everyresume(from:validator:)call, not tracked internally — only the caller knows how many bytes it actually persisted (wrote to disk, etc.), which can lag behind how many bytes the stream delivered.No explicit
pause(): pausing is the caller stopping/cancelling its ownfor try awaitconsumption ofAsyncBytes. Whether that actually tears down the underlying connection cleanly on both transports (NIO and URLSession) rather than leaving it dangling until a timeout is unverified — see open questions.Open questions
Resumptionhide the byte bookkeeping and hand back one byte-continuous stream (merging old + new bytes internally), instead of leaving concatenation entirely to the caller? Raised and pushed back on, not settled.Taskactually release the connection on both the NIO and URLSession backends, or doesResumableTaskneed its own explicit teardown path?RangeHeader/IfRangeHeader(alongsideAcceptEncodingHeader,AcceptLanguageHeader, etc.) as public headers independent ofresumable(), for callers who want to build the request by hand?ResumableTaskonly covers same-process resume. Surviving an app relaunch isBackgroundDownloadTask's territory today — should stay that way, or should this converge with it eventually?ResumableTaskvs. alternatives), and whether it belongs as a method onRequestTaskat all vs. a dedicated initializer.Out of scope (v1)
offset/validator across app restarts — caller-owned state,ResumableTaskdoesn't serialize anything itself.BackgroundDownloadTask.Milestone: 4.5.0
All reactions