Compare commits

...

1 Commits

Author SHA1 Message Date
Cursor Agent
4dff49618b Add exception handling for microtasks and nextTick callbacks 2025-06-10 04:57:34 +00:00
2 changed files with 81 additions and 0 deletions

View File

@@ -113,6 +113,12 @@ pub fn drainMicrotasksWithGlobal(this: *EventLoop, globalObject: *JSC.JSGlobalOb
jsc_vm.releaseWeakRefs();
JSC__JSGlobalObject__drainMicrotasks(globalObject);
// Check if an exception was thrown during microtask execution
if (globalObject.hasException()) {
// Report the exception as unhandled - this will clear the exception and report it
globalObject.reportActiveExceptionAsUnhandled(error.JSError);
}
this.virtual_machine.is_inside_deferred_task_queue = true;
this.deferred_tasks.run();
this.virtual_machine.is_inside_deferred_task_queue = false;

View File

@@ -0,0 +1,75 @@
import { it, expect } from "bun:test";
it("queueMicrotask exception handling", async () => {
// Test that exceptions in microtasks are properly reported and don't crash
const errors = [];
const originalOnError = globalThis.onerror;
// Set up error handler to capture unhandled exceptions
globalThis.onerror = (message, source, lineno, colno, error) => {
errors.push({ message, error });
return true; // Prevent default error handling
};
try {
await new Promise(resolve => {
let microtaskRan = false;
// Queue a microtask that throws
queueMicrotask(() => {
throw new Error("Exception from microtask!");
});
// Queue another microtask to verify execution continues
queueMicrotask(() => {
microtaskRan = true;
});
// Wait a bit for microtasks to run
setTimeout(() => {
expect(microtaskRan).toBe(true);
expect(errors.length).toBeGreaterThan(0);
expect(errors[0].error.message).toBe("Exception from microtask!");
resolve();
}, 10);
});
} finally {
// Restore original error handler
globalThis.onerror = originalOnError;
}
});
it("process.nextTick exception handling", async () => {
// Test that exceptions in nextTick callbacks are properly reported
const errors = [];
const originalOnError = globalThis.onerror;
globalThis.onerror = (message, source, lineno, colno, error) => {
errors.push({ message, error });
return true;
};
try {
await new Promise(resolve => {
let nextTickRan = false;
// Use nextTick which also uses the microtask queue
process.nextTick(() => {
throw new Error("Exception from nextTick!");
});
process.nextTick(() => {
nextTickRan = true;
});
setTimeout(() => {
expect(nextTickRan).toBe(true);
expect(errors.length).toBeGreaterThan(0);
expect(errors[0].error.message).toBe("Exception from nextTick!");
resolve();
}, 10);
});
} finally {
globalThis.onerror = originalOnError;
}
});