-
Notifications
You must be signed in to change notification settings - Fork 557
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
cleanup event handlers in looker2d #4988
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -60,11 +60,17 @@ const getLabelsWorker = (() => { | |
let workers: Worker[]; | ||
|
||
let next = -1; | ||
return (dispatchEvent) => { | ||
return (dispatchEvent, abortController) => { | ||
if (!workers) { | ||
workers = []; | ||
for (let i = 0; i < numWorkers; i++) { | ||
workers.push(createWorker(LookerUtils.workerCallbacks, dispatchEvent)); | ||
workers.push( | ||
createWorker( | ||
LookerUtils.workerCallbacks, | ||
dispatchEvent, | ||
abortController | ||
) | ||
); | ||
Comment on lines
+63
to
+73
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Potential issue with shared AbortControllers in worker pool The Consider modifying the worker management to ensure that each |
||
} | ||
} | ||
|
||
|
@@ -102,11 +108,14 @@ export abstract class AbstractLooker< | |
private isBatching = false; | ||
private isCommittingBatchUpdates = false; | ||
|
||
private readonly abortController: AbortController; | ||
|
||
constructor( | ||
sample: S, | ||
config: State["config"], | ||
options: Partial<State["options"]> = {} | ||
) { | ||
this.abortController = new AbortController(); | ||
this.eventTarget = new EventTarget(); | ||
this.subscriptions = {}; | ||
this.updater = this.makeUpdate(); | ||
|
@@ -383,9 +392,19 @@ export abstract class AbstractLooker< | |
addEventListener( | ||
eventType: string, | ||
handler: EventListenerOrEventListenerObject | null, | ||
...args: any[] | ||
optionsOrUseCapture?: boolean | AddEventListenerOptions | ||
) { | ||
this.eventTarget.addEventListener(eventType, handler, ...args); | ||
const argsWithSignal: AddEventListenerOptions = | ||
typeof optionsOrUseCapture === "boolean" | ||
? { | ||
signal: this.abortController.signal, | ||
capture: optionsOrUseCapture, | ||
} | ||
: { | ||
...(optionsOrUseCapture ?? {}), | ||
signal: this.abortController.signal, | ||
}; | ||
this.eventTarget.addEventListener(eventType, handler, argsWithSignal); | ||
} | ||
|
||
removeEventListener( | ||
|
@@ -489,6 +508,7 @@ export abstract class AbstractLooker< | |
this.lookerElement.element.parentNode.removeChild( | ||
this.lookerElement.element | ||
); | ||
this.abortController.abort(); | ||
} | ||
|
||
abstract updateOptions(options: Partial<State["options"]>): void; | ||
|
@@ -674,8 +694,9 @@ export abstract class AbstractLooker< | |
|
||
private loadSample(sample: Sample) { | ||
const messageUUID = uuid(); | ||
const labelsWorker = getLabelsWorker((event, detail) => | ||
this.dispatchEvent(event, detail) | ||
const labelsWorker = getLabelsWorker( | ||
(event, detail) => this.dispatchEvent(event, detail), | ||
this.abortController | ||
Comment on lines
+697
to
+699
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Unintended side effects when aborting shared workers When calling To prevent cross-instance interference, consider revising the worker pool implementation. Options include:
Would you like assistance in refactoring the worker management to address this issue? |
||
); | ||
const listener = ({ data: { sample, coloring, uuid } }) => { | ||
if (uuid === messageUUID) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,8 +22,11 @@ export class ImaVidFrameSamples { | |
|
||
private readonly storeBufferManager: BufferManager; | ||
|
||
private readonly abortController: AbortController; | ||
|
||
constructor(storeBufferManager: BufferManager) { | ||
this.storeBufferManager = storeBufferManager; | ||
this.abortController = new AbortController(); | ||
|
||
this.samples = new LRUCache<SampleId, ModalSampleExtendedWithImage>({ | ||
max: MAX_FRAME_SAMPLES_CACHE_SIZE, | ||
|
@@ -65,37 +68,45 @@ export class ImaVidFrameSamples { | |
const source = getSampleSrc(standardizedUrls[mediaField]); | ||
|
||
return new Promise((resolve) => { | ||
image.addEventListener("load", () => { | ||
const sample = this.samples.get(sampleId); | ||
|
||
if (!sample) { | ||
// sample was removed from the cache, this shouldn't happen... | ||
// but if it does, it might be because the cache was cleared | ||
// todo: handle this case better | ||
image.addEventListener( | ||
"load", | ||
() => { | ||
const sample = this.samples.get(sampleId); | ||
|
||
if (!sample) { | ||
// sample was removed from the cache, this shouldn't happen... | ||
// but if it does, it might be because the cache was cleared | ||
// todo: handle this case better | ||
console.error( | ||
"Sample was removed from cache before image loaded", | ||
sampleId | ||
); | ||
image.src = BASE64_BLACK_IMAGE; | ||
return; | ||
} | ||
|
||
sample.image = image; | ||
resolve(sampleId); | ||
}, | ||
{ signal: this.abortController.signal } | ||
); | ||
|
||
image.addEventListener( | ||
"error", | ||
() => { | ||
console.error( | ||
"Sample was removed from cache before image loaded", | ||
sampleId | ||
"Failed to load image for sample with id", | ||
sampleId, | ||
"at url", | ||
source | ||
); | ||
image.src = BASE64_BLACK_IMAGE; | ||
return; | ||
} | ||
|
||
sample.image = image; | ||
resolve(sampleId); | ||
}); | ||
|
||
image.addEventListener("error", () => { | ||
console.error( | ||
"Failed to load image for sample with id", | ||
sampleId, | ||
"at url", | ||
source | ||
); | ||
|
||
// use a placeholder blank black image to not block animation | ||
// setting src should trigger the load event | ||
image.src = BASE64_BLACK_IMAGE; | ||
}); | ||
// use a placeholder blank black image to not block animation | ||
// setting src should trigger the load event | ||
image.src = BASE64_BLACK_IMAGE; | ||
}, | ||
{ signal: this.abortController.signal } | ||
); | ||
Comment on lines
+94
to
+109
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Consider enhancing image loading error recovery The current implementation could be improved by:
Here's a suggested improvement: image.addEventListener(
"error",
() => {
+ const error = {
+ type: 'IMAGE_LOAD_ERROR',
+ sampleId,
+ url: source,
+ timestamp: Date.now()
+ };
console.error(
- "Failed to load image for sample with id",
- sampleId,
- "at url",
- source
+ 'Image load failed:',
+ JSON.stringify(error)
);
+ // todo: add retry logic here
image.src = BASE64_BLACK_IMAGE;
},
{ signal: this.abortController.signal }
);
|
||
|
||
image.src = source; | ||
}); | ||
|
@@ -124,5 +135,6 @@ export class ImaVidFrameSamples { | |
this.reverseFrameIndex.clear(); | ||
this.samples.clear(); | ||
this.storeBufferManager.reset(); | ||
this.abortController.abort(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -446,7 +446,8 @@ export const createWorker = ( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
listeners?: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
[key: string]: ((worker: Worker, args: any) => void)[]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
dispatchEvent?: DispatchEvent | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
dispatchEvent?: DispatchEvent, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
abortController?: AbortController | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+449
to
+450
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add null check for abortController parameter The Add a null check at the beginning of the function: export const createWorker = (
listeners?: {
[key: string]: ((worker: Worker, args: any) => void)[];
},
dispatchEvent?: DispatchEvent,
abortController?: AbortController
): Worker => {
+ if (!abortController) {
+ throw new Error('AbortController is required for proper event listener cleanup');
+ }
let worker: Worker = null;
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
): Worker => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
let worker: Worker = null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -456,17 +457,26 @@ export const createWorker = ( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
worker = new Worker(new URL("./worker/index.ts", import.meta.url)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
worker.onerror = (error) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
dispatchEvent("error", error); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
worker.addEventListener("message", ({ data }) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (data.error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const error = !ERRORS[data.error.cls] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
? new Error(data.error.message) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
: new ERRORS[data.error.cls](data.error.data, data.error.message); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
dispatchEvent("error", new ErrorEvent("error", { error })); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
worker.addEventListener( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"error", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
(error) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
dispatchEvent("error", error); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{ signal: abortController.signal } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
worker.addEventListener( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"message", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
({ data }) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (data.error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const error = !ERRORS[data.error.cls] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
? new Error(data.error.message) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
: new ERRORS[data.error.cls](data.error.data, data.error.message); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
dispatchEvent("error", new ErrorEvent("error", { error })); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{ signal: abortController.signal } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
worker.postMessage({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
method: "init", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -477,13 +487,17 @@ export const createWorker = ( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return worker; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
worker.addEventListener("message", ({ data: { method, ...args } }) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (!(method in listeners)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
worker.addEventListener( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"message", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
({ data: { method, ...args } }) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (!(method in listeners)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
listeners[method].forEach((callback) => callback(worker, args)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
listeners[method].forEach((callback) => callback(worker, args)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{ signal: abortController.signal } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+490
to
+500
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Consider improving type safety for message handling While the message handling implementation is good, we could improve type safety by:
Consider applying this improvement: +interface WorkerMessage {
+ method: string;
+ [key: string]: any;
+}
worker.addEventListener(
"message",
- ({ data: { method, ...args } }) => {
+ ({ data }: MessageEvent<WorkerMessage>) => {
+ const { method, ...args } = data;
+ if (typeof method !== 'string') {
+ console.warn('Received worker message with invalid method type');
+ return;
+ }
if (!(method in listeners)) {
return;
}
listeners[method].forEach((callback) => callback(worker, args));
},
{ signal: abortController.signal }
); 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return worker; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
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.
Add null check before removing event listener.
The event listener removal should include a null check to prevent potential runtime errors.
Apply this diff:
📝 Committable suggestion
Potential memory leak: Add cleanup effect.
The event listener cleanup only occurs when the modal is manually closed. If the component is unmounted without closing (e.g., route change), the event listener will remain attached.
Add a cleanup effect to ensure the event listener is always removed: