diff options
author | 2023-08-16 19:40:20 -0700 | |
---|---|---|
committer | 2023-08-16 19:40:20 -0700 | |
commit | 0486cea35a80be97ba43f41a29ce55f0d3a8eb01 (patch) | |
tree | 2a6335ff465826b3e20959c5751c89ba793becca /src/bun.js | |
parent | 2634c64aa32fec00073bd0a776e5ac67ad6aa6e5 (diff) | |
download | bun-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')
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.?); |