-
Notifications
You must be signed in to change notification settings - Fork 315
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: Lockup in request pool when returning non promise result #990
Conversation
✅ Deploy Preview for cornerstone-3d-docs ready!
To edit notification comments on pull requests, go to your Netlify site configuration. |
@@ -5361,6 +5364,7 @@ declare namespace utilities { | |||
getAnnotationNearPoint, | |||
getAnnotationNearPointOnEnabledElement, | |||
jumpToSlice, | |||
pointInSurroundingSphereCallback, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This got removed somehow and I'm just adding it back as OHIF still uses it.
@@ -239,10 +239,17 @@ class RequestPoolManager { | |||
this.numRequests[type]++; | |||
this.awake = true; | |||
|
|||
requestDetails.requestFn()?.finally(() => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a leak on the numRequests pool size.
this.numRequests[type]--; | ||
this.startAgain(); | ||
}); | ||
i--; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is odd, you are decrementing the loop iterator inside the loop. Are you sure about this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, because I don't want to call startAgain, which would run the entire loop again, but I want to add anything the given number of requests to the request pool. The send request really on throws or returns undefined when it is an empty request, so for those the request can simply be skipped and we "redo" that index - except as another request.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this part of this fix or is unrelated and potential bug in future, since I don't think the approach is fine. if it is not part of this fix let's worry about that later
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks a lot!
@sedghi @wayfarer3130 this PR removes the |
@sgielen So how do we test it? |
I think we should look at the design first and see where the root issue stems from. By design, cornerstone3d ImageLoaders receive an ID, start some download, import those bytes into an ImageObject, and return it. The parsing of those download bytes (e.g. DICOM, NIfTI format) is considered to be the ImageLoader responsibility as well, since every format is different. However, Cornerstone still needs generic metadata from the image, so ImageLoaders also register MetadataProviders which can provide the responses to generic questions of many types (width/height, pixel/voxel sizes, LUTs / window settings etc). So, the ImageLoader not only loads pixel data, but also converts metadata into a format recognized by the rest of Cornerstone. So the two (current) purposes of an ImageLoader are to return image pixels, and to keep its metadata in its cache for as long as the image can be handled. Now, my particular issue is that this smartness is included in the Cornerstone DICOM image loader, but in a way that makes it difficult to use in another ImageLoader. The metadata cache is included in the WADOURI implementation runtime as a global variable, and the metadata logic code is tightly coupled with this cache variable. This means either my DICOM datasets need to be in its cache, or I need to copy all of the DICOM -> Cornerstone metadata logic into my own project to make it use my own cache. I can't just call any of the parsing functions without depending on the WADOURI cache as well, so my ImageLoader is either too tightly coupled or does not reuse code properly. So far, I chose to use the WADOURI cache even if I don't use the WADOURI loader, so I don't need to replicate all the code. This is why, in my image loader, I wrap the WADOURI loader, overwriting its I believe that ideally, all of the DICOM functions in the dicomImageLoader would be separate, testable chunks, to be used by WADOURI, perhaps WADORS and other image loaders that eventually work with DICOM bytes. If you agree, perhaps I can file an issue for it and I can propose a refactoring strategy. |
@sgielen Sorry I meant do you have steps or better code that you can share to reproduce |
Oh, yes, certainly! You can find an example e.g. in #888 (https://sjorsgielen.nl/cornerstone3d/webworkermanager-init.zip). The code in the .zip file downloads DICOMs using So, if the For brevity, this is the relevant code in the .zip file: // Since we use a custom Image Loader, use cornerstoneDICOMImageLoader to
// turn this custom method into Cornerstone images
cornerstoneDICOMImageLoader.wadouri.dataSetCacheManager.purge();
cornerstone.registerImageLoader(
'custom',
(imageId: string) => {
const filename = imageId.substring('custom:'.length);
return cornerstoneDICOMImageLoader.wadouri.loadImage(imageId, {
loader: () => {
console.log(filename);
return fetch("/" + filename).then((r) => r.arrayBuffer());
},
preScale: {
enabled: true,
},
});
},
); |
@sedghi any thoughts as to bringing back |
I'm using Because I'm using /**
* Load Image From Dicom File
* @param {string} imageId
* @returns {object}
*/
function loadImageFromDicomFile(imageId) {
// Function Loader
const fnLoader = function (uri, imageId) {
const index = imageId.replace('dicomfile:', '');
// Get file
const file = exStore.manager.get(index); // it's like fileManager
// Promise
const promise = new Promise(function (resolve, reject) {
//
const fileReader = new FileReader();
//
fileReader.onload = function (evt) {
resolve(evt.target.result);
};
//
fileReader.onerror = function (err) {
reject(err);
};
//
fileReader.readAsArrayBuffer(file);
});
// Return
return promise;
};
//
return wadouri.loadImage(imageId, {
loader: fnLoader
});
} |
My opinion in the future, |
I have two PRs open where this topic is being progressed:
Edit: These PRs have been merged. On cornerstone3D 2.0, this issue is resolved as there is now more complete documentation and an example of exactly this use-case. |
Context
The wrong loader was being used in the URI loadImage call, and there was a bug in the request pool manager that failed to start requests when the request returned an empty result instead of a promise (eg short circuit)
Changes & Results
Fix the request pool manager to ensure the counts are decremented even on no response
Fix the loadImage to use the original scheme loader (the options loader was never specified, so it wasn't relevant, and was slightly different in the newer version, so wasn't working)
Testing
Checklist
PR
semantic-release format and guidelines.
Code
[] My code has been well-documented (function documentation, inline comments,
etc.)
[] I have run the
yarn build:update-api
to update the API documentation, and havecommitted the changes to this PR. (Read more here https://www.cornerstonejs.org/docs/contribute/update-api)
Public Documentation Updates
additions or removals.
Tested Environment