aboutsummaryrefslogtreecommitdiff
path: root/src/bun.js
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2023-08-16 19:40:20 -0700
committerGravatar GitHub <noreply@github.com> 2023-08-16 19:40:20 -0700
commit0486cea35a80be97ba43f41a29ce55f0d3a8eb01 (patch)
tree2a6335ff465826b3e20959c5751c89ba793becca /src/bun.js
parent2634c64aa32fec00073bd0a776e5ac67ad6aa6e5 (diff)
downloadbun-0486cea35a80be97ba43f41a29ce55f0d3a8eb01.tar.gz
bun-0486cea35a80be97ba43f41a29ce55f0d3a8eb01.tar.zst
bun-0486cea35a80be97ba43f41a29ce55f0d3a8eb01.zip
`bun --inspect` (#4158)
* Let the debugger to pause/resume the event loop * Add initial support for Debug Adapter Protocol * Add progress * Update Worker.cpp * Fix require("console") #3820 (#4073) * Fix #3820 * Add Module (#4074) * Set exports to {} in user-constructed CommonJSModuleRecords (#4076) * feat(bun/test): Implement "toSatisfy" & "toIncludeRepeated" (fwup) (#3651) * Fix merge issues * oop * make codegen * Fix build issues --------- Co-authored-by: dave caruso <me@paperdave.net> * worker tests (#4058) Co-authored-by: Jarred Sumner <jarred@jarredsumner.com> * feat(bun:test) add support for test.each() and describe.each() (#4047) * rename callback to func * update testscope to handle function arguments * works * big cleanup * works in debug, not release * fix memory issue & update tests * catch & str test * write types for each() & switch tests to ts * rm & typo * move some code around & support describe * review changes * Fix one of the astro segfaults, also fix bun init version (#4079) * 4->16 * add assertions * fix version stuff * Remove unintentional logs from #4043 * Run prettier * Update main-worker-file.js * Update SIMDUTF (#4078) * Fix constructing buffer from a UTF16 string with the Latin1 encoding. (#4086) Close: #3914 * Add support for `bun --revision` (#4027) Co-authored-by: Yash Sharma <yashsharma@Yashs-MacBook-Air.local> * Updates * Update __global.zig * remove non-node node-fallbacks (#4082) * remove non-node node-fallbacks. * organize the imports * Fix test * Sync bun-polyfills branch (#4081) * bun-polyfills: initial impl. & baseline refactor * move @types/ws dep from root to /test/ * bun-types: remove ReadableStream.forEach method (this does not exist, probably added by mistake) * bun-polyfills: remove extraneous stream utils * bun-polyfills: add types syncing file * bun-polyfills: re-arrange global polyfills * bun-polyfills: fix FileBlob streams types again * bun-polyfills: sync all of @types/node * bun-polyfills: typeguard all current polyfills * bun-polyfills: fix import paths * bun-polyfills: switch to wasm impl. of farmhash * bun-polyfills: support default import of bun obj * bun-polyfills: transpiler placeholder file * bun-polyfills: loaderless import.meta polyfill * bun-polyfills: refactor import.meta polyfill * bun-polyfills: repl entrypoint & todo list index * bun-types: Add null to return type of Bun.which * bun-types: match Bun.sha with Bun.hash.SHA512_256 * bun-polyfills: new "repl" package.json script * bun-polyfills: full refactor of toplevel hashes * bun-polyfills: these are fixed * bun-types: NODE_ENV is optional * bun-polyfills: fix Bun.env types * bun-types+polyfills: fix HeapSnapshot.version type * bun-polyfills: fix some web streams type conflicts * bun-polyfills: update internal FileBlob.slice * bun-polyfills: fix subproc stdin conversions * bun-polyfills: better internal fileblob types * bun-polyfills: try to sync global performance type * bun-polyfills: working zig wasm polyfills setup * bun-polyfills: update scripts * bun-polyfills: fix wasm file location resolution * bun-polyfills: goodbye farmhash (replaced by zig) * bun-polyfills: move all Bun.hash polyfills to zig * bun-polyfills: reimpl. seeding of seeded hashes * bun-polyfills: impl. undocumented murmur32v2 * bun-polyfills: switch zighash from jsdoc to .d.ts * bun-types: partial fix of Hash types * bun-polyfills: documented Hash.murmur32v2 * bun-polyfills: misc updates * bun-polyfills: enable sourcemaps * bun-polyfills: handle empty inputs to hash funcs * bun-types: narrow down hash func types * bun-polyfills: remove unnecessary bigint casts * bun-polyfills: impl. Bun.isMainThread * bun-polyfills: impl. Bun.sleep and fix sleepSync * bun-polyfills: impl. indexOfLine * bun-polyfills: impl. Bun.peek.status * bun-types: fix hashing test --------- Co-authored-by: Jarred Sumner <jarred@jarredsumner.com> * Add remix guide * Fix title * add util.formatWithOptions (#4090) * Add formatWithOptions * tests and tweaks * adjust --------- Co-authored-by: Jarred Sumner <jarred@jarredsumner.com> * bun test: format description of test.each (#4092) * bun test: format description * add tests for tests * only --------- Co-authored-by: Jarred Sumner <jarred@jarredsumner.com> * Fixes #4062 (#4106) * Fixes #4062 * Update encoding.zig * Use faster C++ impl * Update wtf-bindings.cpp * undo * Fixup --------- Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> * zig fmt * Update remix guide * fs.zig: create temp files with 0o700, not 0o007 (#4107) * Handle thundering herd of setInterval (#4109) Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> * Fix memory leak in base64url (#4111) Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> * run files without extensions (#4113) * run script without extension * process stdio write fix * don't check for trailing slash, var stream * More lazily initialize these static strings * Remove assertion * wip * Add --inspect flag * Deprecate loading `node_modules.bun` * realpath * regenerate schema * More * more * Update cli.zig * Debugger JS loads * have fun * Most of the code * Its starting to work. * more progress * win some, lose some * Update dap.ts * Async Context Tracking * untested websocket * Emit Console messages * Error handling * errors * Make profiling work better * [clap] CLI arguments with optional values ignore positional params In `bun --inspect foo.js`, `foo.js` should not be the value of `--inspect`. It is a positional parameter. `--inspect=foo` * Support multiple simultaneous clients, automatically unpause on disconnect * regenerate * Update Makefile * Update WebKit * Update cli.zig * Update InternalModuleRegistry+createInternalModuleById.h * BaseURL * Update WebKit * Add web-inspector-bun * Update build.ts * formatting, mostly * Update debugger.ts * Update InternalModuleRegistryConstants.h * wrap * Update test * Update test --------- Co-authored-by: Ashcon Partovi <ashcon@partovi.net> Co-authored-by: Dylan Conway <dylan.conway567@gmail.com> Co-authored-by: dave caruso <me@paperdave.net> Co-authored-by: Tiramify (A.K. Daniel) <94789999+TiranexDev@users.noreply.github.com> Co-authored-by: Jacques <25390037+jecquas@users.noreply.github.com> Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Co-authored-by: Ai Hoshino <ambiguous404@gmail.com> Co-authored-by: Yash Sharma <yashosharma@gmail.com> Co-authored-by: Yash Sharma <yashsharma@Yashs-MacBook-Air.local> Co-authored-by: Colin McDonnell <colinmcd94@gmail.com> Co-authored-by: jhmaster <32803471+jhmaster2000@users.noreply.github.com> Co-authored-by: Adhityaa Chandrasekar <github@adtac.in> Co-authored-by: Dylan Conway <35280289+dylan-conway@users.noreply.github.com>
Diffstat (limited to 'src/bun.js')
m---------src/bun.js/WebKit0
-rw-r--r--src/bun.js/api/bun.zig39
-rw-r--r--src/bun.js/api/server.zig8
-rw-r--r--src/bun.js/base.zig36
-rw-r--r--src/bun.js/bindings/AsyncContextFrame.cpp22
-rw-r--r--src/bun.js/bindings/AsyncContextFrame.h4
-rw-r--r--src/bun.js/bindings/BunDebugger.cpp530
-rw-r--r--src/bun.js/bindings/BunInspector.cpp19
-rw-r--r--src/bun.js/bindings/Debugger.zig33
-rw-r--r--src/bun.js/bindings/InternalModuleRegistry.cpp2
-rw-r--r--src/bun.js/bindings/JSBundlerPlugin.cpp2
-rw-r--r--src/bun.js/bindings/JSReadableHelper.cpp2
-rw-r--r--src/bun.js/bindings/ModuleLoader.cpp2
-rw-r--r--src/bun.js/bindings/ScriptExecutionContext.cpp7
-rw-r--r--src/bun.js/bindings/ScriptExecutionContext.h2
-rw-r--r--src/bun.js/bindings/Strong.cpp21
-rw-r--r--src/bun.js/bindings/Strong.h27
-rw-r--r--src/bun.js/bindings/ZigConsoleClient.cpp11
-rw-r--r--src/bun.js/bindings/ZigGlobalObject.cpp2
-rw-r--r--src/bun.js/bindings/ZigGlobalObject.h1
-rw-r--r--src/bun.js/bindings/bindings.cpp7
-rw-r--r--src/bun.js/bindings/debug-helpers.h18
-rw-r--r--src/bun.js/bindings/napi.cpp2
-rw-r--r--src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h2
-rw-r--r--src/bun.js/bindings/webcore/DOMIsoSubspaces.h1
-rw-r--r--src/bun.js/bindings/webcore/EventEmitter.cpp2
-rw-r--r--src/bun.js/event_loop.zig36
-rw-r--r--src/bun.js/javascript.zig141
-rw-r--r--src/bun.js/node/node_fs.zig33
-rw-r--r--src/bun.js/scripts/generate-jssink.js2
-rw-r--r--src/bun.js/webcore/response.zig10
31 files changed, 961 insertions, 63 deletions
diff --git a/src/bun.js/WebKit b/src/bun.js/WebKit
-Subproject 74609640b2a7c5a1588b824f870d1b0ff91bfd8
+Subproject e84b7bbb14480f117099df22c299e6341c62f42
diff --git a/src/bun.js/api/bun.zig b/src/bun.js/api/bun.zig
index 6daff5fcc..d230028ba 100644
--- a/src/bun.js/api/bun.zig
+++ b/src/bun.js/api/bun.zig
@@ -3498,6 +3498,8 @@ pub const TOML = struct {
}
};
+const Debugger = JSC.Debugger;
+
pub const Timer = struct {
last_id: i32 = 1,
warned: bool = false,
@@ -3612,6 +3614,9 @@ pub const Timer = struct {
};
if (should_cancel_job) {
+ if (vm.isInspectorEnabled()) {
+ Debugger.didCancelAsyncCall(globalThis, .DOMTimer, Timeout.ID.asyncID(.{ .id = this.id, .kind = kind }));
+ }
this.deinit();
return;
} else if (kind != .setInterval) {
@@ -3624,6 +3629,7 @@ pub const Timer = struct {
defer if (args_needs_deinit) bun.default_allocator.free(args);
const callback = this.callback.get() orelse @panic("Expected CallbackJob to have a callback function");
+
if (this.arguments.trySwap()) |arguments| {
// Bun.sleep passes a Promise
if (arguments.jsType() == .JSPromise) {
@@ -3648,11 +3654,19 @@ pub const Timer = struct {
}
}
+ if (vm.isInspectorEnabled()) {
+ Debugger.willDispatchAsyncCall(globalThis, .DOMTimer, Timeout.ID.asyncID(.{ .id = this.id, .kind = kind }));
+ }
+
const result = callback.callWithGlobalThis(
globalThis,
args,
);
+ if (vm.isInspectorEnabled()) {
+ Debugger.didDispatchAsyncCall(globalThis, .DOMTimer, Timeout.ID.asyncID(.{ .id = this.id, .kind = kind }));
+ }
+
if (result.isEmptyOrUndefinedOrNull() or !result.isCell()) {
this.deinit();
return;
@@ -3787,6 +3801,9 @@ pub const Timer = struct {
}
vm.enqueueTask(JSC.Task.init(&job.task));
+ if (vm.isInspectorEnabled()) {
+ Debugger.didScheduleAsyncCall(globalThis, .DOMTimer, id.asyncID(), true);
+ }
map.put(vm.allocator, this.id, null) catch unreachable;
return this_value;
@@ -3888,6 +3905,10 @@ pub const Timer = struct {
kind: Kind = Kind.setTimeout,
+ pub inline fn asyncID(this: ID) u64 {
+ return @bitCast(this);
+ }
+
pub fn repeats(this: ID) bool {
return this.kind == .setInterval;
}
@@ -3976,6 +3997,9 @@ pub const Timer = struct {
job.ref.ref(vm);
vm.enqueueTask(JSC.Task.init(&job.task));
+ if (vm.isInspectorEnabled()) {
+ Debugger.didScheduleAsyncCall(globalThis, .DOMTimer, timer_id.asyncID(), !repeats);
+ }
}
pub fn deinit(this: *Timeout) void {
@@ -4033,6 +4057,9 @@ pub const Timer = struct {
job.ref.ref(vm);
vm.enqueueTask(JSC.Task.init(&job.task));
+ if (vm.isInspectorEnabled()) {
+ Debugger.didScheduleAsyncCall(globalThis, .DOMTimer, Timeout.ID.asyncID(.{ .id = id, .kind = kind }), !repeat);
+ }
map.put(vm.allocator, id, null) catch unreachable;
return;
}
@@ -4056,6 +4083,10 @@ pub const Timer = struct {
timeout.poll_ref.ref(vm);
map.put(vm.allocator, id, timeout) catch unreachable;
+ if (vm.isInspectorEnabled()) {
+ Debugger.didScheduleAsyncCall(globalThis, .DOMTimer, Timeout.ID.asyncID(.{ .id = id, .kind = kind }), !repeat);
+ }
+
timeout.timer.set(
Timeout.ID{
.id = id,
@@ -4117,8 +4148,8 @@ pub const Timer = struct {
JSC.markBinding(@src());
const kind: Timeout.Kind = if (repeats) .setInterval else .setTimeout;
-
- var map = globalThis.bunVM().timer.maps.get(kind);
+ var vm = globalThis.bunVM();
+ var map = vm.timer.maps.get(kind);
const id: Timeout.ID = .{
.id = brk: {
@@ -4137,6 +4168,10 @@ pub const Timer = struct {
};
var timer = map.fetchSwapRemove(id.id) orelse return;
+ if (vm.isInspectorEnabled()) {
+ Debugger.didCancelAsyncCall(globalThis, .DOMTimer, id.asyncID());
+ }
+
if (timer.value == null) {
// this timer was scheduled to run but was cancelled before it was run
// so long as the callback isn't already in progress, fetchSwapRemove will handle invalidating it
diff --git a/src/bun.js/api/server.zig b/src/bun.js/api/server.zig
index 4eca89cc6..df3bdba1f 100644
--- a/src/bun.js/api/server.zig
+++ b/src/bun.js/api/server.zig
@@ -137,6 +137,7 @@ pub const ServerConfig = struct {
websocket: ?WebSocketServer = null,
inspector: bool = false,
+ reuse_port: bool = false,
pub const SSLConfig = struct {
server_name: [*c]const u8 = null,
@@ -744,6 +745,11 @@ pub const ServerConfig = struct {
if (arg.get(global, "development")) |dev| {
args.development = dev.coerce(bool, global);
+ args.reuse_port = !args.development;
+ }
+
+ if (arg.get(global, "reusePort")) |dev| {
+ args.reuse_port = dev.coerce(bool, global);
}
if (arg.get(global, "inspector")) |inspector| {
@@ -5500,7 +5506,7 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type {
this.app.listenWithConfig(*ThisServer, this, onListen, .{
.port = this.config.port,
.host = host,
- .options = 0,
+ .options = if (this.config.reuse_port) 0 else 1,
});
}
};
diff --git a/src/bun.js/base.zig b/src/bun.js/base.zig
index 27f40eeab..d8a758dc2 100644
--- a/src/bun.js/base.zig
+++ b/src/bun.js/base.zig
@@ -4088,3 +4088,39 @@ pub const BinaryType = enum {
}
}
};
+
+pub const AsyncTaskTracker = struct {
+ id: u64,
+
+ pub fn init(vm: *JSC.VirtualMachine) AsyncTaskTracker {
+ return .{ .id = vm.nextAsyncTaskID() };
+ }
+
+ pub fn didSchedule(this: AsyncTaskTracker, globalObject: *JSC.JSGlobalObject) void {
+ if (this.id == 0) return;
+
+ bun.JSC.Debugger.didScheduleAsyncCall(globalObject, bun.JSC.Debugger.AsyncCallType.EventListener, this.id, true);
+ }
+
+ pub fn didCancel(this: AsyncTaskTracker, globalObject: *JSC.JSGlobalObject) void {
+ if (this.id == 0) return;
+
+ bun.JSC.Debugger.didCancelAsyncCall(globalObject, bun.JSC.Debugger.AsyncCallType.EventListener, this.id);
+ }
+
+ pub fn willDispatch(this: AsyncTaskTracker, globalObject: *JSC.JSGlobalObject) void {
+ if (this.id == 0) {
+ return;
+ }
+
+ bun.JSC.Debugger.willDispatchAsyncCall(globalObject, bun.JSC.Debugger.AsyncCallType.EventListener, this.id);
+ }
+
+ pub fn didDispatch(this: AsyncTaskTracker, globalObject: *JSC.JSGlobalObject) void {
+ if (this.id == 0) {
+ return;
+ }
+
+ bun.JSC.Debugger.didDispatchAsyncCall(globalObject, bun.JSC.Debugger.AsyncCallType.EventListener, this.id);
+ }
+};
diff --git a/src/bun.js/bindings/AsyncContextFrame.cpp b/src/bun.js/bindings/AsyncContextFrame.cpp
index 326350664..2a103a8d1 100644
--- a/src/bun.js/bindings/AsyncContextFrame.cpp
+++ b/src/bun.js/bindings/AsyncContextFrame.cpp
@@ -81,27 +81,27 @@ extern "C" EncodedJSValue AsyncContextFrame__withAsyncContextIfNeeded(JSGlobalOb
restoreAsyncContext = asyncContextData->getInternalField(0); \
asyncContextData->putInternalField(vm, 0, wrapper->context.get()); \
} \
- auto result = JSC::call(__VA_ARGS__); \
+ auto result = JSC::profiledCall(__VA_ARGS__); \
if (asyncContextData) { \
asyncContextData->putInternalField(vm, 0, restoreAsyncContext); \
} \
return result;
-JSValue AsyncContextFrame::call(JSGlobalObject* global, JSValue functionObject, const ArgList& args, ASCIILiteral errorMessage)
-{
- ASYNCCONTEXTFRAME_CALL_IMPL(global, functionObject, args, errorMessage);
-}
-JSValue AsyncContextFrame::call(JSGlobalObject* global, JSValue functionObject, JSValue thisValue, const ArgList& args, ASCIILiteral errorMessage)
-{
- ASYNCCONTEXTFRAME_CALL_IMPL(global, functionObject, thisValue, args, errorMessage);
-}
+// JSValue AsyncContextFrame::call(JSGlobalObject* global, JSValue functionObject, const ArgList& args, ASCIILiteral errorMessage)
+// {
+// ASYNCCONTEXTFRAME_CALL_IMPL(global, ProfilingReason::API, functionObject, args, errorMessage);
+// }
+// JSValue AsyncContextFrame::call(JSGlobalObject* global, JSValue functionObject, JSValue thisValue, const ArgList& args, ASCIILiteral errorMessage)
+// {
+// ASYNCCONTEXTFRAME_CALL_IMPL(global, ProfilingReason::API, functionObject, thisValue, args, errorMessage);
+// }
JSValue AsyncContextFrame::call(JSGlobalObject* global, JSValue functionObject, JSValue thisValue, const ArgList& args)
{
- ASYNCCONTEXTFRAME_CALL_IMPL(global, functionObject, JSC::getCallData(functionObject), thisValue, args);
+ ASYNCCONTEXTFRAME_CALL_IMPL(global, ProfilingReason::API, functionObject, JSC::getCallData(functionObject), thisValue, args);
}
JSValue AsyncContextFrame::call(JSGlobalObject* global, JSValue functionObject, JSValue thisValue, const ArgList& args, NakedPtr<Exception>& returnedException)
{
- ASYNCCONTEXTFRAME_CALL_IMPL(global, functionObject, JSC::getCallData(functionObject), thisValue, args, returnedException);
+ ASYNCCONTEXTFRAME_CALL_IMPL(global, ProfilingReason::API, functionObject, JSC::getCallData(functionObject), thisValue, args, returnedException);
}
#undef ASYNCCONTEXTFRAME_CALL_IMPL
diff --git a/src/bun.js/bindings/AsyncContextFrame.h b/src/bun.js/bindings/AsyncContextFrame.h
index 52ca0d160..f5ddf4ce0 100644
--- a/src/bun.js/bindings/AsyncContextFrame.h
+++ b/src/bun.js/bindings/AsyncContextFrame.h
@@ -20,8 +20,8 @@ public:
// The following is JSC::call but
// - it unwraps AsyncContextFrame
// - does not take a CallData, because JSC::getCallData(AsyncContextFrame) -> not callable
- static JSC::JSValue call(JSC::JSGlobalObject*, JSC::JSValue functionObject, const JSC::ArgList&, ASCIILiteral errorMessage);
- static JSC::JSValue call(JSC::JSGlobalObject*, JSC::JSValue functionObject, JSC::JSValue thisValue, const JSC::ArgList&, ASCIILiteral errorMessage);
+ // static JSC::JSValue call(JSC::JSGlobalObject*, JSC::JSValue functionObject, const JSC::ArgList&, ASCIILiteral errorMessage);
+ // static JSC::JSValue call(JSC::JSGlobalObject*, JSC::JSValue functionObject, JSC::JSValue thisValue, const JSC::ArgList&, ASCIILiteral errorMessage);
static JSC::JSValue call(JSC::JSGlobalObject*, JSC::JSValue functionObject, JSC::JSValue thisValue, const JSC::ArgList&);
static JSC::JSValue call(JSC::JSGlobalObject*, JSC::JSValue functionObject, JSC::JSValue thisValue, const JSC::ArgList&, NakedPtr<JSC::Exception>& returnedException);
diff --git a/src/bun.js/bindings/BunDebugger.cpp b/src/bun.js/bindings/BunDebugger.cpp
new file mode 100644
index 000000000..9d14a75b7
--- /dev/null
+++ b/src/bun.js/bindings/BunDebugger.cpp
@@ -0,0 +1,530 @@
+#include "root.h"
+#include <uws/src/App.h>
+
+#include <JavaScriptCore/InspectorFrontendChannel.h>
+#include <JavaScriptCore/JSGlobalObjectDebuggable.h>
+#include <JavaScriptCore/JSGlobalObjectDebugger.h>
+#include <JavaScriptCore/Debugger.h>
+#include "ScriptExecutionContext.h"
+#include "Strong.h"
+#include "debug-helpers.h"
+
+extern "C" void Bun__tickWhilePaused(bool*);
+
+namespace Bun {
+using namespace JSC;
+using namespace WebCore;
+
+class BunInspectorConnection;
+
+static WebCore::ScriptExecutionContext* debuggerScriptExecutionContext = nullptr;
+static WTF::Lock inspectorConnectionsLock = WTF::Lock();
+static WTF::HashMap<ScriptExecutionContextIdentifier, Vector<BunInspectorConnection*, 8>>* inspectorConnections = nullptr;
+
+enum class ConnectionStatus : int32_t {
+ Pending = 0,
+ Connected = 1,
+ Disconnecting = 2,
+ Disconnected = 3,
+};
+
+class BunInspectorConnection : public Inspector::FrontendChannel {
+
+public:
+ BunInspectorConnection(ScriptExecutionContext& scriptExecutionContext, JSC::JSGlobalObject* globalObject)
+ : Inspector::FrontendChannel()
+ , globalObject(globalObject)
+ , scriptExecutionContextIdentifier(scriptExecutionContext.identifier())
+ {
+ }
+
+ ~BunInspectorConnection()
+ {
+ }
+
+ static BunInspectorConnection* create(ScriptExecutionContext& scriptExecutionContext, JSC::JSGlobalObject* globalObject)
+ {
+ return new BunInspectorConnection(scriptExecutionContext, globalObject);
+ }
+
+ ConnectionType connectionType() const override
+ {
+ return ConnectionType::Remote;
+ }
+
+ void connect()
+ {
+ switch (this->status) {
+ case ConnectionStatus::Disconnected:
+ case ConnectionStatus::Disconnecting: {
+ return;
+ }
+ default: {
+ break;
+ }
+ }
+
+ if (this->jsWaitForMessageFromInspectorLock.isLocked())
+ this->jsWaitForMessageFromInspectorLock.unlockFairly();
+
+ ScriptExecutionContext::ensureOnContextThread(scriptExecutionContextIdentifier, [connection = this](ScriptExecutionContext& context) {
+ switch (connection->status) {
+ case ConnectionStatus::Pending: {
+ connection->status = ConnectionStatus::Connected;
+ auto* globalObject = context.jsGlobalObject();
+ globalObject->setInspectable(true);
+
+ auto& inspector = globalObject->inspectorDebuggable();
+ inspector.setInspectable(true);
+
+ inspector.connect(*connection);
+
+ Inspector::JSGlobalObjectDebugger* debugger = reinterpret_cast<Inspector::JSGlobalObjectDebugger*>(globalObject->debugger());
+ if (debugger) {
+ debugger->runWhilePausedCallback = [](JSC::JSGlobalObject& globalObject, bool& isDoneProcessingEvents) -> void {
+ BunInspectorConnection::runWhilePaused(globalObject, isDoneProcessingEvents);
+ };
+ }
+
+ connection->receiveMessagesOnInspectorThread(context, reinterpret_cast<Zig::GlobalObject*>(globalObject));
+
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+ });
+ }
+
+ void disconnect()
+ {
+ if (jsWaitForMessageFromInspectorLock.isLocked())
+ jsWaitForMessageFromInspectorLock.unlockFairly();
+
+ switch (this->status) {
+ case ConnectionStatus::Disconnected: {
+ return;
+ }
+ default: {
+ break;
+ }
+ }
+
+ ScriptExecutionContext::ensureOnContextThread(scriptExecutionContextIdentifier, [connection = this](ScriptExecutionContext& context) {
+ if (connection->status == ConnectionStatus::Disconnected)
+ return;
+
+ connection->status = ConnectionStatus::Disconnected;
+ connection->inspector().disconnect(*connection);
+ });
+ }
+
+ JSC::JSGlobalObjectDebuggable& inspector()
+ {
+ return globalObject->inspectorDebuggable();
+ }
+
+ void sendMessageToFrontend(const String& message) override
+ {
+ if (message.length() == 0)
+ return;
+
+ this->sendMessageToDebuggerThread(message.isolatedCopy());
+ }
+
+ static void runWhilePaused(JSGlobalObject& globalObject, bool& isDoneProcessingEvents)
+ {
+ Zig::GlobalObject* global = reinterpret_cast<Zig::GlobalObject*>(&globalObject);
+ Vector<BunInspectorConnection*, 8> connections;
+ {
+ WTF::LockHolder locker(inspectorConnectionsLock);
+ connections.appendVector(inspectorConnections->get(global->scriptExecutionContext()->identifier()));
+ }
+
+ for (auto* connection : connections) {
+ if (connection->status == ConnectionStatus::Pending) {
+ connection->connect();
+ }
+
+ if (connection->status != ConnectionStatus::Disconnected) {
+ connection->receiveMessagesOnInspectorThread(*global->scriptExecutionContext(), global);
+ }
+ }
+
+ // for (auto* connection : connections) {
+ // if (connection->status == ConnectionStatus::Connected) {
+ // connection->jsWaitForMessageFromInspectorLock.lock();
+ // }
+ // }
+
+ if (connections.size() == 1) {
+ while (!isDoneProcessingEvents) {
+ auto* connection = connections[0];
+ if (connection->status == ConnectionStatus::Disconnected || connection->status == ConnectionStatus::Disconnecting) {
+ if (global->debugger() && global->debugger()->isPaused()) {
+ global->debugger()->continueProgram();
+ }
+ break;
+ }
+ connection->receiveMessagesOnInspectorThread(*global->scriptExecutionContext(), global);
+ }
+ } else {
+ while (!isDoneProcessingEvents) {
+ size_t closedCount = 0;
+ for (auto* connection : connections) {
+ closedCount += connection->status == ConnectionStatus::Disconnected || connection->status == ConnectionStatus::Disconnecting;
+ connection->receiveMessagesOnInspectorThread(*global->scriptExecutionContext(), global);
+ if (isDoneProcessingEvents)
+ break;
+ }
+
+ if (closedCount == connections.size() && global->debugger() && !isDoneProcessingEvents) {
+ global->debugger()->continueProgram();
+ continue;
+ }
+ }
+ }
+ }
+
+ void receiveMessagesOnInspectorThread(ScriptExecutionContext& context, Zig::GlobalObject* globalObject)
+ {
+ this->jsThreadMessageScheduledCount.store(0);
+ WTF::Vector<WTF::String, 12> messages;
+
+ {
+ WTF::LockHolder locker(jsThreadMessagesLock);
+ this->jsThreadMessages.swap(messages);
+ }
+
+ auto& dispatcher = globalObject->inspectorDebuggable();
+ Inspector::JSGlobalObjectDebugger* debugger = reinterpret_cast<Inspector::JSGlobalObjectDebugger*>(globalObject->debugger());
+
+ if (!debugger) {
+ for (auto message : messages) {
+ dispatcher.dispatchMessageFromRemote(WTFMove(message));
+
+ debugger = reinterpret_cast<Inspector::JSGlobalObjectDebugger*>(globalObject->debugger());
+ if (debugger) {
+ debugger->runWhilePausedCallback = [](JSC::JSGlobalObject& globalObject, bool& isDoneProcessingEvents) -> void {
+ runWhilePaused(globalObject, isDoneProcessingEvents);
+ };
+ }
+ }
+ } else {
+ for (auto message : messages) {
+ dispatcher.dispatchMessageFromRemote(WTFMove(message));
+ }
+ }
+
+ messages.clear();
+ }
+
+ void receiveMessagesOnDebuggerThread(ScriptExecutionContext& context, Zig::GlobalObject* debuggerGlobalObject)
+ {
+ debuggerThreadMessageScheduledCount.store(0);
+ WTF::Vector<WTF::String, 12> messages;
+
+ {
+ WTF::LockHolder locker(debuggerThreadMessagesLock);
+ this->debuggerThreadMessages.swap(messages);
+ }
+
+ JSFunction* onMessageFn = jsCast<JSFunction*>(jsBunDebuggerOnMessageFunction->m_cell.get());
+ MarkedArgumentBuffer arguments;
+ arguments.ensureCapacity(messages.size());
+ auto& vm = debuggerGlobalObject->vm();
+
+ for (auto& message : messages) {
+ arguments.append(jsString(vm, message));
+ }
+
+ messages.clear();
+
+ JSC::call(debuggerGlobalObject, onMessageFn, arguments, "BunInspectorConnection::receiveMessagesOnDebuggerThread - onMessageFn"_s);
+ }
+
+ void sendMessageToDebuggerThread(WTF::String&& inputMessage)
+ {
+ {
+ WTF::LockHolder locker(debuggerThreadMessagesLock);
+ debuggerThreadMessages.append(inputMessage);
+ }
+
+ if (this->debuggerThreadMessageScheduledCount++ == 0) {
+ debuggerScriptExecutionContext->postTaskConcurrently([connection = this](ScriptExecutionContext& context) {
+ connection->receiveMessagesOnDebuggerThread(context, reinterpret_cast<Zig::GlobalObject*>(context.jsGlobalObject()));
+ });
+ }
+ }
+
+ void sendMessageToInspectorFromDebuggerThread(const WTF::String& inputMessage)
+ {
+ {
+ WTF::LockHolder locker(jsThreadMessagesLock);
+ jsThreadMessages.append(inputMessage);
+ }
+
+ if (this->jsWaitForMessageFromInspectorLock.isLocked()) {
+ this->jsWaitForMessageFromInspectorLock.unlock();
+ } else if (this->jsThreadMessageScheduledCount++ == 0) {
+ ScriptExecutionContext::postTaskTo(scriptExecutionContextIdentifier, [connection = this](ScriptExecutionContext& context) {
+ connection->receiveMessagesOnInspectorThread(context, reinterpret_cast<Zig::GlobalObject*>(context.jsGlobalObject()));
+ });
+ }
+ }
+
+ WTF::Vector<WTF::String, 12> debuggerThreadMessages;
+ WTF::Lock debuggerThreadMessagesLock = WTF::Lock();
+ std::atomic<uint32_t> debuggerThreadMessageScheduledCount { 0 };
+
+ WTF::Vector<WTF::String, 12> jsThreadMessages;
+ WTF::Lock jsThreadMessagesLock = WTF::Lock();
+ std::atomic<uint32_t> jsThreadMessageScheduledCount { 0 };
+
+ JSC::JSGlobalObject* globalObject;
+ ScriptExecutionContextIdentifier scriptExecutionContextIdentifier;
+ Bun::StrongRef* jsBunDebuggerOnMessageFunction = nullptr;
+
+ WTF::Lock jsWaitForMessageFromInspectorLock;
+ std::atomic<ConnectionStatus> status = ConnectionStatus::Pending;
+};
+
+JSC_DECLARE_HOST_FUNCTION(jsFunctionSend);
+JSC_DECLARE_HOST_FUNCTION(jsFunctionDisconnect);
+
+class JSBunInspectorConnection final : public JSC::JSNonFinalObject {
+public:
+ using Base = JSC::JSNonFinalObject;
+ static constexpr unsigned StructureFlags = Base::StructureFlags;
+ static constexpr bool needsDestruction = false;
+
+ static JSBunInspectorConnection* create(JSC::VM& vm, JSC::Structure* structure, BunInspectorConnection* connection)
+ {
+ JSBunInspectorConnection* ptr = new (NotNull, JSC::allocateCell<JSBunInspectorConnection>(vm)) JSBunInspectorConnection(vm, structure, connection);
+ ptr->finishCreation(vm);
+ return ptr;
+ }
+
+ DECLARE_EXPORT_INFO;
+ template<typename, SubspaceAccess mode>
+ static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
+ {
+ if constexpr (mode == JSC::SubspaceAccess::Concurrently)
+ return nullptr;
+ return WebCore::subspaceForImpl<JSBunInspectorConnection, WebCore::UseCustomHeapCellType::No>(
+ vm,
+ [](auto& spaces) { return spaces.m_clientSubspaceForBunInspectorConnection.get(); },
+ [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForBunInspectorConnection = std::forward<decltype(space)>(space); },
+ [](auto& spaces) { return spaces.m_subspaceForBunInspectorConnection.get(); },
+ [](auto& spaces, auto&& space) { spaces.m_subspaceForBunInspectorConnection = std::forward<decltype(space)>(space); });
+ }
+ static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
+ {
+ return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info(), JSC::NonArray, 2);
+ }
+
+ BunInspectorConnection* connection()
+ {
+ return m_connection;
+ }
+
+private:
+ JSBunInspectorConnection(JSC::VM& vm, JSC::Structure* structure, BunInspectorConnection* connection)
+ : Base(vm, structure)
+ , m_connection(connection)
+ {
+ }
+
+ void finishCreation(JSC::VM& vm)
+ {
+ Base::finishCreation(vm);
+ }
+
+ BunInspectorConnection* m_connection;
+};
+
+JSC_DEFINE_HOST_FUNCTION(jsFunctionSend, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
+{
+ auto* jsConnection = jsDynamicCast<JSBunInspectorConnection*>(callFrame->thisValue());
+ auto message = callFrame->uncheckedArgument(0).toWTFString(globalObject).isolatedCopy();
+
+ if (!jsConnection)
+ return JSValue::encode(jsUndefined());
+
+ jsConnection->connection()->sendMessageToInspectorFromDebuggerThread(message);
+
+ return JSValue::encode(jsUndefined());
+}
+
+JSC_DEFINE_HOST_FUNCTION(jsFunctionDisconnect, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
+{
+ auto* jsConnection = jsDynamicCast<JSBunInspectorConnection*>(callFrame->thisValue());
+ if (!jsConnection)
+ return JSValue::encode(jsUndefined());
+
+ auto& connection = *jsConnection->connection();
+
+ if (connection.status == ConnectionStatus::Connected || connection.status == ConnectionStatus::Pending) {
+ connection.status = ConnectionStatus::Disconnecting;
+ connection.disconnect();
+ if (connection.jsWaitForMessageFromInspectorLock.isLocked())
+ connection.jsWaitForMessageFromInspectorLock.unlockFairly();
+ }
+
+ return JSValue::encode(jsUndefined());
+}
+
+const JSC::ClassInfo JSBunInspectorConnection::s_info = { "BunInspectorConnection"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSBunInspectorConnection) };
+
+extern "C" unsigned int Bun__createJSDebugger(Zig::GlobalObject* globalObject)
+{
+ {
+ WTF::LockHolder locker(inspectorConnectionsLock);
+ if (inspectorConnections == nullptr) {
+ inspectorConnections = new WTF::HashMap<ScriptExecutionContextIdentifier, Vector<BunInspectorConnection*, 8>>();
+ }
+
+ inspectorConnections->add(globalObject->scriptExecutionContext()->identifier(), Vector<BunInspectorConnection*, 8>());
+ }
+
+ return static_cast<unsigned int>(globalObject->scriptExecutionContext()->identifier());
+}
+extern "C" void Bun__tickWhilePaused(bool*);
+
+extern "C" void Bun__ensureDebugger(ScriptExecutionContextIdentifier scriptId, bool pauseOnStart)
+{
+
+ auto* globalObject = ScriptExecutionContext::getScriptExecutionContext(scriptId)->jsGlobalObject();
+ globalObject->setInspectable(true);
+
+ auto& inspector = globalObject->inspectorDebuggable();
+ inspector.setInspectable(true);
+
+ Inspector::JSGlobalObjectDebugger* debugger = reinterpret_cast<Inspector::JSGlobalObjectDebugger*>(globalObject->debugger());
+ if (debugger) {
+ debugger->runWhilePausedCallback = [](JSC::JSGlobalObject& globalObject, bool& isDoneProcessingEvents) -> void {
+ BunInspectorConnection::runWhilePaused(globalObject, isDoneProcessingEvents);
+ };
+ }
+
+ if (pauseOnStart)
+ inspector.pauseWaitingForAutomaticInspection();
+}
+
+JSC_DEFINE_HOST_FUNCTION(jsFunctionCreateConnection, (JSGlobalObject * globalObject, CallFrame* callFrame))
+{
+ auto* debuggerGlobalObject = jsDynamicCast<Zig::GlobalObject*>(globalObject);
+ if (!debuggerGlobalObject)
+ return JSValue::encode(jsUndefined());
+
+ ScriptExecutionContext* targetContext = ScriptExecutionContext::getScriptExecutionContext(static_cast<ScriptExecutionContextIdentifier>(callFrame->argument(0).toUInt32(globalObject)));
+ JSFunction* onMessageFn = jsCast<JSFunction*>(callFrame->argument(1).toObject(globalObject));
+
+ if (!targetContext || !onMessageFn)
+ return JSValue::encode(jsUndefined());
+
+ auto& vm = globalObject->vm();
+ auto connection = BunInspectorConnection::create(
+ *targetContext,
+ targetContext->jsGlobalObject());
+
+ {
+ WTF::LockHolder locker(inspectorConnectionsLock);
+ auto connections = inspectorConnections->get(targetContext->identifier());
+ connections.append(connection);
+ inspectorConnections->set(targetContext->identifier(), connections);
+ }
+ connection->jsBunDebuggerOnMessageFunction = new Bun::StrongRef(vm, onMessageFn);
+ connection->connect();
+
+ return JSValue::encode(JSBunInspectorConnection::create(vm, JSBunInspectorConnection::createStructure(vm, globalObject, globalObject->objectPrototype()), connection));
+}
+
+extern "C" BunString Bun__startJSDebuggerThread(Zig::GlobalObject* debuggerGlobalObject, ScriptExecutionContextIdentifier scriptId, BunString* portOrPathString)
+{
+ if (!debuggerScriptExecutionContext)
+ debuggerScriptExecutionContext = debuggerGlobalObject->scriptExecutionContext();
+ JSC::VM& vm = debuggerGlobalObject->vm();
+ JSValue defaultValue = debuggerGlobalObject->internalModuleRegistry()->requireId(debuggerGlobalObject, vm, InternalModuleRegistry::Field::InternalDebugger);
+ JSFunction* debuggerDefaultFn = jsCast<JSFunction*>(defaultValue.asCell());
+
+ MarkedArgumentBuffer arguments;
+
+ arguments.append(jsNumber(static_cast<unsigned int>(scriptId)));
+ arguments.append(Bun::toJS(debuggerGlobalObject, *portOrPathString));
+ arguments.append(JSFunction::create(vm, debuggerGlobalObject, 1, String(), jsFunctionCreateConnection, ImplementationVisibility::Public));
+ arguments.append(JSFunction::create(vm, debuggerGlobalObject, 1, String("send"_s), jsFunctionSend, ImplementationVisibility::Public));
+ arguments.append(JSFunction::create(vm, debuggerGlobalObject, 0, String("disconnect"_s), jsFunctionDisconnect, ImplementationVisibility::Public));
+
+ JSValue serverURLValue = JSC::call(debuggerGlobalObject, debuggerDefaultFn, arguments, "Bun__initJSDebuggerThread - debuggerDefaultFn"_s);
+
+ if (serverURLValue.isUndefinedOrNull())
+ return BunStringEmpty;
+
+ return Bun::toStringRef(debuggerGlobalObject, serverURLValue);
+}
+
+enum class AsyncCallTypeUint8 : uint8_t {
+ DOMTimer = 1,
+ EventListener = 2,
+ PostMessage = 3,
+ RequestAnimationFrame = 4,
+ Microtask = 5,
+};
+
+static Inspector::InspectorDebuggerAgent::AsyncCallType getCallType(AsyncCallTypeUint8 callType)
+{
+ Inspector::InspectorDebuggerAgent::AsyncCallType type;
+ switch (callType) {
+ case AsyncCallTypeUint8::DOMTimer:
+ return Inspector::InspectorDebuggerAgent::AsyncCallType::DOMTimer;
+ case AsyncCallTypeUint8::EventListener:
+ return Inspector::InspectorDebuggerAgent::AsyncCallType::EventListener;
+ case AsyncCallTypeUint8::PostMessage:
+ return Inspector::InspectorDebuggerAgent::AsyncCallType::PostMessage;
+ case AsyncCallTypeUint8::RequestAnimationFrame:
+ return Inspector::InspectorDebuggerAgent::AsyncCallType::RequestAnimationFrame;
+ case AsyncCallTypeUint8::Microtask:
+ return Inspector::InspectorDebuggerAgent::AsyncCallType::Microtask;
+ default:
+ RELEASE_ASSERT_NOT_REACHED();
+ }
+}
+
+extern "C" void Debugger__didScheduleAsyncCall(JSGlobalObject* globalObject, AsyncCallTypeUint8 callType, uint64_t callbackId, bool singleShot)
+{
+ auto* agent = debuggerAgent(globalObject);
+ if (!agent)
+ return;
+
+ agent->didScheduleAsyncCall(globalObject, getCallType(callType), callbackId, singleShot);
+}
+
+extern "C" void Debugger__didCancelAsyncCall(JSGlobalObject* globalObject, AsyncCallTypeUint8 callType, uint64_t callbackId)
+{
+ auto* agent = debuggerAgent(globalObject);
+ if (!agent)
+ return;
+
+ agent->didCancelAsyncCall(getCallType(callType), callbackId);
+}
+
+extern "C" void Debugger__didDispatchAsyncCall(JSGlobalObject* globalObject, AsyncCallTypeUint8 callType, uint64_t callbackId)
+{
+ auto* agent = debuggerAgent(globalObject);
+ if (!agent)
+ return;
+
+ agent->didDispatchAsyncCall(getCallType(callType), callbackId);
+}
+
+extern "C" void Debugger__willDispatchAsyncCall(JSGlobalObject* globalObject, AsyncCallTypeUint8 callType, uint64_t callbackId)
+{
+ auto* agent = debuggerAgent(globalObject);
+ if (!agent)
+ return;
+
+ agent->willDispatchAsyncCall(getCallType(callType), callbackId);
+}
+}
diff --git a/src/bun.js/bindings/BunInspector.cpp b/src/bun.js/bindings/BunInspector.cpp
index ccb0a702d..76920e398 100644
--- a/src/bun.js/bindings/BunInspector.cpp
+++ b/src/bun.js/bindings/BunInspector.cpp
@@ -3,6 +3,10 @@
#include <JavaScriptCore/InspectorFrontendChannel.h>
#include <JavaScriptCore/JSGlobalObjectDebuggable.h>
+#include <JavaScriptCore/JSGlobalObjectDebugger.h>
+#include <JavaScriptCore/Debugger.h>
+
+extern "C" void Bun__tickWhilePaused(bool*);
namespace Bun {
using namespace JSC;
@@ -31,6 +35,13 @@ public:
{
this->globalObject = globalObject;
this->globalObject->inspectorDebuggable().connect(*this);
+
+ Inspector::JSGlobalObjectDebugger* debugger = reinterpret_cast<Inspector::JSGlobalObjectDebugger*>(this->globalObject->debugger());
+ if (debugger) {
+ debugger->runWhilePausedCallback = [](JSC::JSGlobalObject& globalObject, bool& isPaused) -> void {
+ Bun__tickWhilePaused(&isPaused);
+ };
+ }
}
void onClose()
@@ -57,6 +68,13 @@ public:
void onMessage(std::string_view message)
{
WTF::String messageString = WTF::String::fromUTF8(message.data(), message.length());
+ Inspector::JSGlobalObjectDebugger* debugger = reinterpret_cast<Inspector::JSGlobalObjectDebugger*>(this->globalObject->debugger());
+ if (debugger) {
+ debugger->runWhilePausedCallback = [](JSC::JSGlobalObject& globalObject, bool& done) -> void {
+ Inspector::JSGlobalObjectDebugger* debugger = reinterpret_cast<Inspector::JSGlobalObjectDebugger*>(globalObject.debugger());
+ Bun__tickWhilePaused(&done);
+ };
+ }
this->globalObject->inspectorDebuggable().dispatchMessageFromRemote(WTFMove(messageString));
}
@@ -84,6 +102,7 @@ public:
using BunInspectorConnectionNoSSL = BunInspectorConnection<false>;
using SSLBunInspectorConnection = BunInspectorConnection<true>;
+
template<bool isSSL>
static void addInspector(void* app, JSC::JSGlobalObject* globalObject)
{
diff --git a/src/bun.js/bindings/Debugger.zig b/src/bun.js/bindings/Debugger.zig
new file mode 100644
index 000000000..3d9fc450c
--- /dev/null
+++ b/src/bun.js/bindings/Debugger.zig
@@ -0,0 +1,33 @@
+const bun = @import("root").bun;
+const JSC = bun.JSC;
+
+pub const Debugger = struct {
+ pub const AsyncCallType = enum(u8) {
+ DOMTimer = 1,
+ EventListener = 2,
+ PostMessage = 3,
+ RequestAnimationFrame = 4,
+ Microtask = 5,
+ };
+ extern fn Debugger__didScheduleAsyncCall(*JSC.JSGlobalObject, AsyncCallType, u64, bool) void;
+ extern fn Debugger__didCancelAsyncCall(*JSC.JSGlobalObject, AsyncCallType, u64) void;
+ extern fn Debugger__didDispatchAsyncCall(*JSC.JSGlobalObject, AsyncCallType, u64) void;
+ extern fn Debugger__willDispatchAsyncCall(*JSC.JSGlobalObject, AsyncCallType, u64) void;
+
+ pub fn didScheduleAsyncCall(globalObject: *JSC.JSGlobalObject, call: AsyncCallType, id: u64, single_shot: bool) void {
+ JSC.markBinding(@src());
+ Debugger__didScheduleAsyncCall(globalObject, call, id, single_shot);
+ }
+ pub fn didCancelAsyncCall(globalObject: *JSC.JSGlobalObject, call: AsyncCallType, id: u64) void {
+ JSC.markBinding(@src());
+ Debugger__didCancelAsyncCall(globalObject, call, id);
+ }
+ pub fn didDispatchAsyncCall(globalObject: *JSC.JSGlobalObject, call: AsyncCallType, id: u64) void {
+ JSC.markBinding(@src());
+ Debugger__didDispatchAsyncCall(globalObject, call, id);
+ }
+ pub fn willDispatchAsyncCall(globalObject: *JSC.JSGlobalObject, call: AsyncCallType, id: u64) void {
+ JSC.markBinding(@src());
+ Debugger__willDispatchAsyncCall(globalObject, call, id);
+ }
+};
diff --git a/src/bun.js/bindings/InternalModuleRegistry.cpp b/src/bun.js/bindings/InternalModuleRegistry.cpp
index 8323560d6..8dbf42a02 100644
--- a/src/bun.js/bindings/InternalModuleRegistry.cpp
+++ b/src/bun.js/bindings/InternalModuleRegistry.cpp
@@ -160,4 +160,4 @@ JSC_DEFINE_HOST_FUNCTION(InternalModuleRegistry::jsCreateInternalModuleById, (JS
} // namespace Bun
#undef INTERNAL_MODULE_REGISTRY_GENERATE_
-#undef INTERNAL_MODULE_REGISTRY_GENERATE
+#undef INTERNAL_MODULE_REGISTRY_GENERATE \ No newline at end of file
diff --git a/src/bun.js/bindings/JSBundlerPlugin.cpp b/src/bun.js/bindings/JSBundlerPlugin.cpp
index ec3933574..6ae266df7 100644
--- a/src/bun.js/bindings/JSBundlerPlugin.cpp
+++ b/src/bun.js/bindings/JSBundlerPlugin.cpp
@@ -404,7 +404,7 @@ extern "C" EncodedJSValue JSBundlerPlugin__runSetupFunction(
arguments.append(JSValue::decode(encodedConfig));
auto* lexicalGlobalObject = jsCast<JSFunction*>(JSValue::decode(encodedSetupFunction))->globalObject();
- auto result = JSC::call(lexicalGlobalObject, setupFunction, callData, plugin, arguments);
+ auto result = call(lexicalGlobalObject, setupFunction, callData, plugin, arguments);
if (UNLIKELY(scope.exception())) {
auto exception = scope.exception();
scope.clearException();
diff --git a/src/bun.js/bindings/JSReadableHelper.cpp b/src/bun.js/bindings/JSReadableHelper.cpp
index 3a4de4637..0c459f329 100644
--- a/src/bun.js/bindings/JSReadableHelper.cpp
+++ b/src/bun.js/bindings/JSReadableHelper.cpp
@@ -38,7 +38,7 @@ static bool callRead(JSValue stream, JSFunction* read, JSC::MarkedArgumentBuffer
{
WTF::NakedPtr<JSC::Exception> exceptionPtr;
JSC::CallData callData = JSC::getCallData(read);
- JSValue ret = JSC::call(lexicalGlobalObject, read, callData, JSValue(stream), WTFMove(args), exceptionPtr);
+ JSValue ret = call(lexicalGlobalObject, read, callData, JSValue(stream), WTFMove(args), exceptionPtr);
if (auto* exception = exceptionPtr.get()) {
JSC::Identifier errorEventName = JSC::Identifier::fromString(vm, "error"_s);
if (emitter.hasEventListeners(errorEventName)) {
diff --git a/src/bun.js/bindings/ModuleLoader.cpp b/src/bun.js/bindings/ModuleLoader.cpp
index 2c8b95612..4e2de9294 100644
--- a/src/bun.js/bindings/ModuleLoader.cpp
+++ b/src/bun.js/bindings/ModuleLoader.cpp
@@ -359,7 +359,7 @@ static JSValue handleVirtualModuleResult(
arguments.append(jsUndefined());
arguments.append(pendingModule);
ASSERT(!arguments.hasOverflowed());
- JSC::call(globalObject, performPromiseThenFunction, callData, jsUndefined(), arguments);
+ JSC::profiledCall(globalObject, ProfilingReason::Microtask, performPromiseThenFunction, callData, jsUndefined(), arguments);
return internalPromise;
}
default: {
diff --git a/src/bun.js/bindings/ScriptExecutionContext.cpp b/src/bun.js/bindings/ScriptExecutionContext.cpp
index eab41d584..2113c9f64 100644
--- a/src/bun.js/bindings/ScriptExecutionContext.cpp
+++ b/src/bun.js/bindings/ScriptExecutionContext.cpp
@@ -114,12 +114,17 @@ void ScriptExecutionContext::willDestroyDestructionObserver(ContextDestructionOb
m_destructionObservers.remove(&observer);
}
+bool ScriptExecutionContext::isJSExecutionForbidden()
+{
+ return !m_vm || m_vm->executionForbidden();
+}
+
extern "C" void* Bun__getVM();
bool ScriptExecutionContext::isContextThread()
{
auto clientData = WebCore::clientData(vm());
- return clientData->bunVM == Bun__getVM();
+ return clientData && clientData->bunVM == Bun__getVM();
}
bool ScriptExecutionContext::ensureOnContextThread(ScriptExecutionContextIdentifier identifier, Function<void(ScriptExecutionContext&)>&& task)
diff --git a/src/bun.js/bindings/ScriptExecutionContext.h b/src/bun.js/bindings/ScriptExecutionContext.h
index b32435240..6f10dced7 100644
--- a/src/bun.js/bindings/ScriptExecutionContext.h
+++ b/src/bun.js/bindings/ScriptExecutionContext.h
@@ -129,7 +129,7 @@ public:
bool isContextThread();
bool isDocument() { return false; }
bool isWorkerGlobalScope() { return true; }
- bool isJSExecutionForbidden() { return false; }
+ bool isJSExecutionForbidden();
void reportException(const String& errorMessage, int lineNumber, int columnNumber, const String& sourceURL, JSC::Exception* exception, RefPtr<void*>&&, CachedScript* = nullptr, bool = false)
{
}
diff --git a/src/bun.js/bindings/Strong.cpp b/src/bun.js/bindings/Strong.cpp
index 8ec63e318..045b4484a 100644
--- a/src/bun.js/bindings/Strong.cpp
+++ b/src/bun.js/bindings/Strong.cpp
@@ -1,28 +1,9 @@
#include "root.h"
#include <JavaScriptCore/StrongInlines.h>
#include "BunClientData.h"
-
+#include "Strong.h"
namespace Bun {
-// We tried to pool these
-// But it was very complicated
-class StrongRef {
- WTF_MAKE_ISO_ALLOCATED(StrongRef);
-
-public:
- StrongRef(JSC::VM& vm, JSC::JSValue value)
- : m_cell(vm, value)
- {
- }
-
- StrongRef()
- : m_cell()
- {
- }
-
- JSC::Strong<JSC::Unknown> m_cell;
-};
-
WTF_MAKE_ISO_ALLOCATED_IMPL(StrongRef);
}
diff --git a/src/bun.js/bindings/Strong.h b/src/bun.js/bindings/Strong.h
new file mode 100644
index 000000000..f39d1c611
--- /dev/null
+++ b/src/bun.js/bindings/Strong.h
@@ -0,0 +1,27 @@
+#pragma once
+
+#include "root.h"
+#include "JavaScriptCore/Strong.h"
+
+namespace Bun {
+
+// We tried to pool these
+// But it was very complicated
+class StrongRef {
+ WTF_MAKE_ISO_ALLOCATED(StrongRef);
+
+public:
+ StrongRef(JSC::VM& vm, JSC::JSValue value)
+ : m_cell(vm, value)
+ {
+ }
+
+ StrongRef()
+ : m_cell()
+ {
+ }
+
+ JSC::Strong<JSC::Unknown> m_cell;
+};
+
+} \ No newline at end of file
diff --git a/src/bun.js/bindings/ZigConsoleClient.cpp b/src/bun.js/bindings/ZigConsoleClient.cpp
index a98c246de..6fb364d93 100644
--- a/src/bun.js/bindings/ZigConsoleClient.cpp
+++ b/src/bun.js/bindings/ZigConsoleClient.cpp
@@ -8,6 +8,12 @@
#include "ZigConsoleClient.h"
#include "wtf/text/WTFString.h"
+#undef ENABLE_INSPECTOR_ALTERNATE_DISPATCHERS
+
+#include "JavaScriptCore/JSGlobalObjectInspectorController.h"
+#include "JavaScriptCore/JSGlobalObjectDebuggable.h"
+#include "JavaScriptCore/ConsoleClient.h"
+
#include "GCDefferalContext.h"
using ScriptArguments = Inspector::ScriptArguments;
@@ -24,6 +30,11 @@ void Zig::ConsoleClient::messageWithTypeAndLevel(MessageType type, MessageLevel
JSC::JSGlobalObject* globalObject,
Ref<ScriptArguments>&& arguments)
{
+ if (globalObject->inspectable()) {
+ if (auto* client = globalObject->inspectorController().consoleClient().get()) {
+ client->messageWithTypeAndLevel(type, level, globalObject, arguments.copyRef());
+ }
+ }
JSC::VM& vm = globalObject->vm();
auto args = arguments.ptr();
JSC__JSValue jsArgs[255];
diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp
index 3de7d3daa..baa1ddda7 100644
--- a/src/bun.js/bindings/ZigGlobalObject.cpp
+++ b/src/bun.js/bindings/ZigGlobalObject.cpp
@@ -3863,7 +3863,7 @@ EncodedJSValue GlobalObject::assignToStream(JSValue stream, JSValue controller)
arguments.append(stream);
arguments.append(controller);
- auto result = JSC::call(this, function, callData, JSC::jsUndefined(), arguments);
+ auto result = call(this, function, callData, JSC::jsUndefined(), arguments);
if (scope.exception())
return JSC::JSValue::encode(scope.exception());
diff --git a/src/bun.js/bindings/ZigGlobalObject.h b/src/bun.js/bindings/ZigGlobalObject.h
index 1d82cd0f3..0535b1e8f 100644
--- a/src/bun.js/bindings/ZigGlobalObject.h
+++ b/src/bun.js/bindings/ZigGlobalObject.h
@@ -398,6 +398,7 @@ public:
mutable WriteBarrier<Unknown> m_JSWebSocketSetterValue;
mutable WriteBarrier<Unknown> m_JSWorkerSetterValue;
+ mutable WriteBarrier<Unknown> m_JSBunDebuggerValue;
mutable WriteBarrier<JSFunction> m_thenables[promiseFunctionsSize + 1];
Structure* memoryFootprintStructure()
diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp
index f7998c83c..e1d6ba526 100644
--- a/src/bun.js/bindings/bindings.cpp
+++ b/src/bun.js/bindings/bindings.cpp
@@ -252,7 +252,8 @@ static void handlePromise(PromiseType* promise, JSC__JSGlobalObject* globalObjec
arguments.append(jsUndefined());
arguments.append(JSValue::decode(ctx));
ASSERT(!arguments.hasOverflowed());
- JSC::call(globalThis, performPromiseThenFunction, callData, jsUndefined(), arguments);
+ // async context tracking is handled by performPromiseThenFunction internally.
+ JSC::profiledCall(globalThis, JSC::ProfilingReason::Microtask, performPromiseThenFunction, callData, jsUndefined(), arguments);
} else {
promise->then(globalThis, resolverFunction, rejecterFunction);
}
@@ -1770,7 +1771,7 @@ extern "C" JSC__JSValue JSObjectCallAsFunctionReturnValue(JSContextRef ctx, JSC_
return JSC::JSValue::encode(JSC::JSValue());
NakedPtr<JSC::Exception> returnedException = nullptr;
- auto result = JSC::call(globalObject, jsObject, callData, jsThisObject, argList, returnedException);
+ auto result = JSC::profiledCall(globalObject, ProfilingReason::API, jsObject, callData, jsThisObject, argList, returnedException);
if (asyncContextData) {
asyncContextData->putInternalField(vm, 0, restoreAsyncContext);
@@ -1811,7 +1812,7 @@ JSC__JSValue JSObjectCallAsFunctionReturnValueHoldingAPILock(JSContextRef ctx, J
return JSC::JSValue::encode(JSC::JSValue());
NakedPtr<JSC::Exception> returnedException = nullptr;
- auto result = JSC::call(globalObject, jsObject, callData, jsThisObject, argList, returnedException);
+ auto result = call(globalObject, jsObject, callData, jsThisObject, argList, returnedException);
if (returnedException.get()) {
return JSC::JSValue::encode(JSC::JSValue(returnedException.get()));
diff --git a/src/bun.js/bindings/debug-helpers.h b/src/bun.js/bindings/debug-helpers.h
new file mode 100644
index 000000000..a0fc99868
--- /dev/null
+++ b/src/bun.js/bindings/debug-helpers.h
@@ -0,0 +1,18 @@
+#include "root.h"
+
+#include "JavaScriptCore/InspectorDebuggerAgent.h"
+
+namespace JSC {
+Inspector::InspectorDebuggerAgent* debuggerAgent(JSC::JSGlobalObject* globalObject)
+{
+ if (LIKELY(!globalObject->hasDebugger())) {
+ return nullptr;
+ }
+
+ if (auto* debugger = globalObject->debugger()) {
+ return dynamicDowncast<Inspector::InspectorDebuggerAgent>(debugger->client());
+ }
+
+ return nullptr;
+}
+} \ No newline at end of file
diff --git a/src/bun.js/bindings/napi.cpp b/src/bun.js/bindings/napi.cpp
index c27bcf533..d532e5444 100644
--- a/src/bun.js/bindings/napi.cpp
+++ b/src/bun.js/bindings/napi.cpp
@@ -1918,7 +1918,7 @@ extern "C" napi_status napi_call_function(napi_env env, napi_value recv_napi,
if (thisValue.isEmpty()) {
thisValue = JSC::jsUndefined();
}
- JSC::JSValue result = JSC::call(globalObject, funcValue, callData, thisValue, args);
+ JSC::JSValue result = call(globalObject, funcValue, callData, thisValue, args);
if (result_ptr) {
*result_ptr = toNapi(result);
diff --git a/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h b/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h
index a81b84577..4c09df6a5 100644
--- a/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h
+++ b/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h
@@ -41,7 +41,7 @@ public:
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForMockWithImplementationCleanupData;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForProcessObject;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForInternalModuleRegistry;
-
+ std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForBunInspectorConnection;
#include "ZigGeneratedClasses+DOMClientIsoSubspaces.h"
/* --- bun --- */
diff --git a/src/bun.js/bindings/webcore/DOMIsoSubspaces.h b/src/bun.js/bindings/webcore/DOMIsoSubspaces.h
index c67112388..2b834cf3c 100644
--- a/src/bun.js/bindings/webcore/DOMIsoSubspaces.h
+++ b/src/bun.js/bindings/webcore/DOMIsoSubspaces.h
@@ -41,6 +41,7 @@ public:
std::unique_ptr<IsoSubspace> m_subspaceForMockWithImplementationCleanupData;
std::unique_ptr<IsoSubspace> m_subspaceForProcessObject;
std::unique_ptr<IsoSubspace> m_subspaceForInternalModuleRegistry;
+ std::unique_ptr<IsoSubspace> m_subspaceForBunInspectorConnection;
#include "ZigGeneratedClasses+DOMIsoSubspaces.h"
/*-- BUN --*/
diff --git a/src/bun.js/bindings/webcore/EventEmitter.cpp b/src/bun.js/bindings/webcore/EventEmitter.cpp
index 0e273042b..de0be2c89 100644
--- a/src/bun.js/bindings/webcore/EventEmitter.cpp
+++ b/src/bun.js/bindings/webcore/EventEmitter.cpp
@@ -234,7 +234,7 @@ void EventEmitter::innerInvokeEventListeners(const Identifier& eventType, Simple
continue;
WTF::NakedPtr<JSC::Exception> exceptionPtr;
- JSC::call(lexicalGlobalObject, jsFunction, callData, thisValue, arguments, exceptionPtr);
+ call(lexicalGlobalObject, jsFunction, callData, thisValue, arguments, exceptionPtr);
auto* exception = exceptionPtr.get();
if (UNLIKELY(exception)) {
diff --git a/src/bun.js/event_loop.zig b/src/bun.js/event_loop.zig
index a59deb19d..92613d0f0 100644
--- a/src/bun.js/event_loop.zig
+++ b/src/bun.js/event_loop.zig
@@ -97,17 +97,20 @@ pub fn WorkTask(comptime Context: type, comptime async_io: bool) type {
allocator: std.mem.Allocator,
globalThis: *JSGlobalObject,
concurrent_task: ConcurrentTask = .{},
+ async_task_tracker: JSC.AsyncTaskTracker,
// This is a poll because we want it to enter the uSockets loop
ref: JSC.PollRef = .{},
pub fn createOnJSThread(allocator: std.mem.Allocator, globalThis: *JSGlobalObject, value: *Context) !*This {
var this = try allocator.create(This);
+ var vm = globalThis.bunVM();
this.* = .{
- .event_loop = globalThis.bunVM().eventLoop(),
+ .event_loop = vm.eventLoop(),
.ctx = value,
.allocator = allocator,
.globalThis = globalThis,
+ .async_task_tracker = JSC.AsyncTaskTracker.init(vm),
};
this.ref.ref(this.event_loop.virtual_machine);
@@ -121,12 +124,20 @@ pub fn WorkTask(comptime Context: type, comptime async_io: bool) type {
pub fn runFromJS(this: *This) void {
var ctx = this.ctx;
- this.ref.unref(this.event_loop.virtual_machine);
- ctx.then(this.globalThis);
+ const tracker = this.async_task_tracker;
+ var vm = this.event_loop.virtual_machine;
+ var globalThis = this.globalThis;
+ this.ref.unref(vm);
+
+ tracker.willDispatch(globalThis);
+ ctx.then(globalThis);
+ tracker.didDispatch(globalThis);
}
pub fn schedule(this: *This) void {
- this.ref.ref(this.event_loop.virtual_machine);
+ var vm = this.event_loop.virtual_machine;
+ this.ref.ref(vm);
+ this.async_task_tracker.didSchedule(this.globalThis);
if (comptime async_io) {
NetworkThread.init() catch return;
NetworkThread.global.schedule(NetworkThread.Batch.from(&this.task));
@@ -487,6 +498,17 @@ pub const GarbageCollectionController = struct {
};
};
+export fn Bun__tickWhilePaused(paused: *bool) void {
+ JSC.markBinding(@src());
+ JSC.VirtualMachine.get().eventLoop().tickWhilePaused(paused);
+}
+
+comptime {
+ if (!JSC.is_bindgen) {
+ _ = Bun__tickWhilePaused;
+ }
+}
+
pub const EventLoop = struct {
tasks: Queue = undefined,
concurrent_tasks: ConcurrentTask.Queue = ConcurrentTask.Queue{},
@@ -500,6 +522,12 @@ pub const EventLoop = struct {
pub const Queue = std.fifo.LinearFifo(Task, .Dynamic);
const log = bun.Output.scoped(.EventLoop, false);
+ pub fn tickWhilePaused(this: *EventLoop, done: *bool) void {
+ while (!done.*) {
+ this.virtual_machine.uws_event_loop.?.tick();
+ }
+ }
+
pub fn tickWithCount(this: *EventLoop) u32 {
var global = this.global;
var global_vm = global.vm();
diff --git a/src/bun.js/javascript.zig b/src/bun.js/javascript.zig
index 3e5ac799d..a1dd77674 100644
--- a/src/bun.js/javascript.zig
+++ b/src/bun.js/javascript.zig
@@ -496,6 +496,8 @@ pub const VirtualMachine = struct {
gc_controller: JSC.GarbageCollectionController = .{},
worker: ?*JSC.WebWorker = null,
+ debugger: ?Debugger = null,
+
pub const OnUnhandledRejection = fn (*VirtualMachine, globalObject: *JSC.JSGlobalObject, JSC.JSValue) void;
pub const OnException = fn (*ZigException) void;
@@ -504,6 +506,10 @@ pub const VirtualMachine = struct {
return this.worker == null;
}
+ pub fn isInspectorEnabled(this: *const VirtualMachine) bool {
+ return this.debugger != null;
+ }
+
pub fn setOnException(this: *VirtualMachine, callback: *const OnException) void {
this.on_exception = callback;
}
@@ -706,6 +712,106 @@ pub const VirtualMachine = struct {
}
}
+ pub fn nextAsyncTaskID(this: *VirtualMachine) u64 {
+ var debugger: *Debugger = &(this.debugger orelse return 0);
+ debugger.next_debugger_id +%= 1;
+ return debugger.next_debugger_id;
+ }
+
+ pub const Debugger = struct {
+ path_or_port: []const u8 = "",
+ script_execution_context_id: u32 = 0,
+ next_debugger_id: u64 = 1,
+ poll_ref: JSC.PollRef = .{},
+ auto_pause: bool = false,
+ const debug = Output.scoped(.DEBUGGER, false);
+
+ extern "C" fn Bun__createJSDebugger(*JSC.JSGlobalObject) u32;
+ extern "C" fn Bun__ensureDebugger(u32, bool) void;
+ extern "C" fn Bun__startJSDebuggerThread(*JSC.JSGlobalObject, u32, *bun.String) bun.String;
+ var has_started_debugger_thread: bool = false;
+ var futex_atomic: std.atomic.Atomic(u32) = undefined;
+
+ pub fn create(this: *VirtualMachine, globalObject: *JSGlobalObject) !void {
+ debug("create", .{});
+ this.debugger.?.script_execution_context_id = Bun__createJSDebugger(globalObject);
+ if (!has_started_debugger_thread) {
+ has_started_debugger_thread = true;
+ futex_atomic = std.atomic.Atomic(u32).init(0);
+ var thread = try std.Thread.spawn(.{}, startJSDebuggerThread, .{this});
+ thread.detach();
+ }
+ this.eventLoop().ensureWaker();
+ if (this.debugger.?.auto_pause) {
+ this.debugger.?.poll_ref.ref(this);
+ }
+ debug("spin", .{});
+ while (futex_atomic.load(.Monotonic) > 0) std.Thread.Futex.wait(&futex_atomic, 1);
+ if (comptime Environment.allow_assert)
+ debug("waitForDebugger: {}", .{Output.ElapsedFormatter{
+ .colors = Output.enable_ansi_colors_stderr,
+ .duration_ns = @truncate(@as(u128, @intCast(std.time.nanoTimestamp() - bun.CLI.start_time))),
+ }});
+
+ Bun__ensureDebugger(this.debugger.?.script_execution_context_id, this.debugger.?.auto_pause);
+ }
+
+ pub fn startJSDebuggerThread(other_vm: *VirtualMachine) void {
+ var arena = bun.MimallocArena.init() catch unreachable;
+ Output.Source.configureNamedThread("Debugger");
+ debug("startJSDebuggerThread", .{});
+
+ var vm = JSC.VirtualMachine.init(.{
+ .allocator = arena.allocator(),
+ .args = std.mem.zeroes(Api.TransformOptions),
+ .store_fd = false,
+ }) catch @panic("Failed to create Debugger VM");
+ vm.allocator = arena.allocator();
+ vm.arena = &arena;
+
+ vm.bundler.configureDefines() catch @panic("Failed to configure defines");
+ vm.is_main_thread = false;
+ vm.eventLoop().ensureWaker();
+
+ vm.global.vm().holdAPILock(other_vm, @ptrCast(&start));
+ }
+
+ pub export var Bun__debugger_server_url: bun.String = undefined;
+
+ fn start(other_vm: *VirtualMachine) void {
+ var this = VirtualMachine.get();
+ var str = bun.String.create(other_vm.debugger.?.path_or_port);
+ Bun__debugger_server_url = Bun__startJSDebuggerThread(this.global, other_vm.debugger.?.script_execution_context_id, &str);
+ Bun__debugger_server_url.toThreadSafe();
+
+ this.global.handleRejectedPromises();
+
+ if (this.log.msgs.items.len > 0) {
+ if (Output.enable_ansi_colors) {
+ this.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), true) catch {};
+ } else {
+ this.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), false) catch {};
+ }
+ Output.prettyErrorln("\n", .{});
+ Output.flush();
+ }
+
+ futex_atomic.store(0, .Monotonic);
+ std.Thread.Futex.wake(&futex_atomic, 1);
+ debug("wake", .{});
+ this.eventLoop().tick();
+
+ while (true) {
+ while (this.eventLoop().tasks.count > 0 or this.active_tasks > 0 or this.uws_event_loop.?.active > 0) {
+ this.tick();
+ this.eventLoop().autoTickActive();
+ }
+
+ this.eventLoop().tickPossiblyForever();
+ }
+ }
+ };
+
pub inline fn enqueueTask(this: *VirtualMachine, task: Task) void {
this.eventLoop().enqueueTask(task);
}
@@ -917,6 +1023,8 @@ pub const VirtualMachine = struct {
source_code_printer.?.ctx.append_null_byte = false;
}
+ vm.configureDebugger(opts.debugger);
+
return vm;
}
@@ -928,6 +1036,7 @@ pub const VirtualMachine = struct {
store_fd: bool = false,
smol: bool = false,
graph: ?*bun.StandaloneModuleGraph = null,
+ debugger: bun.CLI.Command.Debugger = .{ .unspecified = {} },
};
pub fn init(opts: Options) !*VirtualMachine {
@@ -1023,9 +1132,32 @@ pub const VirtualMachine = struct {
source_code_printer.?.ctx.append_null_byte = false;
}
+ vm.configureDebugger(opts.debugger);
+
return vm;
}
+ fn configureDebugger(this: *VirtualMachine, debugger: bun.CLI.Command.Debugger) void {
+ switch (debugger) {
+ .unspecified => {},
+ .enable => {
+ this.debugger = Debugger{};
+ },
+ .path_or_port => {
+ this.debugger = Debugger{
+ .path_or_port = debugger.path_or_port,
+ };
+ },
+ }
+
+ if (debugger != .unspecified) {
+ this.bundler.options.minify_identifiers = false;
+ this.bundler.options.minify_syntax = false;
+ this.bundler.options.minify_whitespace = false;
+ this.bundler.options.debugger = true;
+ }
+ }
+
pub fn initWorker(
worker: *WebWorker,
opts: Options,
@@ -1126,11 +1258,6 @@ pub const VirtualMachine = struct {
return vm;
}
- // dynamic import
- // pub fn import(global: *JSGlobalObject, specifier: ZigString, source: ZigString) callconv(.C) ErrorableZigString {
-
- // }
-
pub threadlocal var source_code_printer: ?*js_printer.BufferPrinter = null;
pub fn clearRefString(_: *anyopaque, ref_string: *JSC.RefString) void {
@@ -1771,6 +1898,10 @@ pub const VirtualMachine = struct {
var promise: *JSInternalPromise = undefined;
+ if (this.debugger != null) {
+ try Debugger.create(this, this.global);
+ }
+
if (!this.bundler.options.disable_transpilation) {
{
this.is_in_preload = true;
diff --git a/src/bun.js/node/node_fs.zig b/src/bun.js/node/node_fs.zig
index fbd58b3a1..274bc54a7 100644
--- a/src/bun.js/node/node_fs.zig
+++ b/src/bun.js/node/node_fs.zig
@@ -63,6 +63,7 @@ pub const AsyncReaddirTask = struct {
result: JSC.Maybe(Return.Readdir),
ref: JSC.PollRef = .{},
arena: bun.ArenaAllocator,
+ tracker: JSC.AsyncTaskTracker,
pub fn create(globalObject: *JSC.JSGlobalObject, readdir_args: Arguments.Readdir, vm: *JSC.VirtualMachine, arena: bun.ArenaAllocator) JSC.JSValue {
var task = bun.default_allocator.create(AsyncReaddirTask) catch @panic("out of memory");
@@ -72,10 +73,11 @@ pub const AsyncReaddirTask = struct {
.result = undefined,
.globalObject = globalObject,
.arena = arena,
+ .tracker = JSC.AsyncTaskTracker.init(vm),
};
task.ref.ref(vm);
task.args.path.toThreadSafe();
-
+ task.tracker.didSchedule(globalObject);
JSC.WorkPool.schedule(&task.task);
return task.promise.value();
@@ -92,6 +94,7 @@ pub const AsyncReaddirTask = struct {
fn runFromJSThread(this: *AsyncReaddirTask) void {
var globalObject = this.globalObject;
+
var success = @as(JSC.Maybe(Return.Readdir).Tag, this.result) == .result;
const result = switch (this.result) {
.err => |err| err.toJSC(globalObject),
@@ -111,7 +114,11 @@ pub const AsyncReaddirTask = struct {
var promise = this.promise.get();
promise_value.ensureStillAlive();
+ const tracker = this.tracker;
this.deinit();
+
+ tracker.willDispatch(globalObject);
+ defer tracker.didDispatch(globalObject);
switch (success) {
false => {
promise.reject(globalObject, result);
@@ -140,6 +147,7 @@ pub const AsyncStatTask = struct {
ref: JSC.PollRef = .{},
is_lstat: bool = false,
arena: bun.ArenaAllocator,
+ tracker: JSC.AsyncTaskTracker,
pub fn create(
globalObject: *JSC.JSGlobalObject,
@@ -155,10 +163,12 @@ pub const AsyncStatTask = struct {
.result = undefined,
.globalObject = globalObject,
.is_lstat = is_lstat,
+ .tracker = JSC.AsyncTaskTracker.init(vm),
.arena = arena,
};
task.ref.ref(vm);
task.args.path.toThreadSafe();
+ task.tracker.didSchedule(globalObject);
JSC.WorkPool.schedule(&task.task);
@@ -198,6 +208,10 @@ pub const AsyncStatTask = struct {
var promise = this.promise.get();
promise_value.ensureStillAlive();
+ const tracker = this.tracker;
+ tracker.willDispatch(globalObject);
+ defer tracker.didDispatch(globalObject);
+
this.deinit();
switch (success) {
false => {
@@ -226,6 +240,7 @@ pub const AsyncRealpathTask = struct {
result: JSC.Maybe(Return.Realpath),
ref: JSC.PollRef = .{},
arena: bun.ArenaAllocator,
+ tracker: JSC.AsyncTaskTracker,
pub fn create(
globalObject: *JSC.JSGlobalObject,
@@ -240,10 +255,11 @@ pub const AsyncRealpathTask = struct {
.result = undefined,
.globalObject = globalObject,
.arena = arena,
+ .tracker = JSC.AsyncTaskTracker.init(vm),
};
task.ref.ref(vm);
task.args.path.toThreadSafe();
-
+ task.tracker.didSchedule(globalObject);
JSC.WorkPool.schedule(&task.task);
return task.promise.value();
@@ -283,6 +299,10 @@ pub const AsyncRealpathTask = struct {
var promise = this.promise.get();
promise_value.ensureStillAlive();
+ const tracker = this.tracker;
+ tracker.willDispatch(globalObject);
+ defer tracker.didDispatch(globalObject);
+
this.deinit();
switch (success) {
false => {
@@ -315,6 +335,7 @@ pub const AsyncReadFileTask = struct {
result: JSC.Maybe(Return.ReadFile),
ref: JSC.PollRef = .{},
arena: bun.ArenaAllocator,
+ tracker: JSC.AsyncTaskTracker,
pub fn create(
globalObject: *JSC.JSGlobalObject,
@@ -329,10 +350,11 @@ pub const AsyncReadFileTask = struct {
.result = undefined,
.globalObject = globalObject,
.arena = arena,
+ .tracker = JSC.AsyncTaskTracker.init(vm),
};
task.ref.ref(vm);
task.args.path.toThreadSafe();
-
+ task.tracker.didSchedule(globalObject);
JSC.WorkPool.schedule(&task.task);
return task.promise.value();
@@ -349,6 +371,7 @@ pub const AsyncReadFileTask = struct {
fn runFromJSThread(this: *AsyncReadFileTask) void {
var globalObject = this.globalObject;
+
var success = @as(JSC.Maybe(Return.ReadFile).Tag, this.result) == .result;
const result = switch (this.result) {
.err => |err| err.toJSC(globalObject),
@@ -368,6 +391,10 @@ pub const AsyncReadFileTask = struct {
var promise = this.promise.get();
promise_value.ensureStillAlive();
+ const tracker = this.tracker;
+ tracker.willDispatch(globalObject);
+ defer tracker.didDispatch(globalObject);
+
this.deinit();
switch (success) {
false => {
diff --git a/src/bun.js/scripts/generate-jssink.js b/src/bun.js/scripts/generate-jssink.js
index dc8a117b3..ef60efbba 100644
--- a/src/bun.js/scripts/generate-jssink.js
+++ b/src/bun.js/scripts/generate-jssink.js
@@ -644,7 +644,7 @@ void JS${controllerName}::detach() {
JSC::MarkedArgumentBuffer arguments;
arguments.append(readableStream);
arguments.append(jsUndefined());
- JSC::call(globalObject, onClose, callData, JSC::jsUndefined(), arguments);
+ call(globalObject, onClose, callData, JSC::jsUndefined(), arguments);
}
m_weakReadableStream.clear();
diff --git a/src/bun.js/webcore/response.zig b/src/bun.js/webcore/response.zig
index 0d5691a3f..5d56c59d9 100644
--- a/src/bun.js/webcore/response.zig
+++ b/src/bun.js/webcore/response.zig
@@ -637,6 +637,8 @@ pub const Fetch = struct {
// Custom Hostname
hostname: ?[]u8 = null,
+ tracker: JSC.AsyncTaskTracker,
+
pub const HTTPRequestBody = union(enum) {
AnyBlob: AnyBlob,
Sendfile: HTTPClient.Sendfile,
@@ -725,7 +727,9 @@ pub const Fetch = struct {
}
const promise = promise_value.asAnyPromise().?;
-
+ const tracker = this.tracker;
+ tracker.willDispatch(globalThis);
+ defer tracker.didDispatch(globalThis);
const success = this.result.isSuccess();
const result = switch (success) {
true => this.onResolve(),
@@ -856,8 +860,11 @@ pub const Fetch = struct {
.url_proxy_buffer = fetch_options.url_proxy_buffer,
.signal = fetch_options.signal,
.hostname = fetch_options.hostname,
+ .tracker = JSC.AsyncTaskTracker.init(jsc_vm),
};
+ fetch_tasklet.tracker.didSchedule(globalThis);
+
if (fetch_tasklet.request_body.store()) |store| {
store.ref();
}
@@ -918,6 +925,7 @@ pub const Fetch = struct {
this.abort_reason = reason;
reason.protect();
this.aborted.store(true, .Monotonic);
+ this.tracker.didCancel(this.global_this);
if (this.http != null) {
HTTPClient.http_thread.scheduleShutdown(this.http.?);