-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Correctly reset chunk during artifact upload on retry #458
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,13 +1,21 @@ | ||
| name: artifact-unit-tests | ||
| on: push | ||
| on: | ||
| push: | ||
| branches: | ||
| - master | ||
| paths-ignore: | ||
| - '**.md' | ||
| pull_request: | ||
| paths-ignore: | ||
| - '**.md' | ||
|
|
||
| jobs: | ||
| build: | ||
| name: Build | ||
|
|
||
| strategy: | ||
| matrix: | ||
| runs-on: [ubuntu-latest, windows-latest, macOS-latest] | ||
| runs-on: [ubuntu-latest, windows-latest, macos-latest] | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All of our public documentation has https://github.com/actions/virtual-environments#available-environments
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These are case-insensitive and |
||
| fail-fast: false | ||
|
|
||
| runs-on: ${{ matrix.runs-on }} | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -208,25 +208,30 @@ export class UploadHttpClient { | |
| // for creating a new GZip file, an in-memory buffer is used for compression | ||
| if (totalFileSize < 65536) { | ||
| const buffer = await createGZipFileInBuffer(parameters.file) | ||
| let uploadStream: NodeJS.ReadableStream | ||
|
|
||
| //An open stream is needed in the event of a failure and we need to retry. If a NodeJS.ReadableStream is directly passed in, | ||
| // it will not properly get reset to the start of the stream if a chunk upload needs to be retried | ||
| let openUploadStream: () => NodeJS.ReadableStream | ||
|
|
||
| if (totalFileSize < buffer.byteLength) { | ||
| // compression did not help with reducing the size, use a readable stream from the original file for upload | ||
| uploadStream = fs.createReadStream(parameters.file) | ||
| openUploadStream = () => fs.createReadStream(parameters.file) | ||
| isGzip = false | ||
| uploadFileSize = totalFileSize | ||
| } else { | ||
| // create a readable stream using a PassThrough stream that is both readable and writable | ||
| const passThrough = new stream.PassThrough() | ||
| passThrough.end(buffer) | ||
| uploadStream = passThrough | ||
| openUploadStream = () => { | ||
| const passThrough = new stream.PassThrough() | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I realize this was the existing code, but why do we need a stream that is both readable and writable for upload?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's an implementation detail, we have an in-memory buffer and we have to convert it somehow to a readable stream. A passthrough stream is used to get the job done without any extra pipping or secondary streams. During the actual upload, it's treated only as a readable stream so it makes no difference. I think this is where I originally got the idea from: https://stackoverflow.com/questions/16038705/how-to-wrap-a-buffer-as-a-stream2-readable-stream I recall experimenting with a bunch of other techniques but they all ended up being considerably more complex.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we document that in https://github.com/actions/toolkit/blob/master/packages/artifact/docs/implementation-details.md?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| passThrough.end(buffer) | ||
| return passThrough | ||
| } | ||
| uploadFileSize = buffer.byteLength | ||
| } | ||
|
|
||
| const result = await this.uploadChunk( | ||
| httpClientIndex, | ||
| parameters.resourceUrl, | ||
| uploadStream, | ||
| openUploadStream, | ||
| 0, | ||
| uploadFileSize - 1, | ||
| uploadFileSize, | ||
|
|
@@ -296,11 +301,12 @@ export class UploadHttpClient { | |
| const result = await this.uploadChunk( | ||
| httpClientIndex, | ||
| parameters.resourceUrl, | ||
| fs.createReadStream(uploadFilePath, { | ||
| start, | ||
| end, | ||
| autoClose: false | ||
| }), | ||
| () => | ||
| fs.createReadStream(uploadFilePath, { | ||
| start, | ||
| end, | ||
| autoClose: false | ||
| }), | ||
| start, | ||
| end, | ||
| uploadFileSize, | ||
|
|
@@ -335,7 +341,7 @@ export class UploadHttpClient { | |
| * indicates a retryable status, we try to upload the chunk as well | ||
| * @param {number} httpClientIndex The index of the httpClient being used to make all the necessary calls | ||
| * @param {string} resourceUrl Url of the resource that the chunk will be uploaded to | ||
| * @param {NodeJS.ReadableStream} data Stream of the file that will be uploaded | ||
| * @param {NodeJS.ReadableStream} openStream Stream of the file that will be uploaded | ||
| * @param {number} start Starting byte index of file that the chunk belongs to | ||
| * @param {number} end Ending byte index of file that the chunk belongs to | ||
| * @param {number} uploadFileSize Total size of the file in bytes that is being uploaded | ||
|
|
@@ -346,7 +352,7 @@ export class UploadHttpClient { | |
| private async uploadChunk( | ||
| httpClientIndex: number, | ||
| resourceUrl: string, | ||
| data: NodeJS.ReadableStream, | ||
| openStream: () => NodeJS.ReadableStream, | ||
| start: number, | ||
| end: number, | ||
| uploadFileSize: number, | ||
|
|
@@ -365,7 +371,7 @@ export class UploadHttpClient { | |
|
|
||
| const uploadChunkRequest = async (): Promise<IHttpClientResponse> => { | ||
| const client = this.uploadHttpManager.getClient(httpClientIndex) | ||
| return await client.sendStream('PUT', resourceUrl, data, headers) | ||
| return await client.sendStream('PUT', resourceUrl, openStream(), headers) | ||
| } | ||
|
|
||
| let retryCount = 0 | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also updating these to match our filters in unit-tests.yml