Skip to content
19 changes: 19 additions & 0 deletions src/jsc/bindings/BunProcess.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "ErrorCode+List.h"
#include "JavaScriptCore/ArgList.h"
#include "JavaScriptCore/CallData.h"
#include "JavaScriptCore/DeferTermination.h"
#include "JavaScriptCore/TopExceptionScope.h"
#include "JavaScriptCore/JSCJSValue.h"
#include "JavaScriptCore/JSCast.h"
Expand Down Expand Up @@ -206,8 +207,14 @@ static JSValue constructPlatform(VM& vm, JSObject* processObject)
#endif
}

// LazyPropertyCallback builders run inside getOwnPropertySlot, which performs no
// exception check; defer termination (as JSC's own LazyProperty::callFunc does)
// so a worker.terminate() mid-builder can't leave it pending for the caller.
#define DEFER_TERMINATION_FOR_LAZY_PROPERTY(vm__) JSC::DeferTerminationForAWhile deferScopeForLazyProperty(vm__)

static JSValue constructVersions(VM& vm, JSObject* processObject)
{
DEFER_TERMINATION_FOR_LAZY_PROPERTY(vm);
auto scope = DECLARE_TOP_EXCEPTION_SCOPE(vm);
auto* globalObject = processObject->globalObject();
JSC::JSObject* object = JSC::constructEmptyObject(globalObject, globalObject->objectPrototype(), 24);
Expand Down Expand Up @@ -268,6 +275,7 @@ static JSValue constructVersions(VM& vm, JSObject* processObject)
static JSValue constructProcessReleaseObject(VM& vm, JSObject* processObject)
{
auto* globalObject = processObject->globalObject();
DEFER_TERMINATION_FOR_LAZY_PROPERTY(vm);
auto scope = DECLARE_TOP_EXCEPTION_SCOPE(vm);
auto* release = JSC::constructEmptyObject(globalObject);

Expand Down Expand Up @@ -2501,6 +2509,7 @@ static JSValue constructProcessReportObject(VM& vm, JSObject* processObject)
auto* globalObject = processObject->globalObject();
auto process = uncheckedDowncast<Process>(processObject);

DEFER_TERMINATION_FOR_LAZY_PROPERTY(vm);
auto scope = DECLARE_TOP_EXCEPTION_SCOPE(vm);
auto* report = JSC::constructEmptyObject(globalObject, globalObject->objectPrototype(), 10);
report->putDirect(vm, JSC::Identifier::fromString(vm, "compact"_s), JSC::jsBoolean(false), 0);
Expand Down Expand Up @@ -2545,6 +2554,7 @@ static JSValue constructProcessConfigObject(VM& vm, JSObject* processObject)
// }
// Lazy property builder: exceptions must not propagate into
// reifyStaticProperty, which performs no exception check.
DEFER_TERMINATION_FOR_LAZY_PROPERTY(vm);
auto scope = DECLARE_TOP_EXCEPTION_SCOPE(vm);
JSC::JSObject* config = JSC::constructEmptyObject(globalObject, globalObject->objectPrototype(), 2);
JSC::JSObject* variables = JSC::constructEmptyObject(globalObject, globalObject->objectPrototype(), 2);
Expand Down Expand Up @@ -2673,6 +2683,7 @@ extern "C" void Bun__ForceFileSinkToBeSynchronousForProcessObjectStdio(JSC::JSGl
static JSValue constructStdioWriteStream(JSC::JSGlobalObject* globalObject, JSC::JSObject* processObject, int fd)
{
auto& vm = JSC::getVM(globalObject);
DEFER_TERMINATION_FOR_LAZY_PROPERTY(vm);
auto scope = DECLARE_TOP_EXCEPTION_SCOPE(vm);

JSC::JSFunction* getStdioWriteStream = JSC::JSFunction::create(vm, globalObject, processObjectInternalsGetStdioWriteStreamCodeGenerator(vm), globalObject);
Expand Down Expand Up @@ -2736,6 +2747,7 @@ static JSValue constructStderr(VM& vm, JSObject* processObject)
static JSValue constructStdin(VM& vm, JSObject* processObject)
{
auto* globalObject = processObject->globalObject();
DEFER_TERMINATION_FOR_LAZY_PROPERTY(vm);
auto scope = DECLARE_TOP_EXCEPTION_SCOPE(vm);
JSC::JSFunction* getStdinStream = JSC::JSFunction::create(vm, globalObject, processObjectInternalsGetStdinStreamCodeGenerator(vm), globalObject);
JSC::MarkedArgumentBuffer args;
Expand Down Expand Up @@ -2805,6 +2817,7 @@ static JSValue constructProcessChannel(VM& vm, JSObject* processObject)
auto& vm = JSC::getVM(globalObject);
// Lazy property builder: exceptions must not propagate into
// reifyStaticProperty, which performs no exception check.
DEFER_TERMINATION_FOR_LAZY_PROPERTY(vm);
auto scope = DECLARE_TOP_EXCEPTION_SCOPE(vm);

JSC::JSFunction* getControl = JSC::JSFunction::create(vm, globalObject, processObjectInternalsGetChannelCodeGenerator(vm), globalObject);
Expand Down Expand Up @@ -3012,6 +3025,7 @@ static JSValue constructEnv(VM& vm, JSObject* processObject)
auto* globalObject = uncheckedDowncast<Zig::GlobalObject>(processObject->globalObject());
// Lazy property builder: exceptions must not propagate into
// reifyStaticProperty, which performs no exception check.
DEFER_TERMINATION_FOR_LAZY_PROPERTY(vm);
auto scope = DECLARE_TOP_EXCEPTION_SCOPE(vm);
JSValue env = globalObject->processEnvObject();
if (auto* exception = scope.exception()) [[unlikely]] {
Expand Down Expand Up @@ -3881,6 +3895,7 @@ static JSValue Process_stubEmptyArray(VM& vm, JSObject* processObject)
{
// Lazy property builder: exceptions must not propagate into
// reifyStaticProperty, which performs no exception check.
DEFER_TERMINATION_FOR_LAZY_PROPERTY(vm);
auto scope = DECLARE_TOP_EXCEPTION_SCOPE(vm);
JSC::JSArray* array = JSC::constructEmptyArray(processObject->globalObject(), nullptr);
if (auto* exception = scope.exception()) [[unlikely]] {
Expand All @@ -3894,6 +3909,7 @@ static JSValue Process_stubEmptyArray(VM& vm, JSObject* processObject)
static JSValue Process_stubEmptySet(VM& vm, JSObject* processObject)
{
auto* globalObject = processObject->globalObject();
DEFER_TERMINATION_FOR_LAZY_PROPERTY(vm);
auto scope = DECLARE_TOP_EXCEPTION_SCOPE(vm);
JSSet* result = JSSet::create(vm, globalObject->setStructure());
RETURN_IF_EXCEPTION(scope, {});
Expand Down Expand Up @@ -4016,6 +4032,7 @@ static JSValue constructMainModuleProperty(VM& vm, JSObject* processObject)
{
// Lazy property builder: exceptions must not propagate into
// reifyStaticProperty, which performs no exception check.
DEFER_TERMINATION_FOR_LAZY_PROPERTY(vm);
auto scope = DECLARE_TOP_EXCEPTION_SCOPE(vm);
auto* globalObject = defaultGlobalObject(processObject->globalObject());
auto* bun = globalObject->bunObject();
Expand Down Expand Up @@ -4056,6 +4073,7 @@ JSValue Process::constructNextTickFn(JSC::VM& vm, Zig::GlobalObject* globalObjec

// Lazy property builder: exceptions must not propagate into
// reifyStaticProperty, which performs no exception check.
DEFER_TERMINATION_FOR_LAZY_PROPERTY(vm);
auto scope = DECLARE_TOP_EXCEPTION_SCOPE(vm);
JSValue nextTickFunction = JSC::profiledCall(globalObject, ProfilingReason::API, initializer, JSC::getCallData(initializer), globalObject->globalThis(), args);
if (auto* exception = scope.exception()) [[unlikely]] {
Expand Down Expand Up @@ -4102,6 +4120,7 @@ static JSValue constructFeatures(VM& vm, JSObject* processObject)
// cached_builtins: [Getter]
// }
auto* globalObject = processObject->globalObject();
DEFER_TERMINATION_FOR_LAZY_PROPERTY(vm);
auto scope = DECLARE_TOP_EXCEPTION_SCOPE(vm);
auto* object = constructEmptyObject(globalObject);

Expand Down
40 changes: 40 additions & 0 deletions test/js/web/workers/worker-terminate-lifetime.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,43 @@ test(
},
timeout,
);

// terminate() armed while a lazy process.* PropertyCallback builder enters JS
// used to leave the termination pending inside getOwnPropertySlot and trip its
// EXCEPTION_ASSERT (debug only); the worker_threads preload hits this via stdout.
test.skipIf(!slow)(
"terminate() during a lazy process.* builder doesn't trip getOwnPropertySlot assert",
async () => {
await using proc = Bun.spawn({
cmd: [
bunExe(),
"-e",
`const props = ["stdout", "stderr", "stdin", "nextTick", "mainModule"];
Promise.all(props.map(p => new Promise((resolve, reject) => {
const w = new Worker("data:text/javascript," + encodeURIComponent(
'postMessage("go"); Bun.sleepSync(300); process[' + JSON.stringify(p) + '];'
));
Comment thread
coderabbitai[bot] marked this conversation as resolved.
w.addEventListener("message", () => w.terminate());
w.addEventListener("close", resolve, { once: true });
w.addEventListener("error", e => reject(new Error(p + ": " + (e.error?.message || e.message))), { once: true });
}))).then(() => console.log("ok"), e => { console.error(e); process.exit(1); });`,
],
env: bunEnv,
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
// stderr is diagnostic-only (ASAN/debug can emit benign warnings on success).
expect({
stdout: stdout.trim(),
stderr: exitCode === 0 ? "" : stderr,
exitCode,
signalCode: proc.signalCode,
}).toEqual({
stdout: "ok",
stderr: "",
exitCode: 0,
signalCode: null,
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.
},
timeout,
);
Loading