diff --git a/src/mol-util/retry-if.ts b/src/mol-util/retry-if.ts new file mode 100644 index 0000000000000000000000000000000000000000..c78712dcddb5e8e833a091efe65c49ecfdccc8e4 --- /dev/null +++ b/src/mol-util/retry-if.ts @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +export async function retryIf<T>(promiseProvider: () => Promise<T>, params: { + retryThenIf?: (result: T) => boolean, + retryCatchIf?: (error: any) => boolean, + retryCount: number +}) { + let count = 0; + while (count <= params.retryCount) { + try { + const result = await promiseProvider(); + if (params.retryThenIf && params.retryThenIf(result)) { + count++; + continue; + } + return result; + } catch (e) { + if (!params.retryCatchIf || params.retryCatchIf(e)) { + count++; + continue; + } + throw e; + } + } + + throw new Error('Maximum retry count exceeded.'); +} \ No newline at end of file diff --git a/src/servers/model/properties/structure-quality-report.ts b/src/servers/model/properties/structure-quality-report.ts index b65a1d959befcda3a52354f0b6fe9162e251163d..e46fb5fdce65112f686397031dafa68a66aae33d 100644 --- a/src/servers/model/properties/structure-quality-report.ts +++ b/src/servers/model/properties/structure-quality-report.ts @@ -4,12 +4,12 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import { ResidueIndex, ModelPropertyDescriptor, Model, Structure, Unit, StructureElement } from 'mol-model/structure'; -import fetch from 'node-fetch'; -import { CifWriter } from 'mol-io/writer/cif'; -import CifField = CifWriter.Field; import { Segmentation } from 'mol-data/int'; +import { CifWriter } from 'mol-io/writer/cif'; +import { Model, ModelPropertyDescriptor, ResidueIndex, Structure, StructureElement, Unit } from 'mol-model/structure'; import { residueIdFields } from 'mol-model/structure/export/categories/atom_site'; +import { fetchRetry } from '../utils/fetch-retry'; +import CifField = CifWriter.Field; type IssueMap = Map<ResidueIndex, string[]> @@ -99,7 +99,7 @@ export namespace StructureQualityReport { if (model.customProperties.has(Descriptor)) return true; const id = model.label.toLowerCase(); - const rawData = await fetch(`https://www.ebi.ac.uk/pdbe/api/validation/residuewise_outlier_summary/entry/${model.label.toLowerCase()}`, { timeout: 1500 }); + const rawData = await fetchRetry(`https://www.ebi.ac.uk/pdbe/api/validation/residuewise_outlier_summary/entry/${model.label.toLowerCase()}`, 1500, 5); const json = await rawData.json(); const data = json[id]; if (!data) return false; diff --git a/src/servers/model/utils/fetch-retry.ts b/src/servers/model/utils/fetch-retry.ts new file mode 100644 index 0000000000000000000000000000000000000000..7772212743ed83241a5bfe8e3d020d20f96f7837 --- /dev/null +++ b/src/servers/model/utils/fetch-retry.ts @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import fetch from 'node-fetch'; +import { retryIf } from 'mol-util/retry-if'; + +const RETRIABLE_NETWORK_ERRORS = [ + 'ECONNRESET', 'ENOTFOUND', 'ESOCKETTIMEDOUT', 'ETIMEDOUT', + 'ECONNREFUSED', 'EHOSTUNREACH', 'EPIPE', 'EAI_AGAIN' +]; + +function isRetriableNetworkError(error: any) { + return error && RETRIABLE_NETWORK_ERRORS.includes(error.code); +} + +export async function fetchRetry(url: string, timeout: number, retryCount: number) { + const result = await retryIf(() => fetch(url, { timeout }), { + retryThenIf: r => r.status >= 500 && r.status < 600, + // TODO test retryCatchIf + retryCatchIf: e => isRetriableNetworkError(e), + retryCount + }); + + if (result.status >= 200 && result.status < 300) return result; + throw new Error(result.statusText); +}