From d26929d0284124d2d49df2f9c3f720387906e0b7 Mon Sep 17 00:00:00 2001 From: Chris Sidi Date: Wed, 25 Nov 2020 23:04:07 -0500 Subject: [PATCH 01/11] Retry artifact download when response stream is truncated --- __tests__/download.test.ts | 71 +++++++++++++++++++++++++--- src/internal/download-http-client.ts | 6 +++ 2 files changed, 71 insertions(+), 6 deletions(-) diff --git a/__tests__/download.test.ts b/__tests__/download.test.ts index 305bfa8..81759d4 100644 --- a/__tests__/download.test.ts +++ b/__tests__/download.test.ts @@ -124,7 +124,7 @@ describe('Download Tests', () => { ) const targetPath = path.join(root, 'FileA.txt') - setupDownloadItemResponse(true, 200, fileContents) + setupDownloadItemResponse(true, 200, false, fileContents) const downloadHttpClient = new DownloadHttpClient() const items: DownloadItem[] = [] @@ -147,7 +147,7 @@ describe('Download Tests', () => { ) const targetPath = path.join(root, 'FileB.txt') - setupDownloadItemResponse(false, 200, fileContents) + setupDownloadItemResponse(false, 200, false, fileContents) const downloadHttpClient = new DownloadHttpClient() const items: DownloadItem[] = [] @@ -171,7 +171,7 @@ describe('Download Tests', () => { const fileContents = Buffer.from('try, try again\n', defaultEncoding) const targetPath = path.join(root, `FileC-${statusCode}.txt`) - setupDownloadItemResponse(false, statusCode, fileContents) + setupDownloadItemResponse(false, statusCode, false, fileContents) const downloadHttpClient = new DownloadHttpClient() const items: DownloadItem[] = [] @@ -188,6 +188,52 @@ describe('Download Tests', () => { } }) + it('Test retry on truncated response with gzip', async () => { + const fileContents = Buffer.from( + 'Sometimes gunzip fails on the first try\n', + defaultEncoding + ) + const targetPath = path.join(root, 'FileD.txt') + + setupDownloadItemResponse(true, 200, true, fileContents) + const downloadHttpClient = new DownloadHttpClient() + + const items: DownloadItem[] = [] + items.push({ + sourceLocation: `${configVariables.getRuntimeUrl()}_apis/resources/Containers/13?itemPath=my-artifact%2FFileD.txt`, + targetPath + }) + + await expect( + downloadHttpClient.downloadSingleArtifact(items) + ).resolves.not.toThrow() + + await checkDestinationFile(targetPath, fileContents) + }) + + it('Test retry on truncated response without gzip', async () => { + const fileContents = Buffer.from( + 'You have to inspect the content-length header to know if you got everything\n', + defaultEncoding + ) + const targetPath = path.join(root, 'FileE.txt') + + setupDownloadItemResponse(false, 200, true, fileContents) + const downloadHttpClient = new DownloadHttpClient() + + const items: DownloadItem[] = [] + items.push({ + sourceLocation: `${configVariables.getRuntimeUrl()}_apis/resources/Containers/13?itemPath=my-artifact%2FFileD.txt`, + targetPath + }) + + await expect( + downloadHttpClient.downloadSingleArtifact(items) + ).resolves.not.toThrow() + + await checkDestinationFile(targetPath, fileContents) + }) + /** * Helper used to setup mocking for the HttpClient */ @@ -253,17 +299,24 @@ describe('Download Tests', () => { function setupDownloadItemResponse( isGzip: boolean, firstHttpResponseCode: number, + truncateFirstResponse: boolean, fileContents: Buffer ): void { const spyInstance = jest .spyOn(HttpClient.prototype, 'get') .mockImplementationOnce(async () => { if (firstHttpResponseCode === 200) { + const fullResponse = await constructResponse(isGzip, fileContents) + const actualResponse = truncateFirstResponse + ? fullResponse.subarray(0, 3) + : fullResponse + return { message: getDownloadResponseMessage( firstHttpResponseCode, isGzip, - await constructResponse(isGzip, fileContents) + fullResponse.length, + actualResponse ), readBody: emptyMockReadBody } @@ -272,6 +325,7 @@ describe('Download Tests', () => { message: getDownloadResponseMessage( firstHttpResponseCode, false, + 0, null ), readBody: emptyMockReadBody @@ -283,11 +337,13 @@ describe('Download Tests', () => { if (firstHttpResponseCode !== 200) { spyInstance.mockImplementationOnce(async () => { // chained response, if the HTTP GET function gets called again, return a successful response + const fullResponse = await constructResponse(isGzip, fileContents) return { message: getDownloadResponseMessage( 200, isGzip, - await constructResponse(isGzip, fileContents) + fullResponse.length, + fullResponse ), readBody: emptyMockReadBody } @@ -311,6 +367,7 @@ describe('Download Tests', () => { function getDownloadResponseMessage( httpResponseCode: number, isGzip: boolean, + contentLength: number, response: Buffer | null ): http.IncomingMessage { let readCallCount = 0 @@ -335,7 +392,9 @@ describe('Download Tests', () => { }) mockMessage.statusCode = httpResponseCode - mockMessage.headers = {} + mockMessage.headers = { + 'content-length': contentLength.toString() + } if (isGzip) { mockMessage.headers['content-encoding'] = 'gzip' diff --git a/src/internal/download-http-client.ts b/src/internal/download-http-client.ts index 70ea4e4..3b00872 100644 --- a/src/internal/download-http-client.ts +++ b/src/internal/download-http-client.ts @@ -264,6 +264,12 @@ export class DownloadHttpClient { const gunzip = zlib.createGunzip() response.message .pipe(gunzip) + .on('error', error => { + core.error( + `An error has been encountered while attempting to decompress a file` + ) + reject(error) + }) .pipe(destinationStream) .on('close', () => { resolve() From 61e11bd168a80d39545faa660df16d0a5f8636fc Mon Sep 17 00:00:00 2001 From: Chris Sidi Date: Thu, 26 Nov 2020 01:16:06 -0500 Subject: [PATCH 02/11] More error handling --- src/internal/download-http-client.ts | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/internal/download-http-client.ts b/src/internal/download-http-client.ts index 3b00872..64631c5 100644 --- a/src/internal/download-http-client.ts +++ b/src/internal/download-http-client.ts @@ -263,12 +263,20 @@ export class DownloadHttpClient { if (isGzip) { const gunzip = zlib.createGunzip() response.message + .on('error', error => { + core.error( + `An error occurred while attempting to read the response stream` + ) + reject(error) + gunzip.close() + }) .pipe(gunzip) .on('error', error => { core.error( - `An error has been encountered while attempting to decompress a file` + `An error occurred while attempting to decompress the response stream` ) reject(error) + destinationStream.close() }) .pipe(destinationStream) .on('close', () => { @@ -276,19 +284,26 @@ export class DownloadHttpClient { }) .on('error', error => { core.error( - `An error has been encountered while decompressing and writing a downloaded file to ${destinationStream.path}` + `An error occurred while writing a downloaded file to ${destinationStream.path}` ) reject(error) }) } else { response.message + .on('error', error => { + core.error( + `An error occurred while attempting to read the response stream` + ) + reject(error) + destinationStream.close() + }) .pipe(destinationStream) .on('close', () => { resolve() }) .on('error', error => { core.error( - `An error has been encountered while writing a downloaded file to ${destinationStream.path}` + `An error occurred while writing a downloaded file to ${destinationStream.path}` ) reject(error) }) From 9d3d4bec1ba5551b5618ac9389204cac510e8a73 Mon Sep 17 00:00:00 2001 From: Yang Cao Date: Thu, 3 Dec 2020 10:13:36 -0500 Subject: [PATCH 03/11] Adding retry when we received 200 If we received 200, we will attempt to download the stream. If the stream is gzipped, gzip should throw an error when trying to decompress the stream; or if the stream is truncated, the received bytes should be different from the value set in content-length header. --- __tests__/download.test.ts | 28 ++++++++++---- src/internal/download-http-client.ts | 57 ++++++++++++++++++++++++---- src/internal/utils.ts | 9 +++++ 3 files changed, 79 insertions(+), 15 deletions(-) diff --git a/__tests__/download.test.ts b/__tests__/download.test.ts index 81759d4..518cfc5 100644 --- a/__tests__/download.test.ts +++ b/__tests__/download.test.ts @@ -124,7 +124,7 @@ describe('Download Tests', () => { ) const targetPath = path.join(root, 'FileA.txt') - setupDownloadItemResponse(true, 200, false, fileContents) + setupDownloadItemResponse(true, 200, false, fileContents, false) const downloadHttpClient = new DownloadHttpClient() const items: DownloadItem[] = [] @@ -147,7 +147,7 @@ describe('Download Tests', () => { ) const targetPath = path.join(root, 'FileB.txt') - setupDownloadItemResponse(false, 200, false, fileContents) + setupDownloadItemResponse(false, 200, false, fileContents, false) const downloadHttpClient = new DownloadHttpClient() const items: DownloadItem[] = [] @@ -171,7 +171,7 @@ describe('Download Tests', () => { const fileContents = Buffer.from('try, try again\n', defaultEncoding) const targetPath = path.join(root, `FileC-${statusCode}.txt`) - setupDownloadItemResponse(false, statusCode, false, fileContents) + setupDownloadItemResponse(false, statusCode, false, fileContents, true) const downloadHttpClient = new DownloadHttpClient() const items: DownloadItem[] = [] @@ -195,7 +195,8 @@ describe('Download Tests', () => { ) const targetPath = path.join(root, 'FileD.txt') - setupDownloadItemResponse(true, 200, true, fileContents) + setupDownloadItemResponse(true, 200, true, fileContents, true) + setupThrowWhenReadingStream() const downloadHttpClient = new DownloadHttpClient() const items: DownloadItem[] = [] @@ -218,7 +219,7 @@ describe('Download Tests', () => { ) const targetPath = path.join(root, 'FileE.txt') - setupDownloadItemResponse(false, 200, true, fileContents) + setupDownloadItemResponse(false, 200, true, fileContents, true) const downloadHttpClient = new DownloadHttpClient() const items: DownloadItem[] = [] @@ -291,6 +292,18 @@ describe('Download Tests', () => { }) } + function setupThrowWhenReadingStream(): void { + const spyInstance = jest + .spyOn(DownloadHttpClient.prototype, 'pipeResponseToFile') + .mockImplementationOnce(async () => { + return new Promise(resolve => { + throw new Error( + 'simulate GZip reading truncated buffer and throw Z_BUF_ERROR' + ) + }) + }) + } + /** * Setups up HTTP GET response for downloading items * @param isGzip is the downloaded item gzip encoded @@ -300,7 +313,8 @@ describe('Download Tests', () => { isGzip: boolean, firstHttpResponseCode: number, truncateFirstResponse: boolean, - fileContents: Buffer + fileContents: Buffer, + retryExpected: boolean ): void { const spyInstance = jest .spyOn(HttpClient.prototype, 'get') @@ -334,7 +348,7 @@ describe('Download Tests', () => { }) // set up a second mock only if we expect a retry. Otherwise this mock will affect other tests. - if (firstHttpResponseCode !== 200) { + if (retryExpected) { spyInstance.mockImplementationOnce(async () => { // chained response, if the HTTP GET function gets called again, return a successful response const fullResponse = await constructResponse(isGzip, fileContents) diff --git a/src/internal/download-http-client.ts b/src/internal/download-http-client.ts index 64631c5..2110b6a 100644 --- a/src/internal/download-http-client.ts +++ b/src/internal/download-http-client.ts @@ -9,7 +9,9 @@ import { isThrottledStatusCode, getExponentialRetryTimeInMilliseconds, tryGetRetryAfterValueTimeInMilliseconds, - displayHttpDiagnostics + displayHttpDiagnostics, + getFileSize, + rmFile } from './utils' import {URL} from 'url' import {StatusReporter} from './status-reporter' @@ -151,7 +153,7 @@ export class DownloadHttpClient { ): Promise { let retryCount = 0 const retryLimit = getRetryLimit() - const destinationStream = fs.createWriteStream(downloadPath) + let destinationStream = fs.createWriteStream(downloadPath) const headers = getDownloadHeaders('application/json', true, true) // a single GET request is used to download a file @@ -201,6 +203,27 @@ export class DownloadHttpClient { } } + const isAllBytesReceived = ( + expected?: string, + received?: number + ): boolean => { + // be lenient, if any input is missing, assume success, i.e. not truncated + if ( + !expected || + !received || + process.env['ACTIONS_ARTIFACT_SKIP_DOWNLOAD_VALIDATION'] + ) { + return true + } + + return parseInt(expected) === received + } + + const resetDestinationStream = async (downloadPath: string) => { + await rmFile(downloadPath) + destinationStream = fs.createWriteStream(downloadPath) + } + // keep trying to download a file until a retry limit has been reached while (retryCount <= retryLimit) { let response: IHttpClientResponse @@ -217,19 +240,37 @@ export class DownloadHttpClient { continue } + let forceRetry = false if (isSuccessStatusCode(response.message.statusCode)) { // The body contains the contents of the file however calling response.readBody() causes all the content to be converted to a string // which can cause some gzip encoded data to be lost // Instead of using response.readBody(), response.message is a readableStream that can be directly used to get the raw body contents - return this.pipeResponseToFile( - response, - destinationStream, - isGzip(response.message.headers) - ) - } else if (isRetryableStatusCode(response.message.statusCode)) { + try { + const isGzipped = isGzip(response.message.headers) + await this.pipeResponseToFile(response, destinationStream, isGzipped) + + if ( + isGzipped || + isAllBytesReceived( + response.message.headers['content-length'], + await getFileSize(downloadPath) + ) + ) { + return + } else { + forceRetry = true + } + } catch (error) { + // retry on error, most likely streams were corrupted + forceRetry = true + } + } + + if (forceRetry || isRetryableStatusCode(response.message.statusCode)) { core.info( `A ${response.message.statusCode} response code has been received while attempting to download an artifact` ) + resetDestinationStream(downloadPath) // if a throttled status code is received, try to get the retryAfter header value, else differ to standard exponential backoff isThrottledStatusCode(response.message.statusCode) ? await backOff( diff --git a/src/internal/utils.ts b/src/internal/utils.ts index a9f76b2..ea20364 100644 --- a/src/internal/utils.ts +++ b/src/internal/utils.ts @@ -303,6 +303,15 @@ export async function createEmptyFilesForArtifact( } } +export async function getFileSize(filePath: string): Promise { + const stats = await fs.stat(filePath) + return stats.size +} + +export async function rmFile(filePath: string): Promise { + await fs.unlink(filePath) +} + export function getProperRetention( retentionInput: number, retentionSetting: string | undefined From 8f05cccf8e267aaac0b304da509f58c449cf033a Mon Sep 17 00:00:00 2001 From: Yang Cao Date: Thu, 3 Dec 2020 10:33:10 -0500 Subject: [PATCH 04/11] Fix styling --- __tests__/download.test.ts | 4 ++-- src/internal/download-http-client.ts | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/__tests__/download.test.ts b/__tests__/download.test.ts index 518cfc5..b940fde 100644 --- a/__tests__/download.test.ts +++ b/__tests__/download.test.ts @@ -293,10 +293,10 @@ describe('Download Tests', () => { } function setupThrowWhenReadingStream(): void { - const spyInstance = jest + jest .spyOn(DownloadHttpClient.prototype, 'pipeResponseToFile') .mockImplementationOnce(async () => { - return new Promise(resolve => { + return new Promise(() => { throw new Error( 'simulate GZip reading truncated buffer and throw Z_BUF_ERROR' ) diff --git a/src/internal/download-http-client.ts b/src/internal/download-http-client.ts index 2110b6a..7caef99 100644 --- a/src/internal/download-http-client.ts +++ b/src/internal/download-http-client.ts @@ -219,7 +219,9 @@ export class DownloadHttpClient { return parseInt(expected) === received } - const resetDestinationStream = async (downloadPath: string) => { + const resetDestinationStream = async ( + downloadPath: string + ): Promise => { await rmFile(downloadPath) destinationStream = fs.createWriteStream(downloadPath) } From 09e68f92cd6264af888ff3a8c520e125a4db0432 Mon Sep 17 00:00:00 2001 From: Yang Cao Date: Thu, 3 Dec 2020 10:57:04 -0500 Subject: [PATCH 05/11] More styling fix --- src/internal/download-http-client.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/internal/download-http-client.ts b/src/internal/download-http-client.ts index 7caef99..201a5f7 100644 --- a/src/internal/download-http-client.ts +++ b/src/internal/download-http-client.ts @@ -220,10 +220,10 @@ export class DownloadHttpClient { } const resetDestinationStream = async ( - downloadPath: string + fileDownloadPath: string ): Promise => { - await rmFile(downloadPath) - destinationStream = fs.createWriteStream(downloadPath) + await rmFile(fileDownloadPath) + destinationStream = fs.createWriteStream(fileDownloadPath) } // keep trying to download a file until a retry limit has been reached From 1e10fa415cd17802162a65f784713fd24e8ddd5a Mon Sep 17 00:00:00 2001 From: Yang Cao Date: Thu, 3 Dec 2020 12:40:37 -0500 Subject: [PATCH 06/11] Add some debugging statements --- src/internal/download-http-client.ts | 8 ++++++++ src/internal/utils.ts | 3 +++ 2 files changed, 11 insertions(+) diff --git a/src/internal/download-http-client.ts b/src/internal/download-http-client.ts index 201a5f7..10b59dd 100644 --- a/src/internal/download-http-client.ts +++ b/src/internal/download-http-client.ts @@ -213,6 +213,11 @@ export class DownloadHttpClient { !received || process.env['ACTIONS_ARTIFACT_SKIP_DOWNLOAD_VALIDATION'] ) { + if (process.env['ACTIONS_ARTIFACT_SKIP_DOWNLOAD_VALIDATION']) { + core.info( + 'Skipping download validation since environment variable is set' + ) + } return true } @@ -231,6 +236,9 @@ export class DownloadHttpClient { let response: IHttpClientResponse try { response = await makeDownloadRequest() + if (core.isDebug()) { + displayHttpDiagnostics(response) + } } catch (error) { // if an error is caught, it is usually indicative of a timeout so retry the download core.info('An error occurred while attempting to download a file') diff --git a/src/internal/utils.ts b/src/internal/utils.ts index ea20364..2f4f2f4 100644 --- a/src/internal/utils.ts +++ b/src/internal/utils.ts @@ -305,6 +305,9 @@ export async function createEmptyFilesForArtifact( export async function getFileSize(filePath: string): Promise { const stats = await fs.stat(filePath) + debug( + `${filePath} size:(${stats.size}) blksize:(${stats.blksize}) blocks:(${stats.blocks})` + ) return stats.size } From bd9c0f996d85ede7fe190b4bef142819f497cc35 Mon Sep 17 00:00:00 2001 From: Yang Cao Date: Thu, 3 Dec 2020 13:09:43 -0500 Subject: [PATCH 07/11] Always print skip validation log --- src/internal/download-http-client.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/internal/download-http-client.ts b/src/internal/download-http-client.ts index 10b59dd..7fb06a4 100644 --- a/src/internal/download-http-client.ts +++ b/src/internal/download-http-client.ts @@ -213,11 +213,7 @@ export class DownloadHttpClient { !received || process.env['ACTIONS_ARTIFACT_SKIP_DOWNLOAD_VALIDATION'] ) { - if (process.env['ACTIONS_ARTIFACT_SKIP_DOWNLOAD_VALIDATION']) { - core.info( - 'Skipping download validation since environment variable is set' - ) - } + core.info('Skipping download validation.') return true } From 760a39afb9f2c4703e393b725927f5d2bcab233d Mon Sep 17 00:00:00 2001 From: Yang Cao Date: Thu, 3 Dec 2020 13:34:59 -0500 Subject: [PATCH 08/11] Try close stream before unlink --- src/internal/download-http-client.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/internal/download-http-client.ts b/src/internal/download-http-client.ts index 7fb06a4..bd409eb 100644 --- a/src/internal/download-http-client.ts +++ b/src/internal/download-http-client.ts @@ -223,6 +223,7 @@ export class DownloadHttpClient { const resetDestinationStream = async ( fileDownloadPath: string ): Promise => { + destinationStream.close() await rmFile(fileDownloadPath) destinationStream = fs.createWriteStream(fileDownloadPath) } From 2b5da1115648610b991e6d6511d4ee6128828cd5 Mon Sep 17 00:00:00 2001 From: Yang Cao Date: Fri, 4 Dec 2020 09:19:58 -0500 Subject: [PATCH 09/11] pipeResponseToFile already throws when reading truncated stream --- __tests__/download.test.ts | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/__tests__/download.test.ts b/__tests__/download.test.ts index b940fde..ec07a1f 100644 --- a/__tests__/download.test.ts +++ b/__tests__/download.test.ts @@ -196,7 +196,6 @@ describe('Download Tests', () => { const targetPath = path.join(root, 'FileD.txt') setupDownloadItemResponse(true, 200, true, fileContents, true) - setupThrowWhenReadingStream() const downloadHttpClient = new DownloadHttpClient() const items: DownloadItem[] = [] @@ -292,18 +291,6 @@ describe('Download Tests', () => { }) } - function setupThrowWhenReadingStream(): void { - jest - .spyOn(DownloadHttpClient.prototype, 'pipeResponseToFile') - .mockImplementationOnce(async () => { - return new Promise(() => { - throw new Error( - 'simulate GZip reading truncated buffer and throw Z_BUF_ERROR' - ) - }) - }) - } - /** * Setups up HTTP GET response for downloading items * @param isGzip is the downloaded item gzip encoded From 2c4f1a3166338917c50dd354ae2b25b6fdca5be0 Mon Sep 17 00:00:00 2001 From: Yang Cao Date: Fri, 4 Dec 2020 09:41:00 -0500 Subject: [PATCH 10/11] style change, refactor arg sequence --- __tests__/download.test.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/__tests__/download.test.ts b/__tests__/download.test.ts index ec07a1f..b77e5a2 100644 --- a/__tests__/download.test.ts +++ b/__tests__/download.test.ts @@ -124,7 +124,7 @@ describe('Download Tests', () => { ) const targetPath = path.join(root, 'FileA.txt') - setupDownloadItemResponse(true, 200, false, fileContents, false) + setupDownloadItemResponse(fileContents, true, 200, false, false) const downloadHttpClient = new DownloadHttpClient() const items: DownloadItem[] = [] @@ -147,7 +147,7 @@ describe('Download Tests', () => { ) const targetPath = path.join(root, 'FileB.txt') - setupDownloadItemResponse(false, 200, false, fileContents, false) + setupDownloadItemResponse(fileContents, false, 200, false, false) const downloadHttpClient = new DownloadHttpClient() const items: DownloadItem[] = [] @@ -171,7 +171,7 @@ describe('Download Tests', () => { const fileContents = Buffer.from('try, try again\n', defaultEncoding) const targetPath = path.join(root, `FileC-${statusCode}.txt`) - setupDownloadItemResponse(false, statusCode, false, fileContents, true) + setupDownloadItemResponse(fileContents, false, statusCode, false, true) const downloadHttpClient = new DownloadHttpClient() const items: DownloadItem[] = [] @@ -195,7 +195,7 @@ describe('Download Tests', () => { ) const targetPath = path.join(root, 'FileD.txt') - setupDownloadItemResponse(true, 200, true, fileContents, true) + setupDownloadItemResponse(fileContents, true, 200, true, true) const downloadHttpClient = new DownloadHttpClient() const items: DownloadItem[] = [] @@ -218,7 +218,7 @@ describe('Download Tests', () => { ) const targetPath = path.join(root, 'FileE.txt') - setupDownloadItemResponse(false, 200, true, fileContents, true) + setupDownloadItemResponse(fileContents, false, 200, true, true) const downloadHttpClient = new DownloadHttpClient() const items: DownloadItem[] = [] @@ -297,10 +297,10 @@ describe('Download Tests', () => { * @param firstHttpResponseCode the http response code that should be returned */ function setupDownloadItemResponse( + fileContents: Buffer, isGzip: boolean, firstHttpResponseCode: number, truncateFirstResponse: boolean, - fileContents: Buffer, retryExpected: boolean ): void { const spyInstance = jest From 6f33e4c7b00e85e23dd03740e3c7887e7816d823 Mon Sep 17 00:00:00 2001 From: Yang Cao Date: Fri, 4 Dec 2020 11:03:17 -0500 Subject: [PATCH 11/11] Close destination steam before reject Co-authored-by: Chris Sidi --- src/internal/download-http-client.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/internal/download-http-client.ts b/src/internal/download-http-client.ts index bd409eb..e090c54 100644 --- a/src/internal/download-http-client.ts +++ b/src/internal/download-http-client.ts @@ -315,16 +315,17 @@ export class DownloadHttpClient { core.error( `An error occurred while attempting to read the response stream` ) - reject(error) gunzip.close() + destinationStream.close() + reject(error) }) .pipe(gunzip) .on('error', error => { core.error( `An error occurred while attempting to decompress the response stream` ) - reject(error) destinationStream.close() + reject(error) }) .pipe(destinationStream) .on('close', () => { @@ -342,8 +343,8 @@ export class DownloadHttpClient { core.error( `An error occurred while attempting to read the response stream` ) - reject(error) destinationStream.close() + reject(error) }) .pipe(destinationStream) .on('close', () => {