Deduplicate multipart retry checks
This commit is contained in:
@@ -3,7 +3,7 @@ import {nanoid} from 'nanoid/non-secure'
|
|||||||
|
|
||||||
import {AbortError} from '#/lib/async/cancelable'
|
import {AbortError} from '#/lib/async/cancelable'
|
||||||
import {type CompressedVideo} from '#/lib/media/video/types'
|
import {type CompressedVideo} from '#/lib/media/video/types'
|
||||||
import {isRetryableHttpStatus, shouldRetryError} from '#/lib/strings/errors'
|
import {shouldRetryError} from '#/lib/strings/errors'
|
||||||
import {getServiceAuthToken} from '../upload.shared'
|
import {getServiceAuthToken} from '../upload.shared'
|
||||||
import {mimeToExt} from '../util'
|
import {mimeToExt} from '../util'
|
||||||
import {
|
import {
|
||||||
@@ -19,7 +19,7 @@ import {getMissingParts, planParts} from './planParts'
|
|||||||
import {createChunkReader} from './readChunk'
|
import {createChunkReader} from './readChunk'
|
||||||
import {createUploadPart} from './uploadPart'
|
import {createUploadPart} from './uploadPart'
|
||||||
import {uploadParts} from './uploadParts'
|
import {uploadParts} from './uploadParts'
|
||||||
import {delay} from './utils'
|
import {delay, isRetryableMultipartError} from './utils'
|
||||||
|
|
||||||
export class MultipartFallbackError extends Error {}
|
export class MultipartFallbackError extends Error {}
|
||||||
|
|
||||||
@@ -208,7 +208,7 @@ async function getUploadStatusWithRetry(
|
|||||||
return await getUploadStatus(jobId, token, signal)
|
return await getUploadStatus(jobId, token, signal)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throwIfAborted(signal)
|
throwIfAborted(signal)
|
||||||
if (!isRetryableStatusError(err)) throw err
|
if (!isRetryableMultipartError(err)) throw err
|
||||||
lastError = err
|
lastError = err
|
||||||
if (attempt < 3) await delay(500 * 2 ** (attempt - 1), signal)
|
if (attempt < 3) await delay(500 * 2 ** (attempt - 1), signal)
|
||||||
}
|
}
|
||||||
@@ -216,16 +216,6 @@ async function getUploadStatusWithRetry(
|
|||||||
throw lastError
|
throw lastError
|
||||||
}
|
}
|
||||||
|
|
||||||
function isRetryableStatusError(err: unknown) {
|
|
||||||
return (
|
|
||||||
err instanceof TypeError ||
|
|
||||||
(err instanceof MultipartUploadError &&
|
|
||||||
(err.error === 'ServiceOverloaded' ||
|
|
||||||
err.status === undefined ||
|
|
||||||
isRetryableHttpStatus(err.status)))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function abortThenFallbackOrResolve(
|
async function abortThenFallbackOrResolve(
|
||||||
jobId: string,
|
jobId: string,
|
||||||
token: string,
|
token: string,
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import {AbortError} from '#/lib/async/cancelable'
|
import {AbortError} from '#/lib/async/cancelable'
|
||||||
import {isRetryableHttpStatus} from '#/lib/strings/errors'
|
|
||||||
import {createProgressAggregator} from './aggregateProgress'
|
import {createProgressAggregator} from './aggregateProgress'
|
||||||
import {MultipartUploadError} from './api'
|
|
||||||
import {MULTIPART_CONCURRENCY, MULTIPART_MAX_ATTEMPTS} from './constants'
|
import {MULTIPART_CONCURRENCY, MULTIPART_MAX_ATTEMPTS} from './constants'
|
||||||
import {
|
import {
|
||||||
type ChunkReader,
|
type ChunkReader,
|
||||||
@@ -9,7 +7,7 @@ import {
|
|||||||
type PartUploadResult,
|
type PartUploadResult,
|
||||||
type UploadPartFn,
|
type UploadPartFn,
|
||||||
} from './types'
|
} from './types'
|
||||||
import {delay} from './utils'
|
import {delay, isRetryableMultipartError} from './utils'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Uploads every part with a concurrency cap and per-part retry, aggregating
|
* Uploads every part with a concurrency cap and per-part retry, aggregating
|
||||||
@@ -121,7 +119,7 @@ async function uploadPartWithRetry({
|
|||||||
throw new AbortError()
|
throw new AbortError()
|
||||||
}
|
}
|
||||||
lastError = err
|
lastError = err
|
||||||
if (!isRetryablePartError(err)) throw err
|
if (!isRetryableMultipartError(err)) throw err
|
||||||
if (attempt < maxAttempts) {
|
if (attempt < maxAttempts) {
|
||||||
await delay(500 * 2 ** (attempt - 1), signal)
|
await delay(500 * 2 ** (attempt - 1), signal)
|
||||||
}
|
}
|
||||||
@@ -129,13 +127,3 @@ async function uploadPartWithRetry({
|
|||||||
}
|
}
|
||||||
throw lastError
|
throw lastError
|
||||||
}
|
}
|
||||||
|
|
||||||
function isRetryablePartError(err: unknown) {
|
|
||||||
return (
|
|
||||||
err instanceof TypeError ||
|
|
||||||
(err instanceof MultipartUploadError &&
|
|
||||||
(err.error === 'ServiceOverloaded' ||
|
|
||||||
err.status === undefined ||
|
|
||||||
isRetryableHttpStatus(err.status)))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,4 +1,16 @@
|
|||||||
import {AbortError} from '#/lib/async/cancelable'
|
import {AbortError} from '#/lib/async/cancelable'
|
||||||
|
import {isRetryableHttpStatus} from '#/lib/strings/errors'
|
||||||
|
import {MultipartUploadError} from './api'
|
||||||
|
|
||||||
|
export function isRetryableMultipartError(err: unknown) {
|
||||||
|
return (
|
||||||
|
err instanceof TypeError ||
|
||||||
|
(err instanceof MultipartUploadError &&
|
||||||
|
(err.error === 'ServiceOverloaded' ||
|
||||||
|
err.status === undefined ||
|
||||||
|
isRetryableHttpStatus(err.status)))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export function delay(ms: number, signal: AbortSignal) {
|
export function delay(ms: number, signal: AbortSignal) {
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user