packager: GlobalTransformCache: retry 502 and 503 HTTP statuses

Reviewed By: davidaurelio

Differential Revision: D4818997

fbshipit-source-id: b31b004939f9e83c8a3a8c34b07c8b1ae2d4d17e
This commit is contained in:
Jean Lauliac 2017-04-04 04:03:48 -07:00 committed by Facebook Github Bot
parent b12f6db0ef
commit 03892d1036
1 changed files with 33 additions and 8 deletions

View File

@ -144,10 +144,17 @@ class TransformProfileSet {
} }
} }
type FetchFailedDetails =
{+type: 'unhandled_http_status', +statusCode: number} | {+type: 'unspecified'};
class FetchFailedError extends Error { class FetchFailedError extends Error {
constructor(message) { /** Separate object for details allows us to have a type union. */
+details: FetchFailedDetails;
constructor(message: string, details: FetchFailedDetails) {
super(); super();
this.message = message; this.message = message;
(this: any).details = details;
} }
} }
@ -223,31 +230,49 @@ class GlobalTransformCache {
const response = await fetch(uri, {method: 'GET', timeout: 8000}); const response = await fetch(uri, {method: 'GET', timeout: 8000});
if (response.status !== 200) { if (response.status !== 200) {
const msg = `Unexpected HTTP status: ${response.status} ${response.statusText} `; const msg = `Unexpected HTTP status: ${response.status} ${response.statusText} `;
throw new FetchFailedError(msg); throw new FetchFailedError(msg, {
type: 'unhandled_http_status',
statusCode: response.status,
});
} }
const unvalidatedResult = await response.json(); const unvalidatedResult = await response.json();
const result = validateCachedResult(unvalidatedResult); const result = validateCachedResult(unvalidatedResult);
if (result == null) { if (result == null) {
throw new FetchFailedError('Server returned invalid result.'); throw new FetchFailedError('Server returned invalid result.', {type: 'unspecified'});
} }
return result; return result;
} }
/** /**
* It happens from time to time that a fetch timeouts, we want to try these * It happens from time to time that a fetch fails, we want to try these again
* again a second time. * a second time if we expect them to be transient. We might even consider
* waiting a little time before retring if experience shows it's useful.
*/ */
static fetchResultFromURI(uri: string): Promise<CachedResult> { static fetchResultFromURI(uri: string): Promise<CachedResult> {
return GlobalTransformCache._fetchResultFromURI(uri).catch(error => { return GlobalTransformCache._fetchResultFromURI(uri).catch(error => {
if (!GlobalTransformCache.isTimeoutError(error)) { if (!GlobalTransformCache.shouldRetryAfterThatError(error)) {
throw error; throw error;
} }
return this._fetchResultFromURI(uri); return this._fetchResultFromURI(uri);
}); });
} }
static isTimeoutError(error: Error): boolean { /**
return error instanceof FetchError && error.type === 'request-timeout'; * We want to retry timeouts as they're likely temporary. We retry 503
* (Service Unavailable) and 502 (Bad Gateway) because they may be caused by a
* some rogue server, or because of throttling.
*
* There may be other types of error we'd want to retry for, but these are
* the ones we experienced the most in practice.
*/
static shouldRetryAfterThatError(error: Error): boolean {
return (
error instanceof FetchError && error.type === 'request-timeout' || (
error instanceof FetchFailedError &&
error.details.type === 'wrong_http_status' &&
(error.details.statusCode === 503 || error.details.statusCode === 502)
)
);
} }
shouldFetch(props: FetchProps): boolean { shouldFetch(props: FetchProps): boolean {