Repository: denoland/deno
Pull request: fix(ext/node): fix worker.terminate() return value and exit code

## Summary

Two fixes to `worker.terminate()` behavior to match Node.js:

- **Return `undefined` when worker already exited**: In Node.js, calling `worker.terminate()` after the worker has already exited resolves to `undefined` (the internal handle is null). Previously Deno always resolved to `0`.
- **Use exit code `1` for forced termination**: Node.js uses exit code `1` when a running worker is forcefully terminated via `worker.terminate()`. Previously Deno always used `0`.

Enables 2 new node_compat tests:
- `test-worker-terminate-null-handler.js`
- `test-worker-terminate-microtask-loop.js`

> **Note:** This PR is based on #32161 and should be merged after it.

## Test plan

- [x] `cargo test --test node_compat parallel -- --filter "test-worker-terminate"` — all registered tests pass
- [x] No regressions in `test-worker-dispose.mjs` or other tests calling `terminate()`

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Changed files:
filename: ext/node/polyfills/worker_threads.ts
patch: @@ -601,7 +601,7 @@ class NodeWorker extends EventEmitter {
   // https://nodejs.org/api/worker_threads.html#workerterminate
   terminate() {
     if (this.#status === "TERMINATED") {
-      return PromiseResolve(0);
+      return PromiseResolve(undefined);
     }
 
     this.#status = "TERMINATED";
@@ -610,10 +610,13 @@ class NodeWorker extends EventEmitter {
 
     if (!this.#exited) {
       this.#exited = true;
-      this.emit("exit", 0);
+      this.emit("exit", 1);
+      return PromiseResolve(1);
     }
 
-    return PromiseResolve(0);
+    // Worker already exited - Node.js returns undefined in this case
+    // (the internal handle is already null).
+    return PromiseResolve(undefined);
   }
 
   async [SymbolAsyncDispose]() {

filename: tests/node_compat/config.jsonc
patch: @@ -1588,9 +1588,11 @@
     "parallel/test-worker-ref-onexit.js": {},
     "parallel/test-worker-relative-path-double-dot.js": {},
     "parallel/test-worker-relative-path.js": {},
-    "parallel/test-worker-stdio.js": {},
-    "parallel/test-worker-stdio-flush.js": {},
     "parallel/test-worker-stdio-flush-inflight.js": {},
+    "parallel/test-worker-stdio-flush.js": {},
+    "parallel/test-worker-stdio.js": {},
+    "parallel/test-worker-terminate-microtask-loop.js": {},
+    "parallel/test-worker-terminate-null-handler.js": {},
     "parallel/test-worker-terminate-unrefed.js": {},
     "parallel/test-worker-unref-from-message-during-exit.js": {},
     "parallel/test-worker-thread-name.js": {},

filename: tests/unit_node/worker_threads_test.ts
patch: @@ -550,7 +550,7 @@ Deno.test({
     await deferred.promise;
     const promise = worker.terminate();
     assertEquals(typeof promise.then, "function");
-    assertEquals(await promise, 0);
+    assertEquals(await promise, 1);
   },
 });
 
@@ -925,9 +925,9 @@ Deno.test({
 
     if (typeof termRet.then === "function") {
       const v = await termRet;
-      assertEquals(v, 0);
+      assertEquals(v, 1);
     }
-    assertEquals(code, 0);
+    assertEquals(code, 1);
 
     setTimeout(() => done.resolve(), 50);
     await done.promise;