You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix: keep the Worker object rooted until its thread ends, and report the end as nsworkerended
terminate() reset the Worker object's persistent and dropped the registry
entry the moment it was called, while the thread was still winding down: the
wrapper stopped being reachable from native before it had finished, and
anything the worker had already queued on the parent's loop was discarded on
arrival. The root now survives terminate(); it is released by the worker
thread's own last act, which posts the end back to the parent's event loop.
That post no longer only clears. On the parent's thread it dispatches the
internal `nsworkerended` event on the Worker object and only then releases the
persistent and the registry entry, so the end of a worker is observable from
JS for the first time. The node:worker_threads shim listens for it, which is
what lets 'exit' be emitted exactly once for a worker's own close() as much as
for terminate(), and lets terminate() resolve at that point rather than off a
microtask — after every message and error the worker had already sent. A
parent that is itself tearing down clears its children directly and never
delivers the notification, matching iOS.
Android needed neither half of the iOS change's lifetime rework: the wrapper
is shared_ptr-owned by the registry and by the detached thread itself, its
poWorker_ has been a strong Persistent since construction, and the Worker
object is a plain FunctionTemplate instance ObjectManager never sees — so
there was no finalizer resurrection to take it off, and worker-thread posts
already reached the parent through a weak_ptr to its event loop rather than
through its isolate.
Mirrors NativeScript/ios#456.
Copy file name to clipboardExpand all lines: docs/worker-threads.md
+47-6Lines changed: 47 additions & 6 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -54,7 +54,7 @@ means deliberately unsupported.
54
54
|`threadName`| shim | Always `undefined`. |
55
55
|`workerData`| shim | Always `null` — see below. |
56
56
|`parentPort`| shim |`null` on the main isolate. Inside a worker, a `MessagePort`-shaped `EventTarget` over the worker's existing parent channel: `postMessage` forwards to the global `postMessage`, `message`/`messageerror` are re-dispatched from the worker global scope, `start()` and `close()` are no-ops. It is **not** a real port: not transferable, no queue of its own. |
57
-
|`Worker`| shim | A class over the runtime's global `Worker` with a small Node-style emitter (`on`/`once`/`off`/`removeListener`) for `message`, `messageerror`, `error`, `online` and `exit`. `postMessage(value, transfer)` and `terminate()` forward. `online` is emitted off a microtask after construction, not from the thread. Unsupported options throw a `TypeError` naming the option: `workerData`, `env`, `eval`, `transferList`, and `stdin`/`stdout`/`stderr` when explicitly truthy. The runtime's own `Worker` options (`androidPriority`) ride along untouched — the native constructor ignores keys it does not know. |
57
+
|`Worker`| shim | A class over the runtime's global `Worker` with a small Node-style emitter (`on`/`once`/`off`/`removeListener`) for `message`, `messageerror`, `error`, `online` and `exit`. `postMessage(value, transfer)` and `terminate()` forward. `online` is emitted off a microtask after construction, not from the thread. `exit` (always code `0`) fires exactly once, when the thread has ended, whether the worker was terminated or ended by its own `close()`; `terminate()` resolves at the same point. Unsupported options throw a `TypeError` naming the option: `workerData`, `env`, `eval`, `transferList`, and `stdin`/`stdout`/`stderr` when explicitly truthy. The runtime's own `Worker` options (`androidPriority`) ride along untouched — the native constructor ignores keys it does not know. |
58
58
|`postMessageToThread`| throws |`Error: postMessageToThread is not supported in this runtime`. |
59
59
|`moveMessagePortToContext`| throws |`Error: moveMessagePortToContext is not supported in this runtime`. |
60
60
|`locks`| absent | Web Locks are not implemented; the property does not exist. |
@@ -72,12 +72,16 @@ Values are cloned on the way in and deserialized fresh on each read, so
72
72
mutating the object you passed does not reach a reader, and two readers never
73
73
share one object.
74
74
75
-
### `exit`comes only from `terminate()`
75
+
### `exit`fires when the thread has ended, always with code `0`
76
76
77
-
The runtime has no thread-exit signal — nothing reports that a worker's isolate
78
-
finished. `terminate()` therefore resolves with `0` and emits `exit` with code
79
-
`0` on the way, and that is the only path that emits it. A worker that ends by
80
-
its own `close()` produces no `exit`.
77
+
`exit` is emitted once, from the runtime's end-of-worker notification, so every
78
+
`message` and `error` the worker produced before it ended has been delivered
79
+
first. Node reports the thread's exit code; this runtime has none to report, so
80
+
the code is `0` whichever way the worker ended — `terminate()`, its own
81
+
`close()`, an uncaught error or a missing entry. `terminate()` resolves with
82
+
`0` at the same moment `exit` fires. A parent that is itself tearing down never
83
+
delivers the notification, so a `terminate()` awaited from a dying isolate
84
+
stays pending, as it does in Node when the parent process exits.
81
85
82
86
### A worker error carries no `error` object, and the worker scope's `onerror` is not an event
83
87
@@ -264,3 +268,40 @@ rather than raising a `DataCloneError`, which is long-standing behaviour app
264
268
code relies on. Transfer is not part of that leniency — a port in a worker
265
269
transfer list is validated exactly as it is everywhere else, since degrading a
266
270
transfer would strand the port's sibling.
271
+
272
+
## Worker lifetime
273
+
274
+
**A `Worker` is held strongly by the runtime from the moment it is constructed
275
+
until its thread ends**, the way a browser keeps a running worker's handle
276
+
alive. Dropping every reference to one does not stop it: it keeps running, and
277
+
it keeps dispatching `message` and `error` events at the handlers installed on
278
+
it.
279
+
280
+
```js
281
+
(function () {
282
+
constworker=newWorker("./worker.js");
283
+
worker.onmessage= handle; // still fires; nothing here holds `worker`
284
+
worker.postMessage("go");
285
+
})();
286
+
```
287
+
288
+
Being a GC root also means a `Worker` is a well-behaved key: put one in a
289
+
`WeakMap`, `WeakSet` or `WeakRef` and the entry survives for as long as the
290
+
worker runs.
291
+
292
+
The root is released when the worker ends — `terminate()`, or the worker's own
293
+
`close()`. `terminate()` only starts the wind-down: the object stays rooted
294
+
until the worker thread has actually finished and reported that to the parent.
295
+
From then on the object is collectable like any other, and the runtime drops
296
+
the native side with it. Nothing about a *finished* worker is kept alive.
297
+
298
+
### `nsworkerended`
299
+
300
+
When the worker's thread has finished, the runtime dispatches a plain `Event`
301
+
named `nsworkerended` on the `Worker` object. It is **internal and
302
+
non-standard** — the web has no end-of-worker event, and the name is
303
+
deliberately outside the standard namespace. It exists so that
304
+
`node:worker_threads` can report `'exit'` for a worker that ended by its own
305
+
`close()`; app code should not rely on it. The event is best effort: a worker
306
+
whose parent is already tearing down never delivers it, because the parent's
0 commit comments