Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ Note that this particular transfer handler won’t create an actual `Event`, but

### `Comlink.releaseProxy`

Every proxy created by Comlink has the `[releaseProxy]` method.
Every proxy created by Comlink has the `[releaseProxy]()` method.
Calling it will detach the proxy and the exposed object from the message channel, allowing both ends to be garbage collected.

```js
Expand All @@ -166,10 +166,16 @@ const proxy = Comlink.wrap(port);
proxy[Comlink.releaseProxy]();
```

If the browser supports the [WeakRef proposal], `[releaseProxy]()` will be called automatically when the proxy created by `wrap()` gets garbage collected.

### `Comlink.finalizer`

If an exposed object has a property `[Comlink.finalizer]`, the property will be invoked as a function when the proxy is being released. This can happen either through a manual invocation of `[releaseProxy]()` or automatically during garbage collection if the runtime supports the [WeakRef proposal] (see `Comlink.releaseProxy` above). Note that when the finalizer function is invoked, the endpoint is closed and no more communication can happen.

### `Comlink.createEndpoint`

Every proxy created by Comlink has the `[createEndpoint]` method.
Calling it will return a new `MessagePort`, that has been hooked up to the same object as the proxy that `[createEndpoint]` has been called on.
Every proxy created by Comlink has the `[createEndpoint]()` method.
Calling it will return a new `MessagePort`, that has been hooked up to the same object as the proxy that `[createEndpoint]()` has been called on.

```js
const port = myProxy[Comlink.createEndpoint]();
Expand Down Expand Up @@ -206,6 +212,7 @@ Comlink works with Node’s [`worker_threads`][worker_threads] module. Take a lo
[structured clone table]: structured-clone-table.md
[event]: https://developer.mozilla.org/en-US/docs/Web/API/Event
[worker_threads]: https://nodejs.org/api/worker_threads.html
[weakref proposal]: https://github.com/tc39/proposal-weakrefs

## Additional Resources

Expand Down
59 changes: 52 additions & 7 deletions src/comlink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export { Endpoint };
export const proxyMarker = Symbol("Comlink.proxy");
export const createEndpoint = Symbol("Comlink.endpoint");
export const releaseProxy = Symbol("Comlink.releaseProxy");
export const finalizer = Symbol("Comlink.finalizer");

const throwMarker = Symbol("Comlink.thrown");

Expand Down Expand Up @@ -345,6 +346,9 @@ export function expose(obj: any, ep: Endpoint = self as any) {
// detach and deactive after sending release response above.
ep.removeEventListener("message", callback as any);
closeEndPoint(ep);
if (finalizer in obj && typeof obj[finalizer] === "function") {
obj[finalizer]();
}
}
});
} as any);
Expand All @@ -371,6 +375,50 @@ function throwIfProxyReleased(isReleased: boolean) {
}
}

function releaseEndpoint(ep: Endpoint) {
return requestResponseMessage(ep, {
type: MessageType.RELEASE,
}).then(() => {
closeEndPoint(ep);
});
}

interface FinalizationRegistry<T> {
new (cb: (heldValue: T) => void): FinalizationRegistry<T>;
register(
weakItem: object,
heldValue: T,
unregisterToken?: object | undefined
): void;
unregister(unregisterToken: object): void;
}
declare var FinalizationRegistry: FinalizationRegistry<Endpoint>;

const proxyCounter = new WeakMap<Endpoint, number>();
const proxyFinalizers =
"FinalizationRegistry" in self &&
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR cannot work in the nodejs worker environment, because self is used here, and there is no such object in the nodejs environment, which will cause the following error:

ReferenceError: self is not defined

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

v4.4.0 can no longer work in the nodejs environment.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh dear, I will get right on this. We really need a nodejs test harness.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4.4.1 has now beeen published with a fix and a unit test to prevent this regression in future.

new FinalizationRegistry((ep: Endpoint) => {
const newCount = (proxyCounter.get(ep) || 0) - 1;
proxyCounter.set(ep, newCount);
if (newCount === 0) {
releaseEndpoint(ep);
}
});

function registerProxy(proxy: object, ep: Endpoint) {
const newCount = (proxyCounter.get(ep) || 0) + 1;
proxyCounter.set(ep, newCount);
if (proxyFinalizers) {
proxyFinalizers.register(proxy, ep, proxy);
}
}

function unregisterProxy(proxy: object) {
if (proxyFinalizers) {
proxyFinalizers.unregister(proxy);
}
}

function createProxy<T>(
ep: Endpoint,
path: (string | number | symbol)[] = [],
Expand All @@ -382,13 +430,9 @@ function createProxy<T>(
throwIfProxyReleased(isProxyReleased);
if (prop === releaseProxy) {
return () => {
return requestResponseMessage(ep, {
type: MessageType.RELEASE,
path: path.map((p) => p.toString()),
}).then(() => {
closeEndPoint(ep);
isProxyReleased = true;
});
unregisterProxy(proxy);
releaseEndpoint(ep);
isProxyReleased = true;
};
}
if (prop === "then") {
Expand Down Expand Up @@ -455,6 +499,7 @@ function createProxy<T>(
).then(fromWireValue);
},
});
registerProxy(proxy, ep);
return proxy as any;
}

Expand Down
1 change: 0 additions & 1 deletion src/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,6 @@ export interface EndpointMessage {
export interface ReleaseMessage {
id?: MessageID;
type: MessageType.RELEASE;
path: string[];
}

export type Message =
Expand Down