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.
This commit is contained in:
Yang Cao
2020-12-03 10:13:36 -05:00
parent 990647a104
commit 6b83f0554a
3 changed files with 79 additions and 15 deletions

View File

@@ -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<void> {
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(

View File

@@ -303,6 +303,15 @@ export async function createEmptyFilesForArtifact(
}
}
export async function getFileSize(filePath: string): Promise<number> {
const stats = await fs.stat(filePath)
return stats.size
}
export async function rmFile(filePath: string): Promise<void> {
await fs.unlink(filePath)
}
export function getProperRetention(
retentionInput: number,
retentionSetting: string | undefined