Skip to content

Commit

Permalink
Merge pull request OHIF#2408 from OHIF/feat/idc-2374
Browse files Browse the repository at this point in the history
IDC-2374: Exponential backoff and retry after 500 error
  • Loading branch information
igoroctaviano authored Jun 3, 2021
2 parents f0d7f2e + d575e36 commit ce4c3d5
Show file tree
Hide file tree
Showing 16 changed files with 134 additions and 15 deletions.
5 changes: 2 additions & 3 deletions extensions/debugging/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,12 @@
},
"peerDependencies": {
"@ohif/core": "^2.6.0",
"dicom-parser": "^1.8.3",
"dicomweb-client": "^0.5.2"
"dicom-parser": "^1.8.3"
},
"dependencies": {
"@babel/runtime": "^7.5.5",
"detect-browser": "5.1.1",
"dicomweb-client": "^0.6.0",
"dicomweb-client": "^0.8.1",
"file-saver": "^2.0.2",
"jszip": "^3.2.2"
}
Expand Down
2 changes: 1 addition & 1 deletion extensions/dicom-tag-browser/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
},
"dependencies": {
"@babel/runtime": "^7.5.5",
"dicomweb-client": "^0.6.0",
"dicomweb-client": "^0.8.1",
"moment": "2.24.0",
"react-select": "^3.0.8"
}
Expand Down
3 changes: 2 additions & 1 deletion platform/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,13 @@
"@babel/runtime": "^7.5.5",
"ajv": "^6.10.0",
"dcmjs": "0.18.8",
"dicomweb-client": "^0.6.0",
"dicomweb-client": "^0.8.1",
"immer": "6.0.2",
"isomorphic-base64": "^1.0.2",
"lodash.clonedeep": "^4.5.0",
"lodash.merge": "^4.6.1",
"mousetrap": "^1.6.3",
"retry": "^0.12.0",
"validate.js": "^0.12.0"
}
}
3 changes: 3 additions & 0 deletions platform/core/src/DICOMSR/handleStructuredReport.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import parseDicomStructuredReport from './parseDicomStructuredReport';
import parseMeasurementsData from './parseMeasurementsData';
import getAllDisplaySets from './utils/getAllDisplaySets';
import errorHandler from '../errorHandler';
import getXHRRetryRequestHook from '../utils/xhrRetryRequestHook';

const VERSION_NAME = 'dcmjs-0.0';
const TRANSFER_SYNTAX_UID = '1.2.840.10008.1.2.1';
Expand All @@ -23,6 +24,7 @@ const retrieveMeasurementFromSR = async (series, studies, serverUrl) => {
url: serverUrl,
headers: DICOMWeb.getAuthorizationHeader(),
errorInterceptor: errorHandler.getHTTPErrorHandler(),
requestHooks: [getXHRRetryRequestHook()],
};

const dicomWeb = new api.DICOMwebClient(config);
Expand Down Expand Up @@ -74,6 +76,7 @@ const stowSRFromMeasurements = async (measurements, serverUrl) => {
url: serverUrl,
headers: DICOMWeb.getAuthorizationHeader(),
errorInterceptor: errorHandler.getHTTPErrorHandler(),
requestHooks: [getXHRRetryRequestHook()],
};

const dicomWeb = new api.DICOMwebClient(config);
Expand Down
2 changes: 2 additions & 0 deletions platform/core/src/classes/metadata/StudyMetadata.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { isImage } from '../../utils/isImage';
import { isDisplaySetReconstructable, isSpacingUniform } from '../../utils/isDisplaySetReconstructable';
import errorHandler from '../../errorHandler';
import isLowPriorityModality from '../../utils/isLowPriorityModality';
import getXHRRetryRequestHook from '../../utils/xhrRetryRequestHook';

class StudyMetadata extends Metadata {
constructor(data, uid) {
Expand Down Expand Up @@ -939,6 +940,7 @@ function _getDisplaySetFromSopClassModule(
url: study.getData().wadoRoot,
headers,
errorInterceptor,
requestHooks: [getXHRRetryRequestHook()],
});

let displaySet = plugin.getDisplaySetFromSeries(
Expand Down
2 changes: 2 additions & 0 deletions platform/core/src/studies/services/qido/studies.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { api } from 'dicomweb-client';
import DICOMWeb from '../../../DICOMWeb/';

import errorHandler from '../../../errorHandler';
import getXHRRetryRequestHook from '../../../utils/xhrRetryRequestHook';

/**
* Creates a QIDO date string for a date range query
Expand Down Expand Up @@ -118,6 +119,7 @@ export default function Studies(server, filter) {
url: server.qidoRoot,
headers: DICOMWeb.getAuthorizationHeader(server),
errorInterceptor: errorHandler.getHTTPErrorHandler(),
requestHooks: [getXHRRetryRequestHook()],
};

const dicomWeb = new api.DICOMwebClient(config);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
} from './studyInstanceHelpers';

import errorHandler from '../../../errorHandler';
import { getXHRRetryRequestHook } from '../../../utils/xhrRetryRequestHook';

const { naturalizeDataset } = dcmjs.data.DicomMetaDictionary;

Expand Down Expand Up @@ -76,6 +77,7 @@ export default class RetrieveMetadataLoaderAsync extends RetrieveMetadataLoader
url: server.qidoRoot,
headers: DICOMWeb.getAuthorizationHeader(server),
errorInterceptor: errorHandler.getHTTPErrorHandler(),
requestHooks: [getXHRRetryRequestHook()],
});

this.client = client;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { createStudyFromSOPInstanceList } from './studyInstanceHelpers';
import RetrieveMetadataLoader from './retrieveMetadataLoader';

import errorHandler from '../../../errorHandler';
import getXHRRetryRequestHook from '../../../utils/xhrRetryRequestHook';

/**
* Class for sync load of study metadata.
Expand Down Expand Up @@ -61,6 +62,7 @@ export default class RetrieveMetadataLoaderSync extends RetrieveMetadataLoader {
url: server.wadoRoot,
headers: DICOMWeb.getAuthorizationHeader(server),
errorInterceptor: errorHandler.getHTTPErrorHandler(),
requestHooks: [getXHRRetryRequestHook()],
});

this.client = client;
Expand Down
2 changes: 2 additions & 0 deletions platform/core/src/utils/dicomLoaderService.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { api } from 'dicomweb-client';
import DICOMWeb from '../DICOMWeb';

import errorHandler from '../errorHandler';
import getXHRRetryRequestHook from './xhrRetryRequestHook';

const getImageId = imageObj => {
if (!imageObj) {
Expand Down Expand Up @@ -66,6 +67,7 @@ const wadorsRetriever = (
url,
headers,
errorInterceptor,
requestHooks: [getXHRRetryRequestHook()],
};
const dicomWeb = new api.DICOMwebClient(config);

Expand Down
3 changes: 3 additions & 0 deletions platform/core/src/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import isDicomUid from './isDicomUid';
import resolveObjectPath from './resolveObjectPath';
import * as hierarchicalListUtils from './hierarchicalListUtils';
import * as progressTrackingUtils from './progressTrackingUtils';
import xhrRetryRequestHook from './xhrRetryRequestHook';

const utils = {
guid,
Expand All @@ -40,6 +41,7 @@ const utils = {
resolveObjectPath,
hierarchicalListUtils,
progressTrackingUtils,
xhrRetryRequestHook,
};

export {
Expand All @@ -63,6 +65,7 @@ export {
resolveObjectPath,
hierarchicalListUtils,
progressTrackingUtils,
xhrRetryRequestHook,
};

export default utils;
1 change: 1 addition & 0 deletions platform/core/src/utils/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ describe('Top level exports', () => {
'resolveObjectPath',
'hierarchicalListUtils',
'progressTrackingUtils',
'xhrRetryRequestHook',
].sort();

const exports = Object.keys(utils.default).sort();
Expand Down
2 changes: 2 additions & 0 deletions platform/core/src/utils/metadataProvider/fetchOverlayData.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import str2ab from '../str2ab';
import unpackOverlay from './unpackOverlay';

import errorHandler from '../../errorHandler';
import getXHRRetryRequestHook from '../xhrRetryRequestHook';

export default async function fetchOverlayData(instance, server) {
const OverlayDataPromises = [];
Expand Down Expand Up @@ -69,6 +70,7 @@ async function _getOverlayData(tag, server) {
url: server.wadoRoot, //BulkDataURI is absolute, so this isn't used
headers: DICOMWeb.getAuthorizationHeader(server),
errorInterceptor: errorHandler.getHTTPErrorHandler(),
requestHooks: [getXHRRetryRequestHook()],
};
const dicomWeb = new api.DICOMwebClient(config);
const options = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import DICOMWeb from '../../DICOMWeb';
import str2ab from '../str2ab';

import errorHandler from '../../errorHandler';
import getXHRRetryRequestHook from '../xhrRetryRequestHook';

export default async function fetchPaletteColorLookupTableData(
instance,
Expand Down Expand Up @@ -141,6 +142,7 @@ function _getPaletteColor(server, paletteColorLookupTableData, lutDescriptor) {
url: server.wadoRoot, //BulkDataURI is absolute, so this isn't used
headers: DICOMWeb.getAuthorizationHeader(server),
errorInterceptor: errorHandler.getHTTPErrorHandler(),
requestHooks: [getXHRRetryRequestHook()],
};
const dicomWeb = new api.DICOMwebClient(config);
const options = {
Expand Down
103 changes: 103 additions & 0 deletions platform/core/src/utils/xhrRetryRequestHook.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import retry from 'retry';

const defaultRetryOptions = {
retries: 5,
factor: 3,
minTimeout: 1 * 1000,
maxTimeout: 60 * 1000,
randomize: true,
retryableStatusCodes: [429, 500],
};

let retryOptions = { ...defaultRetryOptions };

/**
* Request hook used to add retry functionality to XHR requests.
*
* @param {XMLHttpRequest} request XHR request instance
* @param {object} metadata Metadata about the request
* @param {object} metadata.url URL
* @param {object} metadata.method HTTP method
* @returns {XMLHttpRequest} request instance optionally modified
*/
const xhrRetryRequestHook = (request, metadata) => {
const { url, method } = metadata;

function faultTolerantRequestSend(...args) {
const operation = retry.operation(retryOptions);

operation.attempt(function operationAttempt(currentAttempt) {
const originalOnReadyStateChange = request.onreadystatechange;

/** Overriding/extending XHR function */
request.onreadystatechange = function onReadyStateChange(...args) {
originalOnReadyStateChange.apply(request, args);

if (retryOptions.retryableStatusCodes.includes(request.status)) {
const errorMessage = `Attempt to request ${url} failed.`;
const attemptFailedError = new Error(errorMessage);
operation.retry(attemptFailedError);
}
};

/** Call open only on retry (after headers and other things were set in the xhr instance) */
if (currentAttempt > 1) {
console.warn(`Requesting ${url}... (attempt: ${currentAttempt})`);
request.open(method, url, true);
}
});

originalRequestSend.apply(request, args);
}

/** Overriding/extending XHR function */
const originalRequestSend = request.send;
request.send = faultTolerantRequestSend;

return request;
};

/**
* Returns a configured retry request hook function
* that can be used to add retry functionality to XHR request.
*
* Default options:
* retries: 5
* factor: 3
* minTimeout: 1 * 1000
* maxTimeout: 60 * 1000
* randomize: true
*
* @param {object} options
* @param {number} options.retires number of retries
* @param {number} options.factor factor
* @param {number} options.minTimeout the min timeout
* @param {number} options.maxTimeout the max timeout
* @param {boolean} options.randomize randomize
* @param {array} options.retryableStatusCodes status codes that can trigger retry
* @returns {function} the configured retry request function
*/
export const getXHRRetryRequestHook = (options = {}) => {
retryOptions = { ...defaultRetryOptions };
if ('retries' in options) {
retryOptions.retries = options.retries;
}
if ('factor' in options) {
retryOptions.factor = options.factor;
}
if ('minTimeout' in options) {
retryOptions.minTimeout = options.minTimeout;
}
if ('maxTimeout' in options) {
retryOptions.maxTimeout = options.maxTimeout;
}
if ('randomize' in options) {
retryOptions.randomize = options.randomize;
}
if ('retryableStatusCodes' in options) {
retryOptions.retryableStatusCodes = options.retryableStatusCodes;
}
return xhrRetryRequestHook;
};

export default getXHRRetryRequestHook;
2 changes: 1 addition & 1 deletion platform/viewer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@
"cornerstone-wado-image-loader": "^3.1.0",
"dcmjs": "0.18.8",
"dicom-parser": "^1.8.3",
"dicomweb-client": "^0.4.4",
"dicomweb-client": "^0.8.1",
"hammerjs": "^2.0.8",
"i18next": "^17.0.3",
"i18next-browser-languagedetector": "^3.0.1",
Expand Down
13 changes: 4 additions & 9 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6940,20 +6940,15 @@ dicom-parser@^1.8.3:
resolved "https://registry.yarnpkg.com/dicom-parser/-/dicom-parser-1.8.3.tgz#6e4b862898112304db30143147562011c1ce6a4d"
integrity sha512-CMeUr+jea7Ml70N+/Z5Pd2MYtvLp6IU+TnvdLe6VRVKzZuTeYLYyuAQa9R+sFK4v4N39hig+hKHN+Wfi9sQ6GA==

dicomweb-client@^0.4.4:
version "0.4.4"
resolved "https://registry.yarnpkg.com/dicomweb-client/-/dicomweb-client-0.4.4.tgz#0ca0c7706556f3114330a3c40946ee66808148e4"
integrity sha512-5S7wLYxQgHnOyEgR1zlZlt79IjzOk+YDB/Jjzte6ijRZsAoj9RUiIZcsTyxvBD9gLMYs/954n2kYHdbz83Xbtw==

dicomweb-client@^0.5.2:
version "0.5.2"
resolved "https://registry.yarnpkg.com/dicomweb-client/-/dicomweb-client-0.5.2.tgz#aa5a4a6a5044b0702bb0a8a2662c86cd16901951"
integrity sha512-e11n2+g7HfBuMyopWq76W11eSZJ707g4U1XiO8URA+23aoe0g1kGAtlPB29NKXl7zX7RznTJ7wfRBxFkW6EJDA==

dicomweb-client@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/dicomweb-client/-/dicomweb-client-0.6.0.tgz#5e35ada52fe0155af1cc1f0e84f9c7f76477f92d"
integrity sha512-VAkBg4W6odIo2XsFxqjN/rptd7bQ8oHpRuKH5d46E9BUIPzRschazE8Dx1xg7/l3N3f1M70jB7yJ339arAXiDQ==
dicomweb-client@^0.8.1:
version "0.8.1"
resolved "https://registry.yarnpkg.com/dicomweb-client/-/dicomweb-client-0.8.1.tgz#12bdf0e9698bcf82730f38829ac30158164bb3fd"
integrity sha512-5EbK8epfSuonSnCW3nr2tkA1CcO1aFT3grEVvKXqv0tVyHsBvbEcEmBcPEbu1m/cF6t7+3uQRLOQ4dw4CodxFg==

diff-sequences@^24.9.0:
version "24.9.0"
Expand Down

0 comments on commit ce4c3d5

Please sign in to comment.