-
Notifications
You must be signed in to change notification settings - Fork 9
Fix message ports not being closed when proxy is relased #6
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
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 |
|---|---|---|
|
|
@@ -231,6 +231,10 @@ type PendingListenersMap = Map< | |
| string, | ||
| (value: WireValue | PromiseLike<WireValue>) => void | ||
| >; | ||
| type EndpointWithPendingListeners = { | ||
| endpoint: Endpoint; | ||
| pendingListeners: PendingListenersMap; | ||
| }; | ||
|
|
||
| /** | ||
| * Internal transfer handler to handle thrown exceptions. | ||
|
|
@@ -396,7 +400,7 @@ function closeEndPoint(endpoint: Endpoint) { | |
| } | ||
|
|
||
| export function wrap<T>(ep: Endpoint, target?: any): Remote<T> { | ||
| const pendingListeners : PendingListenersMap = new Map(); | ||
| const pendingListeners: PendingListenersMap = new Map(); | ||
|
|
||
| ep.addEventListener("message", function handleMessage(ev: Event) { | ||
| const { data } = ev as MessageEvent; | ||
|
|
@@ -415,7 +419,7 @@ export function wrap<T>(ep: Endpoint, target?: any): Remote<T> { | |
| } | ||
| }); | ||
|
|
||
| return createProxy<T>(ep, pendingListeners, [], target) as any; | ||
| return createProxy<T>({ endpoint: ep, pendingListeners }, [], target) as any; | ||
| } | ||
|
|
||
| function throwIfProxyReleased(isReleased: boolean) { | ||
|
|
@@ -424,11 +428,11 @@ function throwIfProxyReleased(isReleased: boolean) { | |
| } | ||
| } | ||
|
|
||
| function releaseEndpoint(ep: Endpoint) { | ||
| return requestResponseMessage(ep, new Map(), { | ||
| function releaseEndpoint(epWithPendingListeners: EndpointWithPendingListeners) { | ||
| return requestResponseMessage(epWithPendingListeners, { | ||
| type: MessageType.RELEASE, | ||
| }).then(() => { | ||
| closeEndPoint(ep); | ||
| closeEndPoint(epWithPendingListeners.endpoint); | ||
| }); | ||
| } | ||
|
|
||
|
|
@@ -441,24 +445,32 @@ interface FinalizationRegistry<T> { | |
| ): void; | ||
| unregister(unregisterToken: object): void; | ||
| } | ||
| declare var FinalizationRegistry: FinalizationRegistry<Endpoint>; | ||
|
|
||
| const proxyCounter = new WeakMap<Endpoint, number>(); | ||
| declare var FinalizationRegistry: FinalizationRegistry<EndpointWithPendingListeners>; | ||
|
|
||
| const proxyCounter = new WeakMap<EndpointWithPendingListeners, number>(); | ||
| const proxyFinalizers = | ||
| "FinalizationRegistry" in globalThis && | ||
| new FinalizationRegistry((ep: Endpoint) => { | ||
| const newCount = (proxyCounter.get(ep) || 0) - 1; | ||
| proxyCounter.set(ep, newCount); | ||
| if (newCount === 0) { | ||
| releaseEndpoint(ep); | ||
| new FinalizationRegistry( | ||
| (epWithPendingListeners: EndpointWithPendingListeners) => { | ||
| const newCount = (proxyCounter.get(epWithPendingListeners) || 0) - 1; | ||
| proxyCounter.set(epWithPendingListeners, newCount); | ||
| if (newCount === 0) { | ||
| releaseEndpoint(epWithPendingListeners).finally(() => { | ||
| epWithPendingListeners.pendingListeners.clear(); | ||
| }); | ||
| } | ||
| } | ||
| }); | ||
| ); | ||
|
|
||
| function registerProxy(proxy: object, ep: Endpoint) { | ||
| const newCount = (proxyCounter.get(ep) || 0) + 1; | ||
| proxyCounter.set(ep, newCount); | ||
| function registerProxy( | ||
| proxy: object, | ||
| epWithPendingListeners: EndpointWithPendingListeners | ||
| ) { | ||
| const newCount = (proxyCounter.get(epWithPendingListeners) || 0) + 1; | ||
| proxyCounter.set(epWithPendingListeners, newCount); | ||
| if (proxyFinalizers) { | ||
| proxyFinalizers.register(proxy, ep, proxy); | ||
| proxyFinalizers.register(proxy, epWithPendingListeners, proxy); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -469,13 +481,12 @@ function unregisterProxy(proxy: object) { | |
| } | ||
|
|
||
| function createProxy<T>( | ||
| ep: Endpoint, | ||
| pendingListeners: PendingListenersMap, | ||
| epWithPendingListeners: EndpointWithPendingListeners, | ||
| path: (string | number | symbol)[] = [], | ||
| target: object = function () {} | ||
| ): Remote<T> { | ||
| let isProxyReleased = false; | ||
| const propProxyCache : Map<(string | symbol), Remote<unknown>> = new Map(); | ||
| const propProxyCache: Map<string | symbol, Remote<unknown>> = new Map(); | ||
| const proxy = new Proxy(target, { | ||
| get(_target, prop) { | ||
| throwIfProxyReleased(isProxyReleased); | ||
|
|
@@ -486,16 +497,17 @@ function createProxy<T>( | |
| } | ||
| propProxyCache.clear(); | ||
| unregisterProxy(proxy); | ||
| releaseEndpoint(ep); | ||
| pendingListeners.clear(); | ||
|
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. Is the issue that clearing the listeners here is premature because the Or is it important to pass
Author
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.
Yes, that's the important bit. If 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. So why do we allow pendingListeners to be undefined? In what situation is that ok?
Author
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. This was necessary when releasing the endpoint when the proxy is garbage collected. However, thinking about this more, we should also have access to the
Author
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. I have added a commit that changes the |
||
| releaseEndpoint(epWithPendingListeners).finally(() => { | ||
| epWithPendingListeners.pendingListeners.clear(); | ||
| }); | ||
| isProxyReleased = true; | ||
| }; | ||
| } | ||
| if (prop === "then") { | ||
| if (path.length === 0) { | ||
| return { then: () => proxy }; | ||
| } | ||
| const r = requestResponseMessage(ep, pendingListeners, { | ||
| const r = requestResponseMessage(epWithPendingListeners, { | ||
| type: MessageType.GET, | ||
| path: path.map((p) => p.toString()), | ||
| }).then(fromWireValue); | ||
|
|
@@ -507,7 +519,7 @@ function createProxy<T>( | |
| return cachedProxy; | ||
| } | ||
|
|
||
| const propProxy = createProxy(ep, pendingListeners, [...path, prop]); | ||
| const propProxy = createProxy(epWithPendingListeners, [...path, prop]); | ||
| propProxyCache.set(prop, propProxy); | ||
| return propProxy; | ||
| }, | ||
|
|
@@ -517,8 +529,7 @@ function createProxy<T>( | |
| // boolean. To show good will, we return true asynchronously ¯\_(ツ)_/¯ | ||
| const [value, transferables] = toWireValue(rawValue); | ||
| return requestResponseMessage( | ||
| ep, | ||
| pendingListeners, | ||
| epWithPendingListeners, | ||
| { | ||
| type: MessageType.SET, | ||
| path: [...path, prop].map((p) => p.toString()), | ||
|
|
@@ -531,18 +542,17 @@ function createProxy<T>( | |
| throwIfProxyReleased(isProxyReleased); | ||
| const last = path[path.length - 1]; | ||
| if ((last as any) === createEndpoint) { | ||
| return requestResponseMessage(ep, pendingListeners, { | ||
| return requestResponseMessage(epWithPendingListeners, { | ||
| type: MessageType.ENDPOINT, | ||
| }).then(fromWireValue); | ||
| } | ||
| // We just pretend that `bind()` didn’t happen. | ||
| if (last === "bind") { | ||
| return createProxy(ep, pendingListeners, path.slice(0, -1)); | ||
| return createProxy(epWithPendingListeners, path.slice(0, -1)); | ||
| } | ||
| const [argumentList, transferables] = processArguments(rawArgumentList); | ||
| return requestResponseMessage( | ||
| ep, | ||
| pendingListeners, | ||
| epWithPendingListeners, | ||
| { | ||
| type: MessageType.APPLY, | ||
| path: path.map((p) => p.toString()), | ||
|
|
@@ -555,8 +565,7 @@ function createProxy<T>( | |
| throwIfProxyReleased(isProxyReleased); | ||
| const [argumentList, transferables] = processArguments(rawArgumentList); | ||
| return requestResponseMessage( | ||
| ep, | ||
| pendingListeners, | ||
| epWithPendingListeners, | ||
| { | ||
| type: MessageType.CONSTRUCT, | ||
| path: path.map((p) => p.toString()), | ||
|
|
@@ -566,7 +575,7 @@ function createProxy<T>( | |
| ).then(fromWireValue); | ||
| }, | ||
| }); | ||
| registerProxy(proxy, ep); | ||
| registerProxy(proxy, epWithPendingListeners); | ||
| return proxy as any; | ||
| } | ||
|
|
||
|
|
@@ -635,18 +644,18 @@ function fromWireValue(value: WireValue): any { | |
| } | ||
|
|
||
| function requestResponseMessage( | ||
| ep: Endpoint, | ||
| pendingListeners: PendingListenersMap, | ||
| epWithPendingListeners: EndpointWithPendingListeners, | ||
| msg: Message, | ||
| transfers?: Transferable[] | ||
| ): Promise<WireValue> { | ||
| const ep = epWithPendingListeners.endpoint; | ||
| const pendingListeners = epWithPendingListeners.pendingListeners; | ||
| return new Promise((resolve) => { | ||
| const id = Math.trunc(Math.random() * Number.MAX_SAFE_INTEGER).toString(); | ||
| pendingListeners.set(id, resolve); | ||
| if (ep.start) { | ||
| ep.start(); | ||
| } | ||
| ep.postMessage({ id, ...msg }, transfers); | ||
| }); | ||
| }); | ||
| } | ||
|
|
||
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 problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm leaving this as is as the logical OR operator works with much older browser versions. The nullish coalescing operator was introduced much 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.
fwiw - that is handled during the transpilation step of ts -> js. When you specify a specific ES version as the target it will produce the code for that ES version so you can write your ts using modern styles.