feat(lib-transfer-manager): add file based download api and worker thread based download. - #8259
feat(lib-transfer-manager): add file based download api and worker thread based download.#8259smilkuri wants to merge 6 commits into
Conversation
3aaff3f to
5ca70ed
Compare
f1faadb to
139a6ba
Compare
| // the other download's temp file. | ||
| const fd = await open(tempFilePath, "wx"); | ||
| try { | ||
| await fd.truncate(totalSize); |
There was a problem hiding this comment.
Specification says not to pre-validate available space, we are just setting the logical file length here so it should be okay ig.
| * | ||
| * @internal | ||
| */ | ||
|
|
There was a problem hiding this comment.
this should not be in tsdoc format if it's module level, use /* */ or //.
or attach it to the main export symbol
| byteLength: number; | ||
| } | ||
|
|
||
| export class OrderedPartQueue { |
There was a problem hiding this comment.
what does this do? ~2 sentences as class description
There was a problem hiding this comment.
It reorders the parts and deliver them sequentially. Will update it.
| } | ||
|
|
||
| /** | ||
| * Returns whether an error has been set. |
There was a problem hiding this comment.
I can tell from the code this is what is happening. Who is the caller and why do they need to know?
There was a problem hiding this comment.
Dispatch loop calls it to stop launching new part requests in case of failure.
| signal.addEventListener("abort", removeListenerAfterAbort, { once: true }); | ||
| signal.addEventListener("abort", removeListenerAfterAbort, { | ||
| once: true, | ||
| }); |
There was a problem hiding this comment.
Formatted when ran make format
| streams, | ||
| requests, | ||
| metadata, | ||
| checksumValidationEnabled |
There was a problem hiding this comment.
are these parameters identical? prebuild in a variable
| * When false, the existing file is overwritten. | ||
| * Defaults to false. | ||
| */ | ||
| failIfExists?: boolean; |
There was a problem hiding this comment.
this parameter is usually called overwrite: boolean = true. Spec driven?
There was a problem hiding this comment.
That's according to the specification.
| metadata.ChecksumCRC32 = undefined; | ||
| metadata.ChecksumCRC32C = undefined; | ||
| metadata.ChecksumSHA1 = undefined; | ||
| metadata.ChecksumSHA256 = undefined; |
There was a problem hiding this comment.
why do this? there are other checksums now too
There was a problem hiding this comment.
The reason for setting them to undefined is s3 returns the checksum value for the whole multipart object (a composite of per-part checksums), not for the byte range being returned. Since we reassemble parts into a single stream, the per-part checksum from the initial response doesn't match to the joined output. But I agree we have to add that new checksums too.
| }; | ||
|
|
||
| const releaseSlot = (): void => { | ||
| inFlight--; |
There was a problem hiding this comment.
should this throw if already at 0?
or, the decrement should be in the conditional with the work being done
There was a problem hiding this comment.
Yes will add a check here
| .catch((error) => { | ||
| queue.setError(error); | ||
| abortController.abort(); | ||
| releaseSlot(); |
| // Create a Readable stream that pulls parts sequentially from the ordered queue. | ||
| // Each chunk is a zero-copy Buffer view into the transferred ArrayBuffer. | ||
| const transferStream = new Readable({ | ||
| read() { |
There was a problem hiding this comment.
is this read method fine to call if it's still working?
There was a problem hiding this comment.
No we shouldn't. The queue only has one waitingConsumer slot, so a second dequeue call would overwrite the first one. Should have some kind of reading check here.
| ETag: headResponse.ETag, | ||
| LastModified: headResponse.LastModified, | ||
| $metadata: headResponse.$metadata, | ||
| }); |
There was a problem hiding this comment.
do you have full ownership of the metadata object at this point where it is mutating?
There was a problem hiding this comment.
Yes, we are creating metadata as empty object initially in download() right, nobody reads it before it reaches the download method, it is populated here. Instead should we return as a new object from the download method instead of mutating the parameter?
| /** | ||
| * Optional CRC algorithm for inline checksum computation. | ||
| */ | ||
| checksumAlgorithm?: string; // "CRC32" | "CRC32C" | "CRC64NVME" |
There was a problem hiding this comment.
Initially didn't add the new XXHASH algorithms because the SDK doesn't have local implementations. Now updated it to just skip validation for those.
| /** | ||
| * Optional CRC algorithm for inline checksum validation against S3 response headers. | ||
| */ | ||
| checksumAlgorithm?: string; |
There was a problem hiding this comment.
why not the ChecksumAlgorithm enum type from S3?
| import { openSync, readSync, closeSync } from "node:fs"; | ||
| import type { LookupOptions } from "node:dns"; | ||
| import type { Checksum } from "@smithy/types"; | ||
| import { Crc32cJs, Crc64NvmeJs } from "@aws-sdk/checksums/crc"; |
There was a problem hiding this comment.
why import the JS implementations of the checksums and not the default?
| /** | ||
| * Thin wrapper around node:zlib crc32 to conform to the Checksum interface. | ||
| */ | ||
| class NodeCrc32 implements Checksum { |
There was a problem hiding this comment.
why is there a new checksum implementation here?
78dc67b to
6880870
Compare
Issue
Internal JS-7054
Description
downloadToFileAPI which takes a destination file path and downloads an S3 object to that location. It supports both PART based and RANGE based strategy. It uses a temp file with atomic rename, the destination file only appears once the download complete successfully. On failure or abort, the temp file is cleaned up.downloadByPartWithWorkersanddownloadByRangeWithWorkersfor thedownload()API. WhenworkerThreadCount > 1, download requests are routed through these methods based on the configured multipart strategy. Internally, Part 1 is fetched on the main thread to discover object metadata(PartsCount, ETag, total size). Remaining parts/range requests are dispatched to worker threads via WorkerHttpHandler, which assembles response bytes into dedicated ArrayBuffers and transfers ownership back to the main thread (zero-copy). AnOrderedPartQueuereorders parts that arrive out-of-order, while a Readable stream pulls from the queue sequentially and is passed to joinStreams to deliver a single ordered body to the caller.Testing
How was this change tested?
Checklist
*.integ.spec.ts) or E2E tests.@public,@internaltags and enabled doc generation on the package. Remember that access level annotations go below the description, not above.By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.