Skip to content

Commit ae22d4f

Browse files
author
Ben Grant
committed
pass test-worker-nested-uncaught.js
1 parent 93713f2 commit ae22d4f

File tree

8 files changed

+97
-10
lines changed

8 files changed

+97
-10
lines changed

src/bun.js/bindings/webcore/JSWorker.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,13 +138,13 @@ template<> JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSWorkerDOMConstructor::
138138
}
139139
RETURN_IF_EXCEPTION(throwScope, {});
140140
EnsureStillAliveScope argument1 = callFrame->argument(1);
141+
142+
WorkerOptions options {};
141143
JSValue nodeWorkerObject {};
142144
if (callFrame->argumentCount() == 3) {
143145
nodeWorkerObject = callFrame->argument(2);
146+
options.kind = WorkerOptions::Kind::Node;
144147
}
145-
RETURN_IF_EXCEPTION(throwScope, {});
146-
147-
auto options = WorkerOptions {};
148148
JSValue workerData = jsUndefined();
149149
Vector<JSC::Strong<JSC::JSObject>> transferList;
150150

src/bun.js/bindings/webcore/Worker.cpp

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -389,12 +389,10 @@ void Worker::fireEarlyMessages(Zig::GlobalObject* workerGlobalObject)
389389
}
390390
}
391391

392-
void Worker::dispatchError(WTF::String message)
392+
void Worker::dispatchErrorWithMessage(WTF::String message)
393393
{
394-
395394
auto* ctx = scriptExecutionContext();
396-
if (!ctx)
397-
return;
395+
if (!ctx) return;
398396

399397
ScriptExecutionContext::postTaskTo(ctx->identifier(), [protectedThis = Ref { *this }, message = message.isolatedCopy()](ScriptExecutionContext& context) -> void {
400398
ErrorEvent::Init init;
@@ -404,6 +402,27 @@ void Worker::dispatchError(WTF::String message)
404402
protectedThis->dispatchEvent(event);
405403
});
406404
}
405+
406+
bool Worker::dispatchErrorWithValue(Zig::GlobalObject* workerGlobalObject, JSValue value)
407+
{
408+
auto* ctx = scriptExecutionContext();
409+
if (!ctx) return false;
410+
auto serialized = SerializedScriptValue::create(*workerGlobalObject, value, SerializationForStorage::No, SerializationErrorMode::NonThrowing);
411+
if (!serialized) return false;
412+
413+
ScriptExecutionContext::postTaskTo(ctx->identifier(), [protectedThis = Ref { *this }, serialized](ScriptExecutionContext& context) -> void {
414+
auto* globalObject = context.globalObject();
415+
ErrorEvent::Init init;
416+
JSValue deserialized = serialized->deserialize(*globalObject, globalObject, SerializationErrorMode::NonThrowing);
417+
if (!deserialized) return;
418+
init.error = deserialized;
419+
420+
auto event = ErrorEvent::create(eventNames().errorEvent, init, EventIsTrusted::Yes);
421+
protectedThis->dispatchEvent(event);
422+
});
423+
return true;
424+
}
425+
407426
void Worker::dispatchExit(int32_t exitCode)
408427
{
409428
auto* ctx = scriptExecutionContext();
@@ -483,7 +502,16 @@ extern "C" void WebWorker__dispatchError(Zig::GlobalObject* globalObject, Worker
483502
init.bubbles = false;
484503

485504
globalObject->globalEventScope->dispatchEvent(ErrorEvent::create(eventNames().errorEvent, init, EventIsTrusted::Yes));
486-
worker->dispatchError(message.toWTFString(BunString::ZeroCopy));
505+
switch (worker->options().kind) {
506+
case WorkerOptions::Kind::Web:
507+
return worker->dispatchErrorWithMessage(message.toWTFString(BunString::ZeroCopy));
508+
case WorkerOptions::Kind::Node:
509+
if (!worker->dispatchErrorWithValue(globalObject, error)) {
510+
// If serialization threw an error, use the string instead
511+
worker->dispatchErrorWithMessage(message.toWTFString(BunString::ZeroCopy));
512+
}
513+
return;
514+
}
487515
}
488516

489517
extern "C" WebCore::Worker* WebWorker__getParentWorker(void* bunVM);

src/bun.js/bindings/webcore/Worker.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,9 @@ class Worker final : public ThreadSafeRefCounted<Worker>, public EventTargetWith
8484
void dispatchOnline(Zig::GlobalObject* workerGlobalObject);
8585
// Fire a 'message' event in the Worker for messages that were sent before the Worker started running
8686
void fireEarlyMessages(Zig::GlobalObject* workerGlobalObject);
87-
void dispatchError(WTF::String message);
87+
void dispatchErrorWithMessage(WTF::String message);
88+
// true if successful
89+
bool dispatchErrorWithValue(Zig::GlobalObject* workerGlobalObject, JSValue value);
8890
void dispatchExit(int32_t exitCode);
8991
ScriptExecutionContext* scriptExecutionContext() const final { return ContextDestructionObserver::scriptExecutionContext(); }
9092
ScriptExecutionContextIdentifier clientIdentifier() const { return m_clientIdentifier; }

src/bun.js/bindings/webcore/WorkerOptions.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@
88
namespace WebCore {
99

1010
struct WorkerOptions {
11+
enum class Kind : uint8_t {
12+
// Created by the global Worker constructor
13+
Web,
14+
// Created by the `require("node:worker_threads").Worker` constructor
15+
Node,
16+
};
17+
1118
String name;
1219
bool mini { false };
1320
bool unref { false };
@@ -16,6 +23,7 @@ struct WorkerOptions {
1623
// true, then we need to make sure that `process.argv` contains "[worker eval]" instead of the
1724
// Blob URL.
1825
bool evalMode { false };
26+
Kind kind { Kind::Web };
1927
// Serialized array containing [workerData, environmentData]
2028
// (environmentData is always a Map)
2129
RefPtr<SerializedScriptValue> workerDataAndEnvironmentData;

src/js/node/worker_threads.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,9 @@ class Worker extends EventEmitter {
362362
#onError(event: ErrorEvent) {
363363
this.#isRunning = false;
364364
let error = event?.error;
365-
if (!error) {
365+
// if the thrown value serialized successfully, the message will be empty
366+
// if not the message is the actual error
367+
if (event.message !== "") {
366368
error = new Error(event.message, { cause: event });
367369
const stack = event?.stack;
368370
if (stack) {
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
'use strict';
2+
const common = require('../common');
3+
const { Worker } = require('worker_threads');
4+
5+
// Regression test for https://github.com/nodejs/node/issues/34309
6+
7+
const w = new Worker(
8+
`const { Worker } = require('worker_threads');
9+
new Worker("throw new Error('uncaught')", { eval:true })`,
10+
{ eval: true });
11+
w.on('error', common.expectsError({
12+
name: 'Error',
13+
message: 'uncaught'
14+
}));

test/js/node/worker_threads/worker_threads.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { bunEnv, bunExe } from "harness";
2+
import { once } from "node:events";
23
import fs from "node:fs";
34
import { join, relative, resolve } from "node:path";
45
import wt, {
@@ -305,3 +306,25 @@ test("worker event", () => {
305306
expect(called).toBeFalse();
306307
return promise;
307308
});
309+
310+
describe("error event", () => {
311+
test("is fired with a copy of the error value", async () => {
312+
const worker = new Worker("throw new TypeError('oh no')", { eval: true });
313+
const [err] = await once(worker, "error");
314+
expect(err).toBeInstanceOf(TypeError);
315+
expect(err.message).toBe("oh no");
316+
});
317+
318+
test("falls back to string when the error cannot be serialized", async () => {
319+
const worker = new Worker(
320+
/* js */ `
321+
import { MessageChannel } from "node:worker_threads";
322+
const { port1 } = new MessageChannel();
323+
throw port1;`,
324+
{ eval: true },
325+
);
326+
const [err] = await once(worker, "error");
327+
expect(err).toBeInstanceOf(Error);
328+
expect(err.message).toMatch(/MessagePort \{.*\}/s);
329+
});
330+
});

test/js/web/workers/worker.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,16 @@ describe("web worker", () => {
286286
return promise;
287287
});
288288
});
289+
290+
describe("error event", () => {
291+
test("is fired with a string of the error", async () => {
292+
const worker = new Worker("data:text/javascript,throw 5");
293+
const [err] = await once(worker, "error");
294+
expect(err.type).toBe("error");
295+
expect(err.message).toBe("5");
296+
expect(err.error).toBe(null);
297+
});
298+
});
289299
});
290300

291301
// TODO: move to node:worker_threads tests directory

0 commit comments

Comments
 (0)