diff --git a/extensions/cornerstone/src/index.tsx b/extensions/cornerstone/src/index.tsx index bcc6f785a..1db533696 100644 --- a/extensions/cornerstone/src/index.tsx +++ b/extensions/cornerstone/src/index.tsx @@ -132,6 +132,10 @@ const cornerstoneExtension: Types.Extensions.Extension = { }, getPanelModule, onModeExit: ({ servicesManager }: withAppTypes): void => { + unsubscriptions.forEach(unsubscribe => unsubscribe()); + // Clear the unsubscriptions + unsubscriptions.length = 0; + const { cineService, segmentationService } = servicesManager.services; // Empty out the image load and retrieval pools to prevent memory leaks // on the mode exits @@ -150,10 +154,6 @@ const cornerstoneExtension: Types.Extensions.Extension = { useToggleOneUpViewportGridStore.getState().clearToggleOneUpViewportGridStore(); useSegmentationPresentationStore.getState().clearSegmentationPresentationStore(); segmentationService.removeAllSegmentations(); - - unsubscriptions.forEach(unsubscribe => unsubscribe()); - // Clear the unsubscriptions - unsubscriptions.length = 0; }, /** diff --git a/extensions/cornerstone/src/services/SegmentationService/SegmentationService.ts b/extensions/cornerstone/src/services/SegmentationService/SegmentationService.ts index 3430c15ca..b9e805663 100644 --- a/extensions/cornerstone/src/services/SegmentationService/SegmentationService.ts +++ b/extensions/cornerstone/src/services/SegmentationService/SegmentationService.ts @@ -255,7 +255,7 @@ class SegmentationService extends PubSubService { this._onSegmentationAddedFromSource ); - this.listeners = {}; + this.reset(); }; public async addSegmentationRepresentation( diff --git a/extensions/cornerstone/src/utils/segmentationHandlers.ts b/extensions/cornerstone/src/utils/segmentationHandlers.ts index 8e233e9cc..b79f30e7c 100644 --- a/extensions/cornerstone/src/utils/segmentationHandlers.ts +++ b/extensions/cornerstone/src/utils/segmentationHandlers.ts @@ -9,7 +9,11 @@ export function setupSegmentationDataModifiedHandler({ customizationService, commandsManager, }) { - const { unsubscribe } = segmentationService.subscribeDebounced( + // A flag to indicate if the event is unsubscribed to. This is important because + // the debounced callback does an await and in that period of time the event may have + // been unsubscribed. + let isUnsubscribed = false; + const { unsubscribe: debouncedUnsubscribe } = segmentationService.subscribeDebounced( segmentationService.EVENTS.SEGMENTATION_DATA_MODIFIED, async ({ segmentationId }) => { const segmentation = segmentationService.getSegmentation(segmentationId); @@ -42,7 +46,7 @@ export function setupSegmentationDataModifiedHandler({ readableText, }); - if (updatedSegmentation) { + if (!isUnsubscribed && updatedSegmentation) { segmentationService.addOrUpdateSegmentation({ segmentationId, segments: updatedSegmentation.segments, @@ -52,6 +56,10 @@ export function setupSegmentationDataModifiedHandler({ 1000 ); + const unsubscribe = () => { + isUnsubscribed = true; + debouncedUnsubscribe(); + }; return { unsubscribe }; } diff --git a/platform/core/src/services/_shared/pubSubServiceInterface.ts b/platform/core/src/services/_shared/pubSubServiceInterface.ts index 59c41e74f..307d7e049 100644 --- a/platform/core/src/services/_shared/pubSubServiceInterface.ts +++ b/platform/core/src/services/_shared/pubSubServiceInterface.ts @@ -54,7 +54,10 @@ function _unsubscribe(eventName, listenerId) { const listeners = this.listeners[eventName]; if (Array.isArray(listeners)) { - this.listeners[eventName] = listeners.filter(({ id }) => id !== listenerId); + this.listeners[eventName] = listeners.filter(({ id, callback }) => { + callback?.clearDebounceTimeout?.(); + return id !== listenerId; + }); } else { this.listeners[eventName] = undefined; } @@ -134,6 +137,13 @@ export class PubSubService { reset() { this.unsubscriptions.forEach(unsub => unsub()); this.unsubscriptions = []; + + Object.keys(this.listeners).forEach(eventName => + this.listeners[eventName].forEach(({ callback }) => { + callback?.clearDebounceTimeout?.(); + }) + ); + this.listeners = {}; } /** diff --git a/platform/core/src/utils/debounce.js b/platform/core/src/utils/debounce.js index 7a5b2bdf3..5e6d97774 100644 --- a/platform/core/src/utils/debounce.js +++ b/platform/core/src/utils/debounce.js @@ -2,9 +2,11 @@ // be triggered. The function will be called after it stops being called for // N milliseconds. If `immediate` is passed, trigger the function on the // leading edge, instead of the trailing. +// The callback function returned is assigned a clearDebounceTimeout function +// that provides for clearing the timeout/debounced function so that it is not called. function debounce(func, wait, immediate) { var timeout; - return function () { + const callback = function () { var context = this, args = arguments; var later = function () { @@ -20,6 +22,13 @@ function debounce(func, wait, immediate) { func.apply(context, args); } }; + + callback.clearDebounceTimeout = () => { + clearTimeout(timeout); + timeout = null; + }; + + return callback; } export default debounce;