diff --git a/packages/backend/src/misc/is-retryable-error.ts b/packages/backend/src/misc/is-retryable-error.ts index c981a6f150..b83aefa81f 100644 --- a/packages/backend/src/misc/is-retryable-error.ts +++ b/packages/backend/src/misc/is-retryable-error.ts @@ -3,6 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ +// TODO replace all direct imports w/ the symbol import { AbortError, FetchError } from 'node-fetch'; import { UnrecoverableError } from 'bullmq'; import { StatusError } from '@/misc/status-error.js'; @@ -17,6 +18,10 @@ import { ConflictError } from '@/misc/errors/ConflictError.js'; * If the error cannot be readily identified as retryable, then recurses to the inner exception ("cause" property). */ export function isRetryableError(e: unknown): boolean { + if (hasRetryableSymbol(e)) { + return e[isRetryableSymbol]; + } + if (e instanceof AggregateError) return e.errors.every(inner => isRetryableError(inner)); if (e instanceof StatusError) return e.isRetryable; if (e instanceof IdentifiableError) return e.isRetryable; @@ -42,3 +47,17 @@ export function isRetryableError(e: unknown): boolean { return true; } + +/** + * Error classes may define a gettable property with this key to directly specify retryability. + * If the property resolves to a boolean, then that value will be used. + * Returning any other value will fall back on the usual logic. + */ +export const isRetryableSymbol = Symbol('isRetryable'); + +function hasRetryableSymbol(obj: unknown): obj is { [isRetryableSymbol]: boolean } { + return obj != null + && typeof(obj) === 'object' + && isRetryableSymbol in obj + && typeof(obj[isRetryableSymbol]) === 'boolean'; +}