Finalizer not invoked automatically with sharedworkers
It doesn't seem like finalizer gets invoked for sharedworkers. I have the following worker, and the finalizer never gets invoked automatically.
declare const self: SharedWorkerGlobalScope;
import * as Comlink from "comlink";
export const api = {
getToken: (token: string) => {
return token;
},
};
let id = 0;
self.addEventListener("connect", (event: MessageEvent): void => {
id++;
const tabId = id;
console.log("tab connected", tabId);
const port = event.ports[0];
Comlink.expose(
{
...api,
[Comlink.finalizer]: () => {
console.log("tab closed", tabId);
},
},
port,
);
});
It does work when I invoke it manually on beforeunload:
addEventListener("beforeunload", () => {
instance[Comlink.releaseProxy]();
});
I also tried to wrap the port in weakRef myself before passing it to Comlink.expose, but no luck either. Was following this post: https://brightinventions.pl/blog/sharing-websocket-connections-between-browser-tabs-and-windows/
Environment: MacOS 15 Chrome Version 130.0.6723.117 (Official Build) (arm64)
I did manage to hack together a version with navigator.locks that seems to work.
Following this: https://github.com/whatwg/html/issues/1766#issuecomment-633197720
- Acquire an infinite lock after Comlink.wrap with a random id
- invoke "ready" function when the lock is acquired, which then tries to acquire the same lock
- Lock gets released when the tab closes / crashes, we can then run a cleanup function for anything related to that tab
Bonus is that it should also be more performant than weakRef for ongoing tasks.
Oh interesting. That might be a corner-case (i.e. working as intended) of the FinalizationRegistry, where the finalizer does not get called when you close a tab. If you were to just drop the proxy, it should work. In general, FinalizationRegistry makes no guarantee about calling the finalizers.
So if you want to be sure that the finalizer gets called, your approach with beforeunload seems like a good idea.
WeakRefs should work (in theory) when closing the tab according to some blog posts online, but I didn't get around to test yet outside the Comlink setup.
Unfortunately beforeunload is not reliable enough from our testing. Locks works great though.
For us, running a cleanup after tabs is crucial for memory management. The shared worker can be running for months on end with variable number of tabs.
Anyways, we're happy now, but there could be some merit in incorporating a solution into the library. So many people are asking the question, yet there's no straightforward solution.