1
0
Fork 0
toolkit/packages/cache/src/internal/requestUtils.ts

139 lines
3.3 KiB
TypeScript
Raw Normal View History

import * as core from '@actions/core'
import {
HttpCodes,
HttpClientError,
HttpClientResponse
} from '@actions/http-client'
2020-10-12 18:21:01 +00:00
import {DefaultRetryDelay, DefaultRetryAttempts} from './constants'
import {ITypedResponseWithError} from './contracts'
export function isSuccessStatusCode(statusCode?: number): boolean {
if (!statusCode) {
return false
}
return statusCode >= 200 && statusCode < 300
}
export function isServerErrorStatusCode(statusCode?: number): boolean {
if (!statusCode) {
return true
}
return statusCode >= 500
}
export function isRetryableStatusCode(statusCode?: number): boolean {
if (!statusCode) {
return false
}
const retryableStatusCodes = [
HttpCodes.BadGateway,
HttpCodes.ServiceUnavailable,
HttpCodes.GatewayTimeout
]
return retryableStatusCodes.includes(statusCode)
}
2020-08-17 20:32:04 +00:00
async function sleep(milliseconds: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, milliseconds))
}
export async function retry<T>(
name: string,
method: () => Promise<T>,
getStatusCode: (arg0: T) => number | undefined,
2020-10-12 18:21:01 +00:00
maxAttempts = DefaultRetryAttempts,
delay = DefaultRetryDelay,
onError: ((arg0: Error) => T | undefined) | undefined = undefined
): Promise<T> {
let errorMessage = ''
let attempt = 1
while (attempt <= maxAttempts) {
let response: T | undefined = undefined
let statusCode: number | undefined = undefined
let isRetryable = false
try {
response = await method()
} catch (error) {
if (onError) {
response = onError(error)
}
isRetryable = true
errorMessage = error.message
}
if (response) {
statusCode = getStatusCode(response)
if (!isServerErrorStatusCode(statusCode)) {
return response
}
}
if (statusCode) {
isRetryable = isRetryableStatusCode(statusCode)
errorMessage = `Cache service responded with ${statusCode}`
}
2020-08-17 20:32:04 +00:00
core.debug(
`${name} - Attempt ${attempt} of ${maxAttempts} failed with error: ${errorMessage}`
)
if (!isRetryable) {
core.debug(`${name} - Error is not retryable`)
break
}
2020-08-17 20:32:04 +00:00
await sleep(delay)
attempt++
}
throw Error(`${name} failed: ${errorMessage}`)
}
export async function retryTypedResponse<T>(
name: string,
method: () => Promise<ITypedResponseWithError<T>>,
2020-10-12 18:21:01 +00:00
maxAttempts = DefaultRetryAttempts,
delay = DefaultRetryDelay
): Promise<ITypedResponseWithError<T>> {
return await retry(
name,
method,
(response: ITypedResponseWithError<T>) => response.statusCode,
maxAttempts,
2020-10-12 18:21:01 +00:00
delay,
// If the error object contains the statusCode property, extract it and return
// an TypedResponse<T> so it can be processed by the retry logic.
2020-10-12 18:21:01 +00:00
(error: Error) => {
if (error instanceof HttpClientError) {
return {
2020-10-12 18:21:01 +00:00
statusCode: error.statusCode,
result: null,
headers: {},
error
}
} else {
return undefined
}
}
)
}
export async function retryHttpClientResponse(
name: string,
method: () => Promise<HttpClientResponse>,
2020-10-12 18:21:01 +00:00
maxAttempts = DefaultRetryAttempts,
delay = DefaultRetryDelay
): Promise<HttpClientResponse> {
return await retry(
name,
method,
(response: HttpClientResponse) => response.message.statusCode,
2020-08-17 20:32:04 +00:00
maxAttempts,
2020-10-12 18:21:01 +00:00
delay
)
}