Add file-based transfer APIs to build cache provider interface and all cache plugins#5746
Add file-based transfer APIs to build cache provider interface and all cache plugins#5746Copilot wants to merge 50 commits into
Conversation
|
@copilot - add APIs to |
Added |
|
@copilot - are there async variants of those |
No, |
|
@copilot - take a look at the other |
Added |
|
@copilot - can we add stream support to the other cache plugins? |
Added streaming support to both remaining cache plugins: Amazon S3 plugin (
Azure Storage plugin (
All three cache plugins (HTTP, S3, Azure) now implement both streaming methods. All existing tests pass (45 S3, 10 Azure). |
|
@copilot - Can you put usage of this functionality behind a Rush experiment? |
Added a |
The HTTP and S3 providers both ensure the parent directory exists before writing the cache entry file. The Azure provider was missing this, which would cause failures on a fresh machine where the build cache folder hasn't been created yet. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Rush change file: replace "streaming APIs" with file-based transfer API names and mention the experiment flag - node-core-library change file: fix broken backtick formatting - Azure provider: update JSDoc from "stream" to "file-based" - HTTP test names: replace "stream consumed" / "stream bodies" with file-based language Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The delay variable is in milliseconds but the log message said "s". This produced misleading output like "Will retry request in 4000s...". Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… signing Remove unused onBlobAlreadyExists parameter from _trySetBlobDataAsync This callback was previously used to drain incoming streams when the blob already existed. With the switch to file-based APIs, no callers pass this parameter anymore.
- HTTP: add 404 cache miss test for tryDownloadCacheEntryToFileAsync - HTTP: add pipeline assertion in download success test - S3: add retry test for downloadObjectToFileAsync on transient 5xx - S3: add pipeline assertions in download success/miss tests Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
For stream-body requests, the log read "unknown bytes" which is awkward. Change to "unknown length" for clarity. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add clarifying comment on maxAttempts: 1 for uploads explaining why the parameter exists (shared between download with retries and upload without) - Replace S3 download snapshot containing auth headers with explicit field assertions, avoiding credential-looking strings in snapshots - Update inline snapshot for "unknown length" wording change Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
dd9bf94 to
f8fecd4
Compare
| // Clean up any partial file left by the failed download so it isn't | ||
| // mistaken for a valid cache entry on the next build. | ||
| try { | ||
| await FileSystem.deleteFileAsync(targetPath); |
There was a problem hiding this comment.
Seems like this only deletes the target file when the provider throws during download.
However, providers generally swallow download failures and return false/undefined instead of throwing:
- HTTP provider catches any error in
tryDownloadCacheEntryToFileAsync()and returnsfalse - S3 provider catches errors and returns
false - Azure provider catches errors in
_tryGetBlobDataAsync()and ultimately returnsfalse
At the same time, the local cache provider considers a cache entry valid if the file merely exists, without validating size, checksum, or completeness.
It seems possible that a partially written (truncated or corrupted) file from a failed streamed download could remain on disk and be treated as a valid cache hit on subsequent runs.
There was a problem hiding this comment.
Good catch — the cleanup now runs for any non-success case (both false return and thrown exception), not just in the catch block. Moved deleteFileAsync outside the try/catch so it triggers whenever cloudCacheHit remains false. This prevents partially written files from being treated as valid cache entries on subsequent runs.
|
@copilot - this branch has conflicts. Rebase or merge main into this branch and resolve conflicts. |
…urns false Providers may catch errors internally and return false instead of throwing, leaving a partially written file on disk. Move the cleanup logic outside the catch block so it runs for any non-success case, preventing corrupted partial files from being treated as valid cache entries on subsequent builds.
…entry-for-http-plugin # Conflicts: # common/reviews/api/rush-lib.api.md # libraries/rush-lib/src/api/ExperimentsConfiguration.ts # libraries/rush-lib/src/schemas/experiments.schema.json
Merged main and resolved three conflicts ( |
| } | ||
| const response: IWebClientResponse = await this._makeSignedRequestAsync('GET', objectName); | ||
| return this._handleGetResponseAsync( | ||
| response.status, |
There was a problem hiding this comment.
@copilot - restructure the responses in these changes.
There was a problem hiding this comment.
Restructured _handleGetResponseAsync to accept the response object (IWebClientResponseBase) directly instead of individual status/statusText/ok fields. Also removed the getError callback — error messages are now constructed inline from the response object, making the call sites cleaner. Commit: the latest push.
| body: entryStream, | ||
| warningText: 'Could not write cache entry', | ||
| // Streaming uploads cannot be retried because the stream is consumed | ||
| maxAttempts: 1 |
| cleanup?: () => void | ||
| ): Promise<RetryableRequestResponse<T | undefined>> { | ||
| if (ok) { | ||
| if (response.ok) { |
There was a problem hiding this comment.
Destructured response into { ok, status, statusText } at the top of the method. All 51 S3 tests pass.
Summary
Adds optional file-based transfer APIs to the build cache provider interface (
ICloudBuildCacheProvider) and implements them across all cache plugins (HTTP, Amazon S3, Azure Blob Storage). When enabled via theuseDirectFileTransfersForBuildCacheexperiment flag, cache entries are transferred directly between local files and cloud storage without buffering entire contents in memory, preventing out-of-memory errors for large build outputs.Details
Core changes:
ICloudBuildCacheProvidergains two optional methods:tryDownloadCacheEntryToFileAsyncandtryUploadCacheEntryFromFileAsync. Providers that don't implement them gracefully fall back to the existing buffer-based APIs.OperationBuildCacheconditionally uses the file-based path whenuseDirectFileTransfersForBuildCacheis enabled and the provider supports it. Includes cleanup of partial files on failed downloads.WebClientis refactored to extract a shared_makeRawRequestAsynccore used by both buffer and streaming request paths, with a newfetchStreamAsyncmethod and Content-Encoding decompression support for streaming responses.FileSystemin node-core-library gainscreateReadStream,createWriteStream, andcreateWriteStreamAsyncmethods (wrapped in_wrapExceptionfor consistent error handling).FileSystemBuildCacheProvideris simplified — the stream method is removed since cloud providers now handle file I/O directly.Plugin implementations:
fetchStreamAsync→pipeline()to file. Uploads viacreateReadStream→fetchStreamAsync. UsesmaxAttempts: 1for uploads (stream consumed after first attempt), with credential fallback skipped for stream bodies._hashFileAsync, then stream with the SHA-256 hash included in the AWS Signature V4 request — restoring full payload signing (no moreUNSIGNED_PAYLOAD). No retry on uploads.blobClient.downloadToFile(). Uploads viablockBlobClient.uploadFile(). Parent directory creation ensured before download.Gating:
useDirectFileTransfersForBuildCacheinexperiments.json. Defaults to off. Falls back to buffer-based APIs if the cloud provider plugin doesn't implement the file-based methods.How it was tested
HttpBuildCacheProvider(14 tests): buffer and file-based GET/SET, 404 cache miss, credential fallback skip for file uploads, write-not-allowed checks, retry behavior, pipeline assertionsAmazonS3Client(38 tests): buffer and file-based GET/SET, signed payload hash verification (not UNSIGNED-PAYLOAD), download retry on transient 5xx, no-retry on upload, credential validation, pipeline assertionsImpacted documentation
experiments.schema.jsonupdated withuseDirectFileTransfersForBuildCachedescriptioncommon/reviews/api/rush-lib.api.mdupdated with new API surfacecommon/reviews/api/node-core-library.api.mdupdated with newFileSystemstream methods