diff --git a/src/Watcher.zig b/src/Watcher.zig index 7d0d88669c..c810af324a 100644 --- a/src/Watcher.zig +++ b/src/Watcher.zig @@ -307,7 +307,7 @@ fn appendFileAssumeCapacity( loader: options.Loader, parent_hash: HashType, package_json: ?*PackageJSON, - comptime copy_file_path: bool, + comptime clone_file_path: bool, ) bun.JSC.Maybe(void) { if (comptime Environment.isWindows) { // on windows we can only watch items that are in the directory tree of the top level dir @@ -320,7 +320,7 @@ fn appendFileAssumeCapacity( const watchlist_id = this.watchlist.len; - const file_path_: string = if (comptime copy_file_path) + const file_path_: string = if (comptime clone_file_path) bun.asByteSlice(this.allocator.dupeZ(u8, file_path) catch bun.outOfMemory()) else file_path; @@ -383,13 +383,12 @@ fn appendFileAssumeCapacity( this.watchlist.appendAssumeCapacity(item); return .{ .result = {} }; } - fn appendDirectoryAssumeCapacity( this: *Watcher, stored_fd: bun.FileDescriptor, file_path: string, hash: HashType, - comptime copy_file_path: bool, + comptime clone_file_path: bool, ) bun.JSC.Maybe(WatchItemIndex) { if (comptime Environment.isWindows) { // on windows we can only watch items that are in the directory tree of the top level dir @@ -408,13 +407,13 @@ fn appendDirectoryAssumeCapacity( }; }; - const parent_hash = getHash(bun.fs.PathName.init(file_path).dirWithTrailingSlash()); - - const file_path_: string = if (comptime copy_file_path) + const file_path_: string = if (comptime clone_file_path) bun.asByteSlice(this.allocator.dupeZ(u8, file_path) catch bun.outOfMemory()) else file_path; + const parent_hash = getHash(bun.fs.PathName.init(file_path_).dirWithTrailingSlash()); + const watchlist_id = this.watchlist.len; var item = WatchItem{ @@ -464,13 +463,21 @@ fn appendDirectoryAssumeCapacity( null, ); } else if (Environment.isLinux) { - const file_path_to_use_ = std.mem.trimRight(u8, file_path_, "/"); - var buf: bun.PathBuffer = undefined; - bun.copy(u8, &buf, file_path_to_use_); - buf[file_path_to_use_.len] = 0; - const slice: [:0]u8 = buf[0..file_path_to_use_.len :0]; - item.eventlist_index = switch (this.platform.watchDir(slice)) { - .err => |err| return .{ .err = err }, + const buf = bun.PathBufferPool.get(); + defer { + bun.PathBufferPool.put(buf); + } + const path: [:0]const u8 = if (clone_file_path and file_path_.len > 0 and file_path_[file_path_.len - 1] == 0) + file_path_[0 .. file_path_.len - 1 :0] + else brk: { + const trailing_slash = if (file_path_.len > 1) std.mem.trimRight(u8, file_path_, &.{ 0, '/' }) else file_path_; + @memcpy(buf[0..trailing_slash.len], trailing_slash); + buf[trailing_slash.len] = 0; + break :brk buf[0..trailing_slash.len :0]; + }; + + item.eventlist_index = switch (this.platform.watchDir(path)) { + .err => |err| return .{ .err = err.withPath(file_path) }, .result => |r| r, }; } @@ -491,7 +498,7 @@ pub fn appendFileMaybeLock( loader: options.Loader, dir_fd: bun.FileDescriptor, package_json: ?*PackageJSON, - comptime copy_file_path: bool, + comptime clone_file_path: bool, comptime lock: bool, ) bun.JSC.Maybe(void) { if (comptime lock) this.mutex.lock(); @@ -524,8 +531,8 @@ pub fn appendFileMaybeLock( this.watchlist.ensureUnusedCapacity(this.allocator, 1 + @as(usize, @intCast(@intFromBool(parent_watch_item == null)))) catch bun.outOfMemory(); if (autowatch_parent_dir) { - parent_watch_item = parent_watch_item orelse switch (this.appendDirectoryAssumeCapacity(dir_fd, parent_dir, parent_dir_hash, copy_file_path)) { - .err => |err| return .{ .err = err }, + parent_watch_item = parent_watch_item orelse switch (this.appendDirectoryAssumeCapacity(dir_fd, parent_dir, parent_dir_hash, clone_file_path)) { + .err => |err| return .{ .err = err.withPath(parent_dir) }, .result => |r| r, }; } @@ -537,9 +544,9 @@ pub fn appendFileMaybeLock( loader, parent_dir_hash, package_json, - copy_file_path, + clone_file_path, )) { - .err => |err| return .{ .err = err }, + .err => |err| return .{ .err = err.withPath(file_path) }, .result => {}, } @@ -568,9 +575,9 @@ pub fn appendFile( loader: options.Loader, dir_fd: bun.FileDescriptor, package_json: ?*PackageJSON, - comptime copy_file_path: bool, + comptime clone_file_path: bool, ) bun.JSC.Maybe(void) { - return appendFileMaybeLock(this, fd, file_path, hash, loader, dir_fd, package_json, copy_file_path, true); + return appendFileMaybeLock(this, fd, file_path, hash, loader, dir_fd, package_json, clone_file_path, true); } pub fn addDirectory( @@ -578,7 +585,7 @@ pub fn addDirectory( fd: bun.FileDescriptor, file_path: string, hash: HashType, - comptime copy_file_path: bool, + comptime clone_file_path: bool, ) bun.JSC.Maybe(WatchItemIndex) { this.mutex.lock(); defer this.mutex.unlock(); @@ -589,7 +596,7 @@ pub fn addDirectory( this.watchlist.ensureUnusedCapacity(this.allocator, 1) catch bun.outOfMemory(); - return this.appendDirectoryAssumeCapacity(fd, file_path, hash, copy_file_path); + return this.appendDirectoryAssumeCapacity(fd, file_path, hash, clone_file_path); } pub fn addFile( @@ -600,7 +607,7 @@ pub fn addFile( loader: options.Loader, dir_fd: bun.FileDescriptor, package_json: ?*PackageJSON, - comptime copy_file_path: bool, + comptime clone_file_path: bool, ) bun.JSC.Maybe(void) { // This must lock due to concurrent transpiler this.mutex.lock(); @@ -617,7 +624,7 @@ pub fn addFile( return .{ .result = {} }; } - return this.appendFileMaybeLock(fd, file_path, hash, loader, dir_fd, package_json, copy_file_path, false); + return this.appendFileMaybeLock(fd, file_path, hash, loader, dir_fd, package_json, clone_file_path, false); } pub fn indexOf(this: *Watcher, hash: HashType) ?u32 { diff --git a/src/bun.js/api/bun/h2_frame_parser.zig b/src/bun.js/api/bun/h2_frame_parser.zig index ec5cac2639..bc9c6d0857 100644 --- a/src/bun.js/api/bun/h2_frame_parser.zig +++ b/src/bun.js/api/bun/h2_frame_parser.zig @@ -605,7 +605,7 @@ const Handlers = struct { return globalObject.throwInvalidArguments("Expected \"{s}\" callback to be a function", .{pair[1]}); } - @field(handlers, pair.@"0") = callback_value; + @field(handlers, pair.@"0") = callback_value.withAsyncContextIfNeeded(globalObject); } } @@ -614,7 +614,7 @@ const Handlers = struct { return globalObject.throwInvalidArguments("Expected \"error\" callback to be a function", .{}); } - handlers.onError = callback_value; + handlers.onError = callback_value.withAsyncContextIfNeeded(globalObject); } // onWrite is required for duplex support or if more than 1 parser is attached to the same socket (unliked) diff --git a/src/bun.js/api/bun/socket.zig b/src/bun.js/api/bun/socket.zig index d6ce7ac303..09d9f7f8a7 100644 --- a/src/bun.js/api/bun/socket.zig +++ b/src/bun.js/api/bun/socket.zig @@ -1362,6 +1362,7 @@ pub fn NewSocket(comptime ssl: bool) type { var prev_handlers = this.handlers; prev_handlers.unprotect(); this.handlers.* = handlers; // TODO: this is a memory leak + this.handlers.withAsyncContextIfNeeded(globalObject); this.handlers.protect(); return .js_undefined; @@ -1404,7 +1405,7 @@ pub fn NewSocket(comptime ssl: bool) type { return .zero; } - const handlers = try Handlers.fromJS(globalObject, socket_obj, this.handlers.is_server); + var handlers = try Handlers.fromJS(globalObject, socket_obj, this.handlers.is_server); if (globalObject.hasException()) { return .zero; @@ -1459,6 +1460,7 @@ pub fn NewSocket(comptime ssl: bool) type { const ext_size = @sizeOf(WrappedSocket); var handlers_ptr = bun.default_allocator.create(Handlers) catch bun.outOfMemory(); + handlers.withAsyncContextIfNeeded(globalObject); handlers_ptr.* = handlers; handlers_ptr.protect(); var tls = bun.new(TLSSocket, .{ @@ -1980,6 +1982,7 @@ pub fn jsUpgradeDuplexToTLS(globalObject: *JSC.JSGlobalObject, callframe: *JSC.C var handlers_ptr = handlers.vm.allocator.create(Handlers) catch bun.outOfMemory(); handlers_ptr.* = handlers; handlers_ptr.is_server = is_server; + handlers_ptr.withAsyncContextIfNeeded(globalObject); handlers_ptr.protect(); var tls = bun.new(TLSSocket, .{ .ref_count = .init(), diff --git a/src/bun.js/api/bun/socket/Handlers.zig b/src/bun.js/api/bun/socket/Handlers.zig index b856b5dd75..7ff2a63e70 100644 --- a/src/bun.js/api/bun/socket/Handlers.zig +++ b/src/bun.js/api/bun/socket/Handlers.zig @@ -172,6 +172,25 @@ pub fn unprotect(this: *Handlers) void { this.onHandshake.unprotect(); } +pub fn withAsyncContextIfNeeded(this: *Handlers, globalObject: *JSC.JSGlobalObject) void { + inline for (.{ + "onOpen", + "onClose", + "onData", + "onWritable", + "onTimeout", + "onConnectError", + "onEnd", + "onError", + "onHandshake", + }) |field| { + const value = @field(this, field); + if (value != .zero) { + @field(this, field) = value.withAsyncContextIfNeeded(globalObject); + } + } +} + pub fn protect(this: *Handlers) void { if (comptime Environment.isDebug) { this.protection_count += 1; @@ -343,6 +362,7 @@ pub const SocketConfig = struct { default_data = default_data_value; } + handlers.withAsyncContextIfNeeded(globalObject); handlers.protect(); return SocketConfig{ diff --git a/src/bun.js/api/bun/socket/Listener.zig b/src/bun.js/api/bun/socket/Listener.zig index da77947e32..2be8e5791d 100644 --- a/src/bun.js/api/bun/socket/Listener.zig +++ b/src/bun.js/api/bun/socket/Listener.zig @@ -90,10 +90,11 @@ pub fn reload(this: *Listener, globalObject: *JSC.JSGlobalObject, callframe: *JS return globalObject.throw("Expected \"socket\" object", .{}); }; - const handlers = try Handlers.fromJS(globalObject, socket_obj, this.handlers.is_server); + var handlers = try Handlers.fromJS(globalObject, socket_obj, this.handlers.is_server); var prev_handlers = &this.handlers; prev_handlers.unprotect(); + handlers.withAsyncContextIfNeeded(globalObject); this.handlers = handlers; // TODO: this is a memory leak this.handlers.protect(); diff --git a/src/bun.js/api/bun/udp_socket.zig b/src/bun.js/api/bun/udp_socket.zig index 6e11447454..d4ae3e6d8a 100644 --- a/src/bun.js/api/bun/udp_socket.zig +++ b/src/bun.js/api/bun/udp_socket.zig @@ -199,7 +199,7 @@ pub const UDPSocketConfig = struct { if (!value.isCell() or !value.isCallable()) { return globalThis.throwInvalidArguments("Expected \"socket.{s}\" to be a function", .{handler.@"0"}); } - @field(config, handler.@"1") = value; + @field(config, handler.@"1") = value.withAsyncContextIfNeeded(globalThis); } } } diff --git a/src/bun.js/api/server/WebSocketServerContext.zig b/src/bun.js/api/server/WebSocketServerContext.zig index bb4e9ba231..60b3dfc643 100644 --- a/src/bun.js/api/server/WebSocketServerContext.zig +++ b/src/bun.js/api/server/WebSocketServerContext.zig @@ -50,71 +50,27 @@ pub const Handler = struct { var valid = false; - if (try object.getTruthyComptime(globalObject, "message")) |message_| { - if (!message_.isCallable()) { - return globalObject.throwInvalidArguments("websocket expects a function for the message option", .{}); + inline for (.{ + .{ "error", "onError" }, + .{ "message", "onMessage" }, + .{ "open", "onOpen" }, + .{ "close", "onClose" }, + .{ "drain", "onDrain" }, + .{ "ping", "onPing" }, + .{ "pong", "onPong" }, + }, 0..) |pair, i| { + if (try object.getTruthy(globalObject, pair[0])) |value| { + if (!value.isCell() or !value.isCallable()) { + return globalObject.throwInvalidArguments("websocket expects a function for the '{s}' option", .{pair[0]}); + } + const cb = value.withAsyncContextIfNeeded(globalObject); + @field(handler, pair[1]) = cb; + cb.ensureStillAlive(); + if (i > 0) { + // anything other than "error" is considered valid. + valid = true; + } } - const message = message_.withAsyncContextIfNeeded(globalObject); - handler.onMessage = message; - message.ensureStillAlive(); - valid = true; - } - - if (try object.getTruthy(globalObject, "open")) |open_| { - if (!open_.isCallable()) { - return globalObject.throwInvalidArguments("websocket expects a function for the open option", .{}); - } - const open = open_.withAsyncContextIfNeeded(globalObject); - handler.onOpen = open; - open.ensureStillAlive(); - valid = true; - } - - if (try object.getTruthy(globalObject, "close")) |close_| { - if (!close_.isCallable()) { - return globalObject.throwInvalidArguments("websocket expects a function for the close option", .{}); - } - const close = close_.withAsyncContextIfNeeded(globalObject); - handler.onClose = close; - close.ensureStillAlive(); - valid = true; - } - - if (try object.getTruthy(globalObject, "drain")) |drain_| { - if (!drain_.isCallable()) { - return globalObject.throwInvalidArguments("websocket expects a function for the drain option", .{}); - } - const drain = drain_.withAsyncContextIfNeeded(globalObject); - handler.onDrain = drain; - drain.ensureStillAlive(); - valid = true; - } - - if (try object.getTruthy(globalObject, "onError")) |onError_| { - if (!onError_.isCallable()) { - return globalObject.throwInvalidArguments("websocket expects a function for the onError option", .{}); - } - const onError = onError_.withAsyncContextIfNeeded(globalObject); - handler.onError = onError; - onError.ensureStillAlive(); - } - - if (try object.getTruthy(globalObject, "ping")) |cb| { - if (!cb.isCallable()) { - return globalObject.throwInvalidArguments("websocket expects a function for the ping option", .{}); - } - handler.onPing = cb; - cb.ensureStillAlive(); - valid = true; - } - - if (try object.getTruthy(globalObject, "pong")) |cb| { - if (!cb.isCallable()) { - return globalObject.throwInvalidArguments("websocket expects a function for the pong option", .{}); - } - handler.onPong = cb; - cb.ensureStillAlive(); - valid = true; } if (valid) diff --git a/src/bun.js/bindings/AsyncContextFrame.cpp b/src/bun.js/bindings/AsyncContextFrame.cpp index 3fcca2555b..ce0b503d4d 100644 --- a/src/bun.js/bindings/AsyncContextFrame.cpp +++ b/src/bun.js/bindings/AsyncContextFrame.cpp @@ -120,4 +120,10 @@ JSValue AsyncContextFrame::profiledCall(JSGlobalObject* global, JSValue function return AsyncContextFrame::call(global, functionObject, thisValue, args, returnedException); } +JSC::JSValue AsyncContextFrame::run(JSGlobalObject* global, JSValue functionObject, JSValue thisValue, const ArgList& args) +{ + ASSERT(global->isAsyncContextTrackingEnabled()); + + ASYNCCONTEXTFRAME_CALL_IMPL(global, ProfilingReason::API, functionObject, JSC::getCallData(functionObject), thisValue, args); +} #undef ASYNCCONTEXTFRAME_CALL_IMPL diff --git a/src/bun.js/bindings/AsyncContextFrame.h b/src/bun.js/bindings/AsyncContextFrame.h index 2daa57eb0a..164550cdd0 100644 --- a/src/bun.js/bindings/AsyncContextFrame.h +++ b/src/bun.js/bindings/AsyncContextFrame.h @@ -37,6 +37,13 @@ public: mutable JSC::WriteBarrier callback; mutable JSC::WriteBarrier context; + /** + * When you have a **specific** AsyncContextFrame to run the function in, use this + * + * Usually, you do not want to use this. Usually, you want to use `call` or `profiledCall`. + */ + JSC::JSValue run(JSC::JSGlobalObject* globalObject, JSC::JSValue functionObject, JSC::JSValue thisValue, const JSC::ArgList& args); + template static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) { diff --git a/src/bun.js/bindings/BunProcess.cpp b/src/bun.js/bindings/BunProcess.cpp index b3c316c09e..13cc33c163 100644 --- a/src/bun.js/bindings/BunProcess.cpp +++ b/src/bun.js/bindings/BunProcess.cpp @@ -3387,7 +3387,12 @@ void Process::queueNextTick(JSC::JSGlobalObject* globalObject, const ArgList& ar ASSERT(!args.isEmpty()); JSObject* nextTickFn = this->m_nextTickFunction.get(); - AsyncContextFrame::call(globalObject, nextTickFn, jsUndefined(), args); + auto* frame = jsDynamicCast(args.at(0)); + if (frame) { + frame->run(globalObject, jsUndefined(), nextTickFn, args); + } else { + AsyncContextFrame::call(globalObject, nextTickFn, jsUndefined(), args); + } RELEASE_AND_RETURN(scope, void()); } @@ -3416,7 +3421,7 @@ void Process::queueNextTick(JSC::JSGlobalObject* globalObject, JSValue value, JS template void Process::queueNextTick(JSC::JSGlobalObject* globalObject, JSValue func, const JSValue (&args)[NumArgs]) { - ASSERT_WITH_MESSAGE(func.isCallable(), "Must be a function for us to call"); + ASSERT_WITH_MESSAGE(func.isCallable() || func.inherits(), "Must be a function for us to call"); MarkedArgumentBuffer argsBuffer; argsBuffer.ensureCapacity(NumArgs + 1); if (!func.isEmpty()) { diff --git a/src/bun.js/bindings/JSValue.zig b/src/bun.js/bindings/JSValue.zig index 4a100cd5ee..0ff210cc59 100644 --- a/src/bun.js/bindings/JSValue.zig +++ b/src/bun.js/bindings/JSValue.zig @@ -290,9 +290,6 @@ pub const JSValue = enum(i64) { extern fn Bun__Process__queueNextTick2(*JSGlobalObject, func: JSValue, JSValue, JSValue) void; pub inline fn callNextTick(function: JSValue, global: *JSGlobalObject, args: anytype) void { - if (Environment.isDebug) { - bun.assert(function.isCallable()); - } switch (comptime bun.len(@as(@TypeOf(args), undefined))) { 1 => Bun__Process__queueNextTick1(@ptrCast(global), function, args[0]), 2 => Bun__Process__queueNextTick2(@ptrCast(global), function, args[0], args[1]), diff --git a/src/bun.js/node/node_crypto_binding.zig b/src/bun.js/node/node_crypto_binding.zig index 4931d46ed1..1d45de8787 100644 --- a/src/bun.js/node/node_crypto_binding.zig +++ b/src/bun.js/node/node_crypto_binding.zig @@ -48,7 +48,7 @@ fn ExternCryptoJob(comptime name: []const u8) type { } pub fn createAndSchedule(global: *JSGlobalObject, ctx: *Ctx, callback: JSValue) callconv(.c) void { - var job = create(global, ctx, callback); + var job = create(global, ctx, callback.withAsyncContextIfNeeded(global)); job.schedule(); } @@ -142,7 +142,7 @@ fn CryptoJob(comptime Ctx: type) type { }, .any_task = undefined, .ctx = ctx.*, - .callback = .create(callback, global), + .callback = .create(callback.withAsyncContextIfNeeded(global), global), }); errdefer bun.destroy(job); try job.ctx.init(global); @@ -266,6 +266,8 @@ const random = struct { const res = std.crypto.random.intRangeLessThan(i64, min, max); if (!callback.isUndefined()) { + callback = callback.withAsyncContextIfNeeded(global); + callback.callNextTick(global, [2]JSValue{ .js_undefined, JSValue.jsNumber(res) }); return .js_undefined; } diff --git a/src/bun.js/node/node_fs_watcher.zig b/src/bun.js/node/node_fs_watcher.zig index 0f9417e421..f886f8ea56 100644 --- a/src/bun.js/node/node_fs_watcher.zig +++ b/src/bun.js/node/node_fs_watcher.zig @@ -167,6 +167,7 @@ pub const FSWatcher = struct { pub fn dupe(event: Event) !Event { return switch (event) { inline .rename, .change => |path, t| @unionInit(Event, @tagName(t), try bun.default_allocator.dupe(u8, path)), + .@"error" => |err| .{ .@"error" = try err.clone(bun.default_allocator) }, inline else => |value, t| @unionInit(Event, @tagName(t), value), }; } @@ -177,6 +178,7 @@ pub const FSWatcher = struct { else => bun.default_allocator.free(path.*), .windows => path.deinit(), }, + .@"error" => |*err| err.deinit(), else => {}, } } @@ -641,33 +643,35 @@ pub const FSWatcher = struct { } pub fn init(args: Arguments) bun.JSC.Maybe(*FSWatcher) { - var buf: bun.PathBuffer = undefined; - var slice = args.path.slice(); - if (bun.strings.startsWith(slice, "file://")) { - slice = slice[6..]; - } + const joined_buf = bun.PathBufferPool.get(); + defer bun.PathBufferPool.put(joined_buf); + const file_path: [:0]const u8 = brk: { + const buf = bun.PathBufferPool.get(); + defer bun.PathBufferPool.put(buf); + var slice = args.path.slice(); + if (bun.strings.startsWith(slice, "file://")) { + slice = slice[6..]; + } - var parts = [_]string{ - slice, + const cwd = switch (bun.sys.getcwd(buf)) { + .result => |r| r, + .err => |err| return .{ .err = err }, + }; + buf[cwd.len] = std.fs.path.sep; + + const parts = &[_]string{ + cwd, + slice, + }; + + break :brk Path.joinAbsStringBufZ( + buf[0 .. cwd.len + 1], + joined_buf, + parts, + .auto, + ); }; - const cwd = switch (bun.sys.getcwd(&buf)) { - .result => |r| r, - .err => |err| return .{ .err = err }, - }; - buf[cwd.len] = std.fs.path.sep; - - var joined_buf: bun.PathBuffer = undefined; - const file_path = Path.joinAbsStringBuf( - buf[0 .. cwd.len + 1], - &joined_buf, - &parts, - .auto, - ); - - joined_buf[file_path.len] = 0; - const file_path_z = joined_buf[0..file_path.len :0]; - const vm = args.global_this.bunVM(); const ctx = bun.new(FSWatcher, .{ @@ -689,7 +693,7 @@ pub const FSWatcher = struct { ctx.current_task.ctx = ctx; ctx.path_watcher = if (args.signal == null or !args.signal.?.aborted()) - switch (PathWatcher.watch(vm, file_path_z, args.recursive, onPathUpdate, onUpdateEnd, bun.cast(*anyopaque, ctx))) { + switch (PathWatcher.watch(vm, file_path, args.recursive, onPathUpdate, onUpdateEnd, bun.cast(*anyopaque, ctx))) { .result => |r| r, .err => |err| { ctx.deinit(); @@ -702,7 +706,7 @@ pub const FSWatcher = struct { } else null; - ctx.initJS(args.listener); + ctx.initJS(args.listener.withAsyncContextIfNeeded(args.global_this)); return .{ .result = ctx }; } }; diff --git a/src/bun.js/node/node_zlib_binding.zig b/src/bun.js/node/node_zlib_binding.zig index ff2dfc8e10..5093700de8 100644 --- a/src/bun.js/node/node_zlib_binding.zig +++ b/src/bun.js/node/node_zlib_binding.zig @@ -247,7 +247,7 @@ pub fn CompressionStream(comptime T: type) type { pub fn setOnError(_: *T, this_value: JSC.JSValue, globalObject: *JSC.JSGlobalObject, value: JSC.JSValue) void { if (value.isFunction()) { - T.js.errorCallbackSetCached(this_value, globalObject, value); + T.js.errorCallbackSetCached(this_value, globalObject, value.withAsyncContextIfNeeded(globalObject)); } } diff --git a/src/bun.js/node/path_watcher.zig b/src/bun.js/node/path_watcher.zig index 6a0f33144a..1f24866e27 100644 --- a/src/bun.js/node/path_watcher.zig +++ b/src/bun.js/node/path_watcher.zig @@ -497,7 +497,7 @@ pub const PathWatcherManager = struct { if (watcher.recursive and !watcher.isClosed()) { // this may trigger another thread with is desired when available to watch long trees switch (manager._addDirectory(watcher, child_path)) { - .err => |err| return .{ .err = err }, + .err => |err| return .{ .err = err.withPath(child_path.path) }, .result => {}, } } @@ -537,7 +537,7 @@ pub const PathWatcherManager = struct { fn _addDirectory(this: *PathWatcherManager, watcher: *PathWatcher, path: PathInfo) bun.JSC.Maybe(void) { const fd = path.fd; switch (this.main_watcher.addDirectory(fd, path.path, path.hash, false)) { - .err => |err| return .{ .err = err }, + .err => |err| return .{ .err = err.withPath(path.path) }, .result => {}, } @@ -878,7 +878,9 @@ pub const PathWatcher = struct { } this.needs_flush = true; - if (this.isClosed()) return; + if (this.isClosed()) { + return; + } this.callback(this.ctx, event, is_file); } diff --git a/src/bun.js/node/zlib/NativeBrotli.zig b/src/bun.js/node/zlib/NativeBrotli.zig index 71ea321d95..212007fb55 100644 --- a/src/bun.js/node/zlib/NativeBrotli.zig +++ b/src/bun.js/node/zlib/NativeBrotli.zig @@ -83,7 +83,7 @@ pub fn init(this: *@This(), globalThis: *JSC.JSGlobalObject, callframe: *JSC.Cal this.write_result = writeResult; - js.writeCallbackSetCached(this_value, globalThis, writeCallback); + js.writeCallbackSetCached(this_value, globalThis, writeCallback.withAsyncContextIfNeeded(globalThis)); var err = this.stream.init(); if (err.isError()) { diff --git a/src/bun.js/node/zlib/NativeZlib.zig b/src/bun.js/node/zlib/NativeZlib.zig index 945136e59a..5aaf817b2b 100644 --- a/src/bun.js/node/zlib/NativeZlib.zig +++ b/src/bun.js/node/zlib/NativeZlib.zig @@ -84,7 +84,7 @@ pub fn init(this: *@This(), globalThis: *JSC.JSGlobalObject, callframe: *JSC.Cal const dictionary = if (arguments[6].isUndefined()) null else arguments[6].asArrayBuffer(globalThis).?.byteSlice(); this.write_result = writeResult; - js.writeCallbackSetCached(this_value, globalThis, writeCallback); + js.writeCallbackSetCached(this_value, globalThis, writeCallback.withAsyncContextIfNeeded(globalThis)); // Keep the dictionary alive by keeping a reference to it in the JS object. if (dictionary != null) { diff --git a/src/bun.js/node/zlib/NativeZstd.zig b/src/bun.js/node/zlib/NativeZstd.zig index 0d9756bf81..95acc32749 100644 --- a/src/bun.js/node/zlib/NativeZstd.zig +++ b/src/bun.js/node/zlib/NativeZstd.zig @@ -83,7 +83,7 @@ pub fn init(this: *@This(), globalThis: *JSC.JSGlobalObject, callframe: *JSC.Cal this.write_result = writeState.asU32().ptr; const write_js_callback = try validators.validateFunction(globalThis, "processCallback", processCallback_value); - js.writeCallbackSetCached(this_value, globalThis, write_js_callback); + js.writeCallbackSetCached(this_value, globalThis, write_js_callback.withAsyncContextIfNeeded(globalThis)); var pledged_src_size: u64 = std.math.maxInt(u64); if (pledgedSrcSize_value.isNumber()) { diff --git a/test/js/node/async_hooks/AsyncLocalStorage-tracking.test.ts b/test/js/node/async_hooks/AsyncLocalStorage-tracking.test.ts new file mode 100644 index 0000000000..7e77f3141e --- /dev/null +++ b/test/js/node/async_hooks/AsyncLocalStorage-tracking.test.ts @@ -0,0 +1,40 @@ +import { Glob } from "bun"; +import { describe, test } from "bun:test"; +import { bunEnv, bunExe, isASAN, isBroken, isLinux, nodeExe } from "harness"; +import { basename, join } from "path"; + +describe("AsyncLocalStorage passes context to callbacks", () => { + let files = [...new Glob(join(import.meta.dir, "async-context", "async-context-*.js")).scanSync()]; + + let todos = ["async-context-worker_threads-message.js"]; + if (isASAN && isBroken && isLinux) { + todos.push("async-context-dns-resolveTxt.js"); + } + + files = files.filter(file => !todos.includes(basename(file))); + + for (const filepath of files) { + const file = basename(filepath).replaceAll("async-context-", "").replaceAll(".js", ""); + test(file, async () => { + async function run(exe) { + const { exited } = Bun.spawn({ + cmd: [exe, filepath], + stdout: "inherit", + stderr: "inherit", + env: bunEnv, + }); + + if (await exited) { + throw new Error(`${basename(exe)} failed in ${filepath}`); + } + } + + await Promise.all([run(bunExe()), run(nodeExe())]); + }); + } + + for (const filepath of todos) { + const file = basename(filepath).replaceAll("async-context-", "").replaceAll(".js", ""); + test.todo(file); + } +}); diff --git a/test/js/node/async_hooks/async-context/async-context-async-iterator.js b/test/js/node/async_hooks/async-context/async-context-async-iterator.js new file mode 100644 index 0000000000..fe60d2d9e8 --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-async-iterator.js @@ -0,0 +1,25 @@ +const { AsyncLocalStorage } = require("async_hooks"); + +const asyncLocalStorage = new AsyncLocalStorage(); + +// Create an async generator +async function* asyncGenerator() { + yield 1; + yield 2; + yield 3; +} + +asyncLocalStorage.run({ test: "async.iterator" }, async () => { + try { + for await (const value of asyncGenerator()) { + if (asyncLocalStorage.getStore()?.test !== "async.iterator") { + console.error("FAIL: async iterator lost context at value", value); + process.exit(1); + } + } + process.exit(0); + } catch (err) { + console.error("ERROR:", err); + process.exit(1); + } +}); diff --git a/test/js/node/async_hooks/async-context/async-context-child_process-exec.js b/test/js/node/async_hooks/async-context/async-context-child_process-exec.js new file mode 100644 index 0000000000..9ff5acabeb --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-child_process-exec.js @@ -0,0 +1,14 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const { exec } = require("child_process"); + +const asyncLocalStorage = new AsyncLocalStorage(); + +asyncLocalStorage.run({ test: "child_process.exec" }, () => { + exec("echo test", (error, stdout, stderr) => { + if (asyncLocalStorage.getStore()?.test !== "child_process.exec") { + console.error("FAIL: child_process.exec callback lost context"); + process.exit(1); + } + process.exit(0); + }); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-child_process-execFile.js b/test/js/node/async_hooks/async-context/async-context-child_process-execFile.js new file mode 100644 index 0000000000..c3fe10e868 --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-child_process-execFile.js @@ -0,0 +1,14 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const { execFile } = require("child_process"); + +const asyncLocalStorage = new AsyncLocalStorage(); + +asyncLocalStorage.run({ test: "child_process.execFile" }, () => { + execFile("echo", ["test"], (error, stdout, stderr) => { + if (asyncLocalStorage.getStore()?.test !== "child_process.execFile") { + console.error("FAIL: child_process.execFile callback lost context"); + process.exit(1); + } + process.exit(0); + }); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-child_process-spawn-events.js b/test/js/node/async_hooks/async-context/async-context-child_process-spawn-events.js new file mode 100644 index 0000000000..dd7b9ae218 --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-child_process-spawn-events.js @@ -0,0 +1,35 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const { spawn } = require("child_process"); + +const asyncLocalStorage = new AsyncLocalStorage(); +let failed = false; + +asyncLocalStorage.run({ test: "child_process.spawn" }, () => { + const child = spawn("bun", ["-e", "Bun.sleepSync(100)"]); + + child.on("spawn", () => { + if (asyncLocalStorage.getStore()?.test !== "child_process.spawn") { + console.error("FAIL: spawn event lost context"); + failed = true; + } + }); + + child.stdout.on("data", data => { + if (asyncLocalStorage.getStore()?.test !== "child_process.spawn") { + console.error("FAIL: spawn stdout data event lost context"); + failed = true; + } + }); + + child.on("close", code => { + if (asyncLocalStorage.getStore()?.test !== "child_process.spawn") { + console.error("FAIL: spawn close event lost context"); + failed = true; + } + process.exit(failed ? 1 : 0); + }); + + child.on("error", () => { + process.exit(1); + }); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-crypto-cipher.js b/test/js/node/async_hooks/async-context/async-context-crypto-cipher.js new file mode 100644 index 0000000000..ec6608dd83 --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-crypto-cipher.js @@ -0,0 +1,31 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const crypto = require("crypto"); + +const asyncLocalStorage = new AsyncLocalStorage(); + +asyncLocalStorage.run({ test: "crypto.cipher" }, () => { + const algorithm = "aes-256-cbc"; + const key = crypto.randomBytes(32); + const iv = crypto.randomBytes(16); + + const cipher = crypto.createCipheriv(algorithm, key, iv); + + cipher.on("readable", () => { + if (asyncLocalStorage.getStore()?.test !== "crypto.cipher") { + console.error("FAIL: crypto cipher readable event lost context"); + process.exit(1); + } + cipher.read(); + }); + + cipher.on("end", () => { + if (asyncLocalStorage.getStore()?.test !== "crypto.cipher") { + console.error("FAIL: crypto cipher end event lost context"); + process.exit(1); + } + process.exit(0); + }); + + cipher.write("test data"); + cipher.end(); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-crypto-generateKey.js b/test/js/node/async_hooks/async-context/async-context-crypto-generateKey.js new file mode 100644 index 0000000000..a9327bda68 --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-crypto-generateKey.js @@ -0,0 +1,14 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const crypto = require("crypto"); + +const asyncLocalStorage = new AsyncLocalStorage(); + +asyncLocalStorage.run({ test: "crypto.generateKey" }, () => { + crypto.generateKey("hmac", { length: 64 }, (err, key) => { + if (asyncLocalStorage.getStore()?.test !== "crypto.generateKey") { + console.error("FAIL: crypto.generateKey callback lost context"); + process.exit(1); + } + process.exit(0); + }); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-crypto-generateKeyPair.js b/test/js/node/async_hooks/async-context/async-context-crypto-generateKeyPair.js new file mode 100644 index 0000000000..81df0fa59f --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-crypto-generateKeyPair.js @@ -0,0 +1,20 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const crypto = require("crypto"); + +const asyncLocalStorage = new AsyncLocalStorage(); + +asyncLocalStorage.run({ test: "crypto.generateKeyPair" }, () => { + crypto.generateKeyPair( + "rsa", + { + modulusLength: 512, // Small for faster test + }, + (err, publicKey, privateKey) => { + if (asyncLocalStorage.getStore()?.test !== "crypto.generateKeyPair") { + console.error("FAIL: crypto.generateKeyPair callback lost context"); + process.exit(1); + } + process.exit(0); + }, + ); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-crypto-hash.js b/test/js/node/async_hooks/async-context/async-context-crypto-hash.js new file mode 100644 index 0000000000..74ed4f18d7 --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-crypto-hash.js @@ -0,0 +1,19 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const crypto = require("crypto"); + +const asyncLocalStorage = new AsyncLocalStorage(); + +asyncLocalStorage.run({ test: "crypto.hash" }, () => { + const hash = crypto.createHash("sha256"); + hash.update("test data"); + + // Test with callback style if available + setImmediate(() => { + if (asyncLocalStorage.getStore()?.test !== "crypto.hash") { + console.error("FAIL: crypto hash operation lost context"); + process.exit(1); + } + const digest = hash.digest("hex"); + process.exit(0); + }); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-crypto-pbkdf2.js b/test/js/node/async_hooks/async-context/async-context-crypto-pbkdf2.js new file mode 100644 index 0000000000..e308e73db8 --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-crypto-pbkdf2.js @@ -0,0 +1,14 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const crypto = require("crypto"); + +const asyncLocalStorage = new AsyncLocalStorage(); + +asyncLocalStorage.run({ test: "crypto.pbkdf2" }, () => { + crypto.pbkdf2("password", "salt", 100, 32, "sha256", (err, derivedKey) => { + if (asyncLocalStorage.getStore()?.test !== "crypto.pbkdf2") { + console.error("FAIL: crypto.pbkdf2 callback lost context"); + process.exit(1); + } + process.exit(0); + }); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-crypto-randomBytes.js b/test/js/node/async_hooks/async-context/async-context-crypto-randomBytes.js new file mode 100644 index 0000000000..7e52553ce8 --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-crypto-randomBytes.js @@ -0,0 +1,14 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const crypto = require("crypto"); + +const asyncLocalStorage = new AsyncLocalStorage(); + +asyncLocalStorage.run({ test: "crypto.randomBytes" }, () => { + crypto.randomBytes(16, (err, buf) => { + if (asyncLocalStorage.getStore()?.test !== "crypto.randomBytes") { + console.error("FAIL: crypto.randomBytes callback lost context"); + process.exit(1); + } + process.exit(0); + }); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-crypto-randomFill.js b/test/js/node/async_hooks/async-context/async-context-crypto-randomFill.js new file mode 100644 index 0000000000..2e69af78b2 --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-crypto-randomFill.js @@ -0,0 +1,15 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const crypto = require("crypto"); + +const asyncLocalStorage = new AsyncLocalStorage(); + +asyncLocalStorage.run({ test: "crypto.randomFill" }, () => { + const buffer = Buffer.alloc(16); + crypto.randomFill(buffer, (err, buf) => { + if (asyncLocalStorage.getStore()?.test !== "crypto.randomFill") { + console.error("FAIL: crypto.randomFill callback lost context"); + process.exit(1); + } + process.exit(0); + }); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-crypto-randomInt.js b/test/js/node/async_hooks/async-context/async-context-crypto-randomInt.js new file mode 100644 index 0000000000..2053ad7ece --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-crypto-randomInt.js @@ -0,0 +1,14 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const crypto = require("crypto"); + +const asyncLocalStorage = new AsyncLocalStorage(); + +asyncLocalStorage.run({ test: "crypto.randomInt" }, () => { + crypto.randomInt(100, (err, n) => { + if (asyncLocalStorage.getStore()?.test !== "crypto.randomInt") { + console.error("FAIL: crypto.randomInt callback lost context"); + process.exit(1); + } + process.exit(0); + }); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-crypto-randomUUID.js b/test/js/node/async_hooks/async-context/async-context-crypto-randomUUID.js new file mode 100644 index 0000000000..7985977a2d --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-crypto-randomUUID.js @@ -0,0 +1,17 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const crypto = require("crypto"); + +const asyncLocalStorage = new AsyncLocalStorage(); + +// Note: crypto.randomUUID is synchronous in Node.js +// Testing if async wrapper maintains context +asyncLocalStorage.run({ test: "crypto.randomUUID" }, () => { + setImmediate(() => { + const uuid = crypto.randomUUID(); + if (asyncLocalStorage.getStore()?.test !== "crypto.randomUUID") { + console.error("FAIL: crypto.randomUUID async wrapper lost context"); + process.exit(1); + } + process.exit(0); + }); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-crypto-scrypt.js b/test/js/node/async_hooks/async-context/async-context-crypto-scrypt.js new file mode 100644 index 0000000000..efa13aba37 --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-crypto-scrypt.js @@ -0,0 +1,14 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const crypto = require("crypto"); + +const asyncLocalStorage = new AsyncLocalStorage(); + +asyncLocalStorage.run({ test: "crypto.scrypt" }, () => { + crypto.scrypt("password", "salt", 32, (err, derivedKey) => { + if (asyncLocalStorage.getStore()?.test !== "crypto.scrypt") { + console.error("FAIL: crypto.scrypt callback lost context"); + process.exit(1); + } + process.exit(0); + }); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-crypto-sign-verify.js b/test/js/node/async_hooks/async-context/async-context-crypto-sign-verify.js new file mode 100644 index 0000000000..b538b3d269 --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-crypto-sign-verify.js @@ -0,0 +1,34 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const crypto = require("crypto"); + +const asyncLocalStorage = new AsyncLocalStorage(); +let failed = false; + +// First generate a key pair synchronously +const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", { + modulusLength: 512, +}); + +asyncLocalStorage.run({ test: "crypto.sign/verify" }, () => { + // Test sign with stream + const sign = crypto.createSign("SHA256"); + sign.write("test data"); + sign.end(); + + const signature = sign.sign(privateKey); + + // Test verify with stream + const verify = crypto.createVerify("SHA256"); + verify.write("test data"); + verify.end(); + + setImmediate(() => { + if (asyncLocalStorage.getStore()?.test !== "crypto.sign/verify") { + console.error("FAIL: crypto sign/verify lost context"); + failed = true; + } + + const isValid = verify.verify(publicKey, signature); + process.exit(failed ? 1 : 0); + }); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-dgram-events.js b/test/js/node/async_hooks/async-context/async-context-dgram-events.js new file mode 100644 index 0000000000..f66aa3ff4e --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-dgram-events.js @@ -0,0 +1,39 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const dgram = require("dgram"); + +const asyncLocalStorage = new AsyncLocalStorage(); +let failed = false; + +asyncLocalStorage.run({ test: "dgram.events" }, () => { + const server = dgram.createSocket("udp4"); + const client = dgram.createSocket("udp4"); + + server.on("message", (msg, rinfo) => { + if (asyncLocalStorage.getStore()?.test !== "dgram.events") { + console.error("FAIL: dgram message event lost context"); + failed = true; + } + server.close(); + client.close(); + }); + + server.on("listening", () => { + if (asyncLocalStorage.getStore()?.test !== "dgram.events") { + console.error("FAIL: dgram listening event lost context"); + failed = true; + } + + const port = server.address().port; + client.send("test", port, "localhost"); + }); + + server.on("close", () => { + if (asyncLocalStorage.getStore()?.test !== "dgram.events") { + console.error("FAIL: dgram close event lost context"); + failed = true; + } + process.exit(failed ? 1 : 0); + }); + + server.bind(0); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-dgram-send.js b/test/js/node/async_hooks/async-context/async-context-dgram-send.js new file mode 100644 index 0000000000..ac72d16b1e --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-dgram-send.js @@ -0,0 +1,25 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const dgram = require("dgram"); + +const asyncLocalStorage = new AsyncLocalStorage(); + +asyncLocalStorage.run({ test: "dgram.send" }, () => { + const server = dgram.createSocket("udp4"); + const client = dgram.createSocket("udp4"); + + server.on("message", () => { + server.close(); + client.close(); + }); + + server.bind(0, () => { + const port = server.address().port; + client.send("test", port, "localhost", err => { + if (asyncLocalStorage.getStore()?.test !== "dgram.send") { + console.error("FAIL: dgram.send callback lost context"); + process.exit(1); + } + setTimeout(() => process.exit(0), 100); + }); + }); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-dns-lookup.js b/test/js/node/async_hooks/async-context/async-context-dns-lookup.js new file mode 100644 index 0000000000..e4d6df2f03 --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-dns-lookup.js @@ -0,0 +1,14 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const dns = require("dns"); + +const asyncLocalStorage = new AsyncLocalStorage(); + +asyncLocalStorage.run({ test: "dns.lookup" }, () => { + dns.lookup("localhost", (err, address, family) => { + if (asyncLocalStorage.getStore()?.test !== "dns.lookup") { + console.error("FAIL: dns.lookup callback lost context"); + process.exit(1); + } + process.exit(0); + }); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-dns-resolve4.js b/test/js/node/async_hooks/async-context/async-context-dns-resolve4.js new file mode 100644 index 0000000000..af901f606e --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-dns-resolve4.js @@ -0,0 +1,14 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const dns = require("dns"); + +const asyncLocalStorage = new AsyncLocalStorage(); + +asyncLocalStorage.run({ test: "dns.resolve4" }, () => { + dns.resolve4("localhost", (err, addresses) => { + if (asyncLocalStorage.getStore()?.test !== "dns.resolve4") { + console.error("FAIL: dns.resolve4 callback lost context"); + process.exit(1); + } + process.exit(0); + }); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-dns-resolveCname.js b/test/js/node/async_hooks/async-context/async-context-dns-resolveCname.js new file mode 100644 index 0000000000..bf33f21d88 --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-dns-resolveCname.js @@ -0,0 +1,14 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const dns = require("dns"); + +const asyncLocalStorage = new AsyncLocalStorage(); + +asyncLocalStorage.run({ test: "dns.resolveCname" }, () => { + dns.resolveCname("www.example.com", (err, addresses) => { + if (asyncLocalStorage.getStore()?.test !== "dns.resolveCname") { + console.error("FAIL: dns.resolveCname callback lost context"); + process.exit(1); + } + process.exit(0); + }); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-dns-resolveMx.js b/test/js/node/async_hooks/async-context/async-context-dns-resolveMx.js new file mode 100644 index 0000000000..51d739f33f --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-dns-resolveMx.js @@ -0,0 +1,14 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const dns = require("dns"); + +const asyncLocalStorage = new AsyncLocalStorage(); + +asyncLocalStorage.run({ test: "dns.resolveMx" }, () => { + dns.resolveMx("example.com", (err, addresses) => { + if (asyncLocalStorage.getStore()?.test !== "dns.resolveMx") { + console.error("FAIL: dns.resolveMx callback lost context"); + process.exit(1); + } + process.exit(0); + }); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-dns-resolveTxt.js b/test/js/node/async_hooks/async-context/async-context-dns-resolveTxt.js new file mode 100644 index 0000000000..b29368a0ab --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-dns-resolveTxt.js @@ -0,0 +1,14 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const dns = require("dns"); + +const asyncLocalStorage = new AsyncLocalStorage(); + +asyncLocalStorage.run({ test: "dns.resolveTxt" }, () => { + dns.resolveTxt("google.com", (err, records) => { + if (asyncLocalStorage.getStore()?.test !== "dns.resolveTxt") { + console.error("FAIL: dns.resolveTxt callback lost context"); + process.exit(1); + } + process.exit(0); + }); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-dns-reverse.js b/test/js/node/async_hooks/async-context/async-context-dns-reverse.js new file mode 100644 index 0000000000..f3ce75f8fe --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-dns-reverse.js @@ -0,0 +1,14 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const dns = require("dns"); + +const asyncLocalStorage = new AsyncLocalStorage(); + +asyncLocalStorage.run({ test: "dns.reverse" }, () => { + dns.reverse("8.8.8.8", (err, hostnames) => { + if (asyncLocalStorage.getStore()?.test !== "dns.reverse") { + console.error("FAIL: dns.reverse callback lost context"); + process.exit(1); + } + process.exit(0); + }); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-events-emitter.js b/test/js/node/async_hooks/async-context/async-context-events-emitter.js new file mode 100644 index 0000000000..e518d6e2d4 --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-events-emitter.js @@ -0,0 +1,43 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const { EventEmitter } = require("events"); + +const asyncLocalStorage = new AsyncLocalStorage(); +let failed = false; + +asyncLocalStorage.run({ test: "EventEmitter" }, () => { + const emitter = new EventEmitter(); + + // Test regular event + emitter.on("test", () => { + if (asyncLocalStorage.getStore()?.test !== "EventEmitter") { + console.error("FAIL: EventEmitter listener lost context"); + failed = true; + } + }); + + // Test once event + emitter.once("once-test", () => { + if (asyncLocalStorage.getStore()?.test !== "EventEmitter") { + console.error("FAIL: EventEmitter once listener lost context"); + failed = true; + } + }); + + // Test async event handler + emitter.on("async-test", async () => { + await new Promise(resolve => setImmediate(resolve)); + if (asyncLocalStorage.getStore()?.test !== "EventEmitter") { + console.error("FAIL: EventEmitter async listener lost context"); + failed = true; + } + }); + + // Emit events + emitter.emit("test"); + emitter.emit("once-test"); + emitter.emit("async-test"); + + setTimeout(() => { + process.exit(failed ? 1 : 0); + }, 100); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-events-on-async.js b/test/js/node/async_hooks/async-context/async-context-events-on-async.js new file mode 100644 index 0000000000..9e716b1895 --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-events-on-async.js @@ -0,0 +1,32 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const { EventEmitter, on } = require("events"); + +const asyncLocalStorage = new AsyncLocalStorage(); + +asyncLocalStorage.run({ test: "events.on" }, async () => { + const emitter = new EventEmitter(); + + // Start async iterator in background + (async () => { + try { + for await (const [value] of on(emitter, "data")) { + if (asyncLocalStorage.getStore()?.test !== "events.on") { + console.error("FAIL: events.on async iterator lost context"); + process.exit(1); + } + if (value === "end") break; + } + process.exit(0); + } catch (err) { + console.error("ERROR:", err); + process.exit(1); + } + })(); + + // Emit events after a delay + setTimeout(() => { + emitter.emit("data", "test1"); + emitter.emit("data", "test2"); + emitter.emit("data", "end"); + }, 10); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-fs-access.js b/test/js/node/async_hooks/async-context/async-context-fs-access.js new file mode 100644 index 0000000000..3d2381910e --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-fs-access.js @@ -0,0 +1,14 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const fs = require("fs"); + +const asyncLocalStorage = new AsyncLocalStorage(); + +asyncLocalStorage.run({ test: "fs.access" }, () => { + fs.access(__filename, fs.constants.R_OK, err => { + if (asyncLocalStorage.getStore()?.test !== "fs.access") { + console.error("FAIL: fs.access callback lost context"); + process.exit(1); + } + process.exit(0); + }); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-fs-appendFile.js b/test/js/node/async_hooks/async-context/async-context-fs-appendFile.js new file mode 100644 index 0000000000..e14c65670a --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-fs-appendFile.js @@ -0,0 +1,20 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const fs = require("fs"); +const path = require("path"); + +const asyncLocalStorage = new AsyncLocalStorage(); +const testFile = path.join(fs.mkdtempSync("fstest"), "appendfile-test-" + Date.now() + ".txt"); + +asyncLocalStorage.run({ test: "fs.appendFile" }, () => { + fs.appendFile(testFile, "test data", err => { + if (asyncLocalStorage.getStore()?.test !== "fs.appendFile") { + console.error("FAIL: fs.appendFile callback lost context"); + try { + fs.unlinkSync(testFile); + } catch {} + process.exit(1); + } + fs.unlinkSync(testFile); + process.exit(0); + }); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-fs-chmod.js b/test/js/node/async_hooks/async-context/async-context-fs-chmod.js new file mode 100644 index 0000000000..cfcaa0ebdf --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-fs-chmod.js @@ -0,0 +1,20 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const fs = require("fs"); +const path = require("path"); + +const asyncLocalStorage = new AsyncLocalStorage(); +const testFile = path.join(fs.mkdtempSync("fstest"), "chmod-test-" + Date.now() + ".txt"); + +fs.writeFileSync(testFile, "test"); + +asyncLocalStorage.run({ test: "fs.chmod" }, () => { + fs.chmod(testFile, 0o644, err => { + if (asyncLocalStorage.getStore()?.test !== "fs.chmod") { + console.error("FAIL: fs.chmod callback lost context"); + fs.unlinkSync(testFile); + process.exit(1); + } + fs.unlinkSync(testFile); + process.exit(0); + }); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-fs-copyFile.js b/test/js/node/async_hooks/async-context/async-context-fs-copyFile.js new file mode 100644 index 0000000000..a9ca9899fa --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-fs-copyFile.js @@ -0,0 +1,27 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const fs = require("fs"); +const path = require("path"); + +const asyncLocalStorage = new AsyncLocalStorage(); +const dir = fs.mkdtempSync("copy-test-"); +const srcFile = path.join(dir, "copy-src-" + Date.now() + ".txt"); +const destFile = path.join(dir, "copy-dest-" + Date.now() + ".txt"); + +fs.writeFileSync(srcFile, "test data"); + +asyncLocalStorage.run({ test: "fs.copyFile" }, () => { + fs.copyFile(srcFile, destFile, err => { + if (asyncLocalStorage.getStore()?.test !== "fs.copyFile") { + console.error("FAIL: fs.copyFile callback lost context"); + try { + fs.unlinkSync(srcFile); + fs.unlinkSync(destFile); + } catch {} + process.exit(1); + } + fs.unlinkSync(srcFile); + fs.unlinkSync(destFile); + fs.rmdirSync(dir); + process.exit(0); + }); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-fs-createReadStream.js b/test/js/node/async_hooks/async-context/async-context-fs-createReadStream.js new file mode 100644 index 0000000000..6edcdb0d97 --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-fs-createReadStream.js @@ -0,0 +1,37 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const fs = require("fs"); +const path = require("path"); + +const asyncLocalStorage = new AsyncLocalStorage(); +const testFile = path.join(fs.mkdtempSync("fstest"), "readstream-test-" + Date.now() + ".txt"); +let failed = false; + +// Create test file +fs.writeFileSync(testFile, "test data for read stream"); + +asyncLocalStorage.run({ test: "fs.createReadStream" }, () => { + const stream = fs.createReadStream(testFile); + + stream.on("data", chunk => { + if (asyncLocalStorage.getStore()?.test !== "fs.createReadStream") { + console.error("FAIL: fs.createReadStream data event lost context"); + failed = true; + } + }); + + stream.on("end", () => { + if (asyncLocalStorage.getStore()?.test !== "fs.createReadStream") { + console.error("FAIL: fs.createReadStream end event lost context"); + failed = true; + } + }); + + stream.on("close", () => { + if (asyncLocalStorage.getStore()?.test !== "fs.createReadStream") { + console.error("FAIL: fs.createReadStream close event lost context"); + failed = true; + } + fs.unlinkSync(testFile); + process.exit(failed ? 1 : 0); + }); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-fs-createWriteStream.js b/test/js/node/async_hooks/async-context/async-context-fs-createWriteStream.js new file mode 100644 index 0000000000..eb81a37156 --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-fs-createWriteStream.js @@ -0,0 +1,30 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const fs = require("fs"); +const path = require("path"); + +const asyncLocalStorage = new AsyncLocalStorage(); +const testFile = path.join(fs.mkdtempSync("fstest"), "writestream-test-" + Date.now() + ".txt"); +let failed = false; + +asyncLocalStorage.run({ test: "fs.createWriteStream" }, () => { + const stream = fs.createWriteStream(testFile); + + stream.on("finish", () => { + if (asyncLocalStorage.getStore()?.test !== "fs.createWriteStream") { + console.error("FAIL: fs.createWriteStream finish event lost context"); + failed = true; + } + }); + + stream.on("close", () => { + if (asyncLocalStorage.getStore()?.test !== "fs.createWriteStream") { + console.error("FAIL: fs.createWriteStream close event lost context"); + failed = true; + } + fs.unlinkSync(testFile); + process.exit(failed ? 1 : 0); + }); + + stream.write("test data"); + stream.end(); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-fs-fstat.js b/test/js/node/async_hooks/async-context/async-context-fs-fstat.js new file mode 100644 index 0000000000..8fdeec3ff3 --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-fs-fstat.js @@ -0,0 +1,30 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const fs = require("fs"); +const path = require("path"); + +const asyncLocalStorage = new AsyncLocalStorage(); +const testFile = path.join(fs.mkdtempSync("fstest"), "fstat-test-" + Date.now() + ".txt"); + +fs.writeFileSync(testFile, "test"); + +asyncLocalStorage.run({ test: "fs.fstat" }, () => { + fs.open(testFile, "r", (err, fd) => { + if (err) { + console.error("ERROR:", err); + process.exit(1); + } + + fs.fstat(fd, (err, stats) => { + if (asyncLocalStorage.getStore()?.test !== "fs.fstat") { + console.error("FAIL: fs.fstat callback lost context"); + fs.closeSync(fd); + fs.unlinkSync(testFile); + process.exit(1); + } + + fs.closeSync(fd); + fs.unlinkSync(testFile); + process.exit(0); + }); + }); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-fs-lstat.js b/test/js/node/async_hooks/async-context/async-context-fs-lstat.js new file mode 100644 index 0000000000..fb194d909c --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-fs-lstat.js @@ -0,0 +1,14 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const fs = require("fs"); + +const asyncLocalStorage = new AsyncLocalStorage(); + +asyncLocalStorage.run({ test: "fs.lstat" }, () => { + fs.lstat(__filename, (err, stats) => { + if (asyncLocalStorage.getStore()?.test !== "fs.lstat") { + console.error("FAIL: fs.lstat callback lost context"); + process.exit(1); + } + process.exit(0); + }); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-fs-mkdir.js b/test/js/node/async_hooks/async-context/async-context-fs-mkdir.js new file mode 100644 index 0000000000..28f548cf10 --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-fs-mkdir.js @@ -0,0 +1,22 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const fs = require("fs"); +const path = require("path"); + +const asyncLocalStorage = new AsyncLocalStorage(); +const dir = fs.mkdtempSync("mkdir-test-"); +const testDir = path.join(dir, "mkdir-test-" + Date.now()); + +asyncLocalStorage.run({ test: "fs.mkdir" }, () => { + fs.mkdir(testDir, err => { + if (asyncLocalStorage.getStore()?.test !== "fs.mkdir") { + console.error("FAIL: fs.mkdir callback lost context"); + try { + fs.rmdirSync(testDir); + } catch {} + process.exit(1); + } + fs.rmdirSync(testDir); + fs.rmdirSync(dir); + process.exit(0); + }); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-fs-mkdtemp.js b/test/js/node/async_hooks/async-context/async-context-fs-mkdtemp.js new file mode 100644 index 0000000000..4e74138b61 --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-fs-mkdtemp.js @@ -0,0 +1,20 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const fs = require("fs"); +const path = require("path"); + +const asyncLocalStorage = new AsyncLocalStorage(); + +asyncLocalStorage.run({ test: "fs.mkdtemp" }, () => { + const dir = fs.mkdtempSync("test-"); + fs.mkdtemp(path.join(dir, "test-"), (err, directory) => { + if (asyncLocalStorage.getStore()?.test !== "fs.mkdtemp") { + console.error("FAIL: fs.mkdtemp callback lost context"); + try { + fs.rmdirSync(directory); + } catch {} + process.exit(1); + } + fs.rmdirSync(directory); + process.exit(0); + }); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-fs-open.js b/test/js/node/async_hooks/async-context/async-context-fs-open.js new file mode 100644 index 0000000000..1d0c6dd072 --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-fs-open.js @@ -0,0 +1,35 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const fs = require("fs"); +const path = require("path"); + +const asyncLocalStorage = new AsyncLocalStorage(); +const testFile = path.join(fs.mkdtempSync("fstest"), "open-test-" + Date.now() + ".txt"); + +fs.writeFileSync(testFile, "test"); + +asyncLocalStorage.run({ test: "fs.open" }, () => { + fs.open(testFile, "r", (err, fd) => { + if (err) { + console.error("ERROR:", err); + process.exit(1); + } + + if (asyncLocalStorage.getStore()?.test !== "fs.open") { + console.error("FAIL: fs.open callback lost context"); + fs.closeSync(fd); + fs.unlinkSync(testFile); + process.exit(1); + } + + // Test fs.close + fs.close(fd, err => { + if (asyncLocalStorage.getStore()?.test !== "fs.open") { + console.error("FAIL: fs.close callback lost context"); + fs.unlinkSync(testFile); + process.exit(1); + } + fs.unlinkSync(testFile); + process.exit(0); + }); + }); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-fs-promises.js b/test/js/node/async_hooks/async-context/async-context-fs-promises.js new file mode 100644 index 0000000000..be3e8cce7e --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-fs-promises.js @@ -0,0 +1,28 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const fs = require("fs").promises; +const path = require("path"); + +const asyncLocalStorage = new AsyncLocalStorage(); +const testFile = path.join(require("fs").mkdtempSync("fstest"), "promises-test-" + Date.now() + ".txt"); + +asyncLocalStorage.run({ test: "fs.promises" }, async () => { + try { + await fs.writeFile(testFile, "test"); + if (asyncLocalStorage.getStore()?.test !== "fs.promises") { + console.error("FAIL: fs.promises.writeFile lost context"); + process.exit(1); + } + + await fs.readFile(testFile); + if (asyncLocalStorage.getStore()?.test !== "fs.promises") { + console.error("FAIL: fs.promises.readFile lost context"); + process.exit(1); + } + + await fs.unlink(testFile); + process.exit(0); + } catch (err) { + console.error("ERROR:", err); + process.exit(1); + } +}); diff --git a/test/js/node/async_hooks/async-context/async-context-fs-read.js b/test/js/node/async_hooks/async-context/async-context-fs-read.js new file mode 100644 index 0000000000..a1251bb09c --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-fs-read.js @@ -0,0 +1,31 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const fs = require("fs"); +const path = require("path"); + +const asyncLocalStorage = new AsyncLocalStorage(); +const testFile = path.join(fs.mkdtempSync("fstest"), "read-test-" + Date.now() + ".txt"); + +fs.writeFileSync(testFile, "test data for read"); + +asyncLocalStorage.run({ test: "fs.read" }, () => { + fs.open(testFile, "r", (err, fd) => { + if (err) { + console.error("ERROR:", err); + process.exit(1); + } + + const buffer = Buffer.alloc(10); + fs.read(fd, buffer, 0, 10, 0, (err, bytesRead, buffer) => { + if (asyncLocalStorage.getStore()?.test !== "fs.read") { + console.error("FAIL: fs.read callback lost context"); + fs.closeSync(fd); + fs.unlinkSync(testFile); + process.exit(1); + } + + fs.closeSync(fd); + fs.unlinkSync(testFile); + process.exit(0); + }); + }); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-fs-readdir.js b/test/js/node/async_hooks/async-context/async-context-fs-readdir.js new file mode 100644 index 0000000000..83c6fc3616 --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-fs-readdir.js @@ -0,0 +1,14 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const fs = require("fs"); + +const asyncLocalStorage = new AsyncLocalStorage(); + +asyncLocalStorage.run({ test: "fs.readdir" }, () => { + fs.readdir("/tmp", (err, files) => { + if (asyncLocalStorage.getStore()?.test !== "fs.readdir") { + console.error("FAIL: fs.readdir callback lost context"); + process.exit(1); + } + process.exit(0); + }); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-fs-realpath.js b/test/js/node/async_hooks/async-context/async-context-fs-realpath.js new file mode 100644 index 0000000000..bf0fced6ca --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-fs-realpath.js @@ -0,0 +1,14 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const fs = require("fs"); + +const asyncLocalStorage = new AsyncLocalStorage(); + +asyncLocalStorage.run({ test: "fs.realpath" }, () => { + fs.realpath("/tmp", (err, resolvedPath) => { + if (asyncLocalStorage.getStore()?.test !== "fs.realpath") { + console.error("FAIL: fs.realpath callback lost context"); + process.exit(1); + } + process.exit(0); + }); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-fs-rename.js b/test/js/node/async_hooks/async-context/async-context-fs-rename.js new file mode 100644 index 0000000000..81141e8a55 --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-fs-rename.js @@ -0,0 +1,28 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const fs = require("fs"); +const path = require("path"); + +const asyncLocalStorage = new AsyncLocalStorage(); +const dir = fs.mkdtempSync("rename-test-"); +const oldPath = path.join(dir, "rename-old-" + Date.now() + ".txt"); +const newPath = path.join(dir, "rename-new-" + Date.now() + ".txt"); + +fs.writeFileSync(oldPath, "test"); + +asyncLocalStorage.run({ test: "fs.rename" }, () => { + fs.rename(oldPath, newPath, err => { + if (asyncLocalStorage.getStore()?.test !== "fs.rename") { + console.error("FAIL: fs.rename callback lost context"); + try { + fs.unlinkSync(oldPath); + } catch {} + try { + fs.unlinkSync(newPath); + } catch {} + process.exit(1); + } + fs.unlinkSync(newPath); + fs.rmdirSync(dir); + process.exit(0); + }); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-fs-rmdir.js b/test/js/node/async_hooks/async-context/async-context-fs-rmdir.js new file mode 100644 index 0000000000..b60d86cea1 --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-fs-rmdir.js @@ -0,0 +1,20 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const fs = require("fs"); +const path = require("path"); + +const asyncLocalStorage = new AsyncLocalStorage(); +const dir = fs.mkdtempSync("rmdir-test-"); +const testDir = path.join(dir, "rmdir-test-" + Date.now()); + +fs.mkdirSync(testDir); + +asyncLocalStorage.run({ test: "fs.rmdir" }, () => { + fs.rmdir(testDir, err => { + if (asyncLocalStorage.getStore()?.test !== "fs.rmdir") { + console.error("FAIL: fs.rmdir callback lost context"); + process.exit(1); + } + fs.rmdirSync(dir); + process.exit(0); + }); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-fs-stat.js b/test/js/node/async_hooks/async-context/async-context-fs-stat.js new file mode 100644 index 0000000000..4ca3e9a2fb --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-fs-stat.js @@ -0,0 +1,14 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const fs = require("fs"); + +const asyncLocalStorage = new AsyncLocalStorage(); + +asyncLocalStorage.run({ test: "fs.stat" }, () => { + fs.stat(__filename, (err, stats) => { + if (asyncLocalStorage.getStore()?.test !== "fs.stat") { + console.error("FAIL: fs.stat callback lost context"); + process.exit(1); + } + process.exit(0); + }); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-fs-truncate.js b/test/js/node/async_hooks/async-context/async-context-fs-truncate.js new file mode 100644 index 0000000000..47feb2aa7f --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-fs-truncate.js @@ -0,0 +1,22 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const fs = require("fs"); +const path = require("path"); + +const asyncLocalStorage = new AsyncLocalStorage(); +const testFile = path.join(fs.mkdtempSync("fstest"), "truncate-test-" + Date.now() + ".txt"); + +fs.writeFileSync(testFile, "test data for truncation"); + +asyncLocalStorage.run({ test: "fs.truncate" }, () => { + fs.truncate(testFile, 5, err => { + if (asyncLocalStorage.getStore()?.test !== "fs.truncate") { + console.error("FAIL: fs.truncate callback lost context"); + try { + fs.unlinkSync(testFile); + } catch {} + process.exit(1); + } + fs.unlinkSync(testFile); + process.exit(0); + }); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-fs-unlink.js b/test/js/node/async_hooks/async-context/async-context-fs-unlink.js new file mode 100644 index 0000000000..34749e8fbb --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-fs-unlink.js @@ -0,0 +1,18 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const fs = require("fs"); +const path = require("path"); + +const asyncLocalStorage = new AsyncLocalStorage(); +const testFile = path.join(fs.mkdtempSync("unlink-test"), "unlink-test-" + Date.now() + ".txt"); + +fs.writeFileSync(testFile, "test"); + +asyncLocalStorage.run({ test: "fs.unlink" }, () => { + fs.unlink(testFile, err => { + if (asyncLocalStorage.getStore()?.test !== "fs.unlink") { + console.error("FAIL: fs.unlink callback lost context"); + process.exit(1); + } + process.exit(0); + }); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-fs-watch.js b/test/js/node/async_hooks/async-context/async-context-fs-watch.js new file mode 100644 index 0000000000..561e0defeb --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-fs-watch.js @@ -0,0 +1,40 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const fs = require("fs"); +const path = require("path"); + +const asyncLocalStorage = new AsyncLocalStorage(); +const testFile = path.join(fs.mkdtempSync("watch-test"), "watch-test-" + Date.now() + ".txt"); + +asyncLocalStorage.run({ test: "fs.watch" }, () => { + fs.writeFileSync(testFile, "initial"); + + const watcher = fs.watch(testFile, (eventType, filename) => { + if (asyncLocalStorage.getStore()?.test !== "fs.watch") { + console.error("FAIL: fs.watch callback lost context"); + watcher.close(); + try { + fs.unlinkSync(testFile); + } catch {} + process.exit(1); + } + watcher.close(); + try { + fs.unlinkSync(testFile); + } catch {} + process.exit(0); + }); + + // Trigger the watch event + setTimeout(() => { + fs.writeFileSync(testFile, "modified"); + }, 100); + + // Timeout safety + setTimeout(() => { + watcher.close(); + try { + fs.unlinkSync(testFile); + } catch {} + process.exit(0); + }, 5000); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-fs-watchFile.js b/test/js/node/async_hooks/async-context/async-context-fs-watchFile.js new file mode 100644 index 0000000000..caa5127ef1 --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-fs-watchFile.js @@ -0,0 +1,40 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const fs = require("fs"); +const path = require("path"); + +const asyncLocalStorage = new AsyncLocalStorage(); +const testFile = path.join(fs.mkdtempSync("watchfile-test"), "watchfile-test-" + Date.now() + ".txt"); + +asyncLocalStorage.run({ test: "fs.watchFile" }, () => { + fs.writeFileSync(testFile, "initial"); + + fs.watchFile(testFile, { interval: 50 }, (curr, prev) => { + if (asyncLocalStorage.getStore()?.test !== "fs.watchFile") { + console.error("FAIL: fs.watchFile callback lost context"); + fs.unwatchFile(testFile); + try { + fs.unlinkSync(testFile); + } catch {} + process.exit(1); + } + fs.unwatchFile(testFile); + try { + fs.unlinkSync(testFile); + } catch {} + process.exit(0); + }); + + // Trigger the watch event + setTimeout(() => { + fs.writeFileSync(testFile, "modified"); + }, 100); + + // Timeout safety + setTimeout(() => { + fs.unwatchFile(testFile); + try { + fs.unlinkSync(testFile); + } catch {} + process.exit(0); + }, 5000); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-http-clientrequest.js b/test/js/node/async_hooks/async-context/async-context-http-clientrequest.js new file mode 100644 index 0000000000..505bdc45ed --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-http-clientrequest.js @@ -0,0 +1,48 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const http = require("http"); + +const asyncLocalStorage = new AsyncLocalStorage(); +let failed = false; + +const server = http.createServer((req, res) => { + res.writeHead(200); + res.end("ok"); +}); + +server.listen(0, () => { + const port = server.address().port; + + asyncLocalStorage.run({ test: "http.ClientRequest" }, () => { + const req = http.request({ + port, + method: "POST", + }); + + req.on("response", res => { + if (asyncLocalStorage.getStore()?.test !== "http.ClientRequest") { + console.error("FAIL: ClientRequest response event lost context"); + failed = true; + } + res.resume(); + }); + + req.on("finish", () => { + if (asyncLocalStorage.getStore()?.test !== "http.ClientRequest") { + console.error("FAIL: ClientRequest finish event lost context"); + failed = true; + } + }); + + req.on("close", () => { + if (asyncLocalStorage.getStore()?.test !== "http.ClientRequest") { + console.error("FAIL: ClientRequest close event lost context"); + failed = true; + } + server.close(); + process.exit(failed ? 1 : 0); + }); + + req.write("test data"); + req.end(); + }); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-http-request.js b/test/js/node/async_hooks/async-context/async-context-http-request.js new file mode 100644 index 0000000000..e4c7940eef --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-http-request.js @@ -0,0 +1,51 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const http = require("http"); + +const asyncLocalStorage = new AsyncLocalStorage(); +let failed = false; + +const server = http.createServer((req, res) => { + res.end("ok"); +}); + +server.listen(0, () => { + const port = server.address().port; + + asyncLocalStorage.run({ test: "http.request" }, () => { + const req = http.request( + { + port, + method: "GET", + }, + res => { + if (asyncLocalStorage.getStore()?.test !== "http.request") { + console.error("FAIL: http.request response callback lost context"); + failed = true; + } + + res.on("data", chunk => { + if (asyncLocalStorage.getStore()?.test !== "http.request") { + console.error("FAIL: http response data event lost context"); + failed = true; + } + }); + + res.on("end", () => { + if (asyncLocalStorage.getStore()?.test !== "http.request") { + console.error("FAIL: http response end event lost context"); + failed = true; + } + server.close(); + process.exit(failed ? 1 : 0); + }); + }, + ); + + req.on("error", () => { + server.close(); + process.exit(1); + }); + + req.end(); + }); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-https-request.js b/test/js/node/async_hooks/async-context/async-context-https-request.js new file mode 100644 index 0000000000..4fdc6791a0 --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-https-request.js @@ -0,0 +1,25 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const https = require("https"); + +const asyncLocalStorage = new AsyncLocalStorage(); + +asyncLocalStorage.run({ test: "https.request" }, () => { + const req = https.request("https://httpbin.org/get", res => { + if (asyncLocalStorage.getStore()?.test !== "https.request") { + console.error("FAIL: https.request response callback lost context"); + process.exit(1); + } + + res.on("data", () => {}); + res.on("end", () => { + process.exit(0); + }); + }); + + req.on("error", () => { + // Skip test if network is unavailable + process.exit(0); + }); + + req.end(); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-net-connect.js b/test/js/node/async_hooks/async-context/async-context-net-connect.js new file mode 100644 index 0000000000..e21b95f1f7 --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-net-connect.js @@ -0,0 +1,30 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const net = require("net"); + +const asyncLocalStorage = new AsyncLocalStorage(); +let failed = false; + +const server = net.createServer(); +server.listen(0, () => { + const port = server.address().port; + + asyncLocalStorage.run({ test: "net.connect" }, () => { + const client = net.connect(port, () => { + if (asyncLocalStorage.getStore()?.test !== "net.connect") { + console.error("FAIL: net.connect callback lost context"); + failed = true; + } + client.end(); + }); + + client.on("close", () => { + if (asyncLocalStorage.getStore()?.test !== "net.connect") { + console.error("FAIL: net socket close event lost context"); + failed = true; + } + server.close(() => { + process.exit(failed ? 1 : 0); + }); + }); + }); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-net-server.js b/test/js/node/async_hooks/async-context/async-context-net-server.js new file mode 100644 index 0000000000..beaa9ba2ba --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-net-server.js @@ -0,0 +1,37 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const net = require("net"); + +const asyncLocalStorage = new AsyncLocalStorage(); +let failed = false; + +asyncLocalStorage.run({ test: "net.Server" }, () => { + const server = net.createServer(); + + server.on("connection", socket => { + if (asyncLocalStorage.getStore()?.test !== "net.Server") { + console.error("FAIL: net.Server connection event lost context"); + failed = true; + } + socket.end(); + }); + + server.on("listening", () => { + if (asyncLocalStorage.getStore()?.test !== "net.Server") { + console.error("FAIL: net.Server listening event lost context"); + failed = true; + } + + // Connect to trigger connection event + const client = net.connect(server.address().port); + client.on("close", () => { + // Give time for server connection event to fire + setTimeout(() => { + server.close(() => { + process.exit(failed ? 1 : 0); + }); + }, 50); + }); + }); + + server.listen(0); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-net-socket-write.js b/test/js/node/async_hooks/async-context/async-context-net-socket-write.js new file mode 100644 index 0000000000..a9964a08ec --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-net-socket-write.js @@ -0,0 +1,42 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const net = require("net"); + +const asyncLocalStorage = new AsyncLocalStorage(); +let failed = false; + +const server = net.createServer(socket => { + socket.on("data", () => { + socket.end(); + }); +}); + +server.listen(0, () => { + const port = server.address().port; + + asyncLocalStorage.run({ test: "net.Socket.write" }, () => { + const client = net.connect(port); + + client.on("connect", () => { + // Test write callback + client.write("test data", err => { + if (asyncLocalStorage.getStore()?.test !== "net.Socket.write") { + console.error("FAIL: net.Socket write callback lost context"); + failed = true; + } + }); + + // Test end callback + client.end("final data", err => { + if (asyncLocalStorage.getStore()?.test !== "net.Socket.write") { + console.error("FAIL: net.Socket end callback lost context"); + failed = true; + } + }); + }); + + client.on("close", () => { + server.close(); + process.exit(failed ? 1 : 0); + }); + }); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-process-nextTick.js b/test/js/node/async_hooks/async-context/async-context-process-nextTick.js new file mode 100644 index 0000000000..ab2524eb5a --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-process-nextTick.js @@ -0,0 +1,21 @@ +const { AsyncLocalStorage } = require("async_hooks"); + +const asyncLocalStorage = new AsyncLocalStorage(); + +asyncLocalStorage.run({ test: "process.nextTick" }, () => { + process.nextTick(() => { + if (asyncLocalStorage.getStore()?.test !== "process.nextTick") { + console.error("FAIL: process.nextTick callback lost context"); + process.exit(1); + } + + // Test nested nextTick + process.nextTick(() => { + if (asyncLocalStorage.getStore()?.test !== "process.nextTick") { + console.error("FAIL: nested process.nextTick callback lost context"); + process.exit(1); + } + process.exit(0); + }); + }); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-queueMicrotask.js b/test/js/node/async_hooks/async-context/async-context-queueMicrotask.js new file mode 100644 index 0000000000..4f82d66503 --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-queueMicrotask.js @@ -0,0 +1,13 @@ +const { AsyncLocalStorage } = require("async_hooks"); + +const asyncLocalStorage = new AsyncLocalStorage(); + +asyncLocalStorage.run({ test: "queueMicrotask" }, () => { + queueMicrotask(() => { + if (asyncLocalStorage.getStore()?.test !== "queueMicrotask") { + console.error("FAIL: queueMicrotask callback lost context"); + process.exit(1); + } + process.exit(0); + }); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-readline-interface.js b/test/js/node/async_hooks/async-context/async-context-readline-interface.js new file mode 100644 index 0000000000..a8a4be084f --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-readline-interface.js @@ -0,0 +1,37 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const readline = require("readline"); +const { Readable } = require("stream"); + +const asyncLocalStorage = new AsyncLocalStorage(); +let failed = false; + +asyncLocalStorage.run({ test: "readline" }, () => { + const input = new Readable({ + read() {}, + }); + + const rl = readline.createInterface({ + input, + output: process.stdout, + terminal: false, + }); + + rl.on("line", line => { + if (asyncLocalStorage.getStore()?.test !== "readline") { + console.error("FAIL: readline line event lost context"); + failed = true; + } + }); + + rl.on("close", () => { + if (asyncLocalStorage.getStore()?.test !== "readline") { + console.error("FAIL: readline close event lost context"); + failed = true; + } + process.exit(failed ? 1 : 0); + }); + + // Send data and close + input.push("test line\n"); + input.push(null); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-stream-async-iterator.js b/test/js/node/async_hooks/async-context/async-context-stream-async-iterator.js new file mode 100644 index 0000000000..1b896c7c88 --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-stream-async-iterator.js @@ -0,0 +1,28 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const { Readable } = require("stream"); + +const asyncLocalStorage = new AsyncLocalStorage(); + +asyncLocalStorage.run({ test: "stream.async.iterator" }, async () => { + const readable = new Readable({ + read() { + this.push("a"); + this.push("b"); + this.push("c"); + this.push(null); + }, + }); + + try { + for await (const chunk of readable) { + if (asyncLocalStorage.getStore()?.test !== "stream.async.iterator") { + console.error("FAIL: stream async iterator lost context"); + process.exit(1); + } + } + process.exit(0); + } catch (err) { + console.error("ERROR:", err); + process.exit(1); + } +}); diff --git a/test/js/node/async_hooks/async-context/async-context-stream-readable.js b/test/js/node/async_hooks/async-context/async-context-stream-readable.js new file mode 100644 index 0000000000..43e80abec8 --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-stream-readable.js @@ -0,0 +1,36 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const { Readable } = require("stream"); + +const asyncLocalStorage = new AsyncLocalStorage(); +let failed = false; + +asyncLocalStorage.run({ test: "stream.Readable" }, () => { + const readable = new Readable({ + read() { + this.push("test"); + this.push(null); + }, + }); + + readable.on("data", chunk => { + if (asyncLocalStorage.getStore()?.test !== "stream.Readable") { + console.error("FAIL: Readable stream data event lost context"); + failed = true; + } + }); + + readable.on("end", () => { + if (asyncLocalStorage.getStore()?.test !== "stream.Readable") { + console.error("FAIL: Readable stream end event lost context"); + failed = true; + } + process.exit(failed ? 1 : 0); + }); + + readable.on("close", () => { + if (asyncLocalStorage.getStore()?.test !== "stream.Readable") { + console.error("FAIL: Readable stream close event lost context"); + failed = true; + } + }); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-stream-transform.js b/test/js/node/async_hooks/async-context/async-context-stream-transform.js new file mode 100644 index 0000000000..49f9ecc0be --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-stream-transform.js @@ -0,0 +1,35 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const { Transform } = require("stream"); + +const asyncLocalStorage = new AsyncLocalStorage(); +let failed = false; + +asyncLocalStorage.run({ test: "stream.Transform" }, () => { + const transform = new Transform({ + transform(chunk, encoding, callback) { + if (asyncLocalStorage.getStore()?.test !== "stream.Transform") { + console.error("FAIL: Transform stream transform method lost context"); + failed = true; + } + callback(null, chunk); + }, + }); + + transform.on("data", chunk => { + if (asyncLocalStorage.getStore()?.test !== "stream.Transform") { + console.error("FAIL: Transform stream data event lost context"); + failed = true; + } + }); + + transform.on("end", () => { + if (asyncLocalStorage.getStore()?.test !== "stream.Transform") { + console.error("FAIL: Transform stream end event lost context"); + failed = true; + } + process.exit(failed ? 1 : 0); + }); + + transform.write("test"); + transform.end(); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-stream-writable.js b/test/js/node/async_hooks/async-context/async-context-stream-writable.js new file mode 100644 index 0000000000..baa494dc03 --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-stream-writable.js @@ -0,0 +1,28 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const { Writable } = require("stream"); + +const asyncLocalStorage = new AsyncLocalStorage(); +let failed = false; + +asyncLocalStorage.run({ test: "stream.Writable" }, () => { + const writable = new Writable({ + write(chunk, encoding, callback) { + if (asyncLocalStorage.getStore()?.test !== "stream.Writable") { + console.error("FAIL: Writable stream write method lost context"); + failed = true; + } + callback(); + }, + }); + + writable.on("finish", () => { + if (asyncLocalStorage.getStore()?.test !== "stream.Writable") { + console.error("FAIL: Writable stream finish event lost context"); + failed = true; + } + process.exit(failed ? 1 : 0); + }); + + writable.write("test"); + writable.end(); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-timers-promises.js b/test/js/node/async_hooks/async-context/async-context-timers-promises.js new file mode 100644 index 0000000000..3c3f5755c6 --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-timers-promises.js @@ -0,0 +1,27 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const timers = require("timers/promises"); + +const asyncLocalStorage = new AsyncLocalStorage(); + +asyncLocalStorage.run({ test: "timers.promises" }, async () => { + try { + // Test setTimeout promise + await timers.setTimeout(10); + if (asyncLocalStorage.getStore()?.test !== "timers.promises") { + console.error("FAIL: timers.promises.setTimeout lost context"); + process.exit(1); + } + + // Test setImmediate promise + await timers.setImmediate(); + if (asyncLocalStorage.getStore()?.test !== "timers.promises") { + console.error("FAIL: timers.promises.setImmediate lost context"); + process.exit(1); + } + + process.exit(0); + } catch (err) { + console.error("ERROR:", err); + process.exit(1); + } +}); diff --git a/test/js/node/async_hooks/async-context/async-context-timers-ref-unref.js b/test/js/node/async_hooks/async-context/async-context-timers-ref-unref.js new file mode 100644 index 0000000000..31cc46582c --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-timers-ref-unref.js @@ -0,0 +1,17 @@ +const { AsyncLocalStorage } = require("async_hooks"); + +const asyncLocalStorage = new AsyncLocalStorage(); + +asyncLocalStorage.run({ test: "timers.ref.unref" }, () => { + const timeout = setTimeout(() => { + if (asyncLocalStorage.getStore()?.test !== "timers.ref.unref") { + console.error("FAIL: setTimeout with ref/unref lost context"); + process.exit(1); + } + process.exit(0); + }, 10); + + // Test ref/unref operations + timeout.unref(); + timeout.ref(); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-timers-setInterval.js b/test/js/node/async_hooks/async-context/async-context-timers-setInterval.js new file mode 100644 index 0000000000..89c252b171 --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-timers-setInterval.js @@ -0,0 +1,19 @@ +const { AsyncLocalStorage } = require("async_hooks"); + +const asyncLocalStorage = new AsyncLocalStorage(); +let count = 0; + +asyncLocalStorage.run({ test: "setInterval" }, () => { + const interval = setInterval(() => { + if (asyncLocalStorage.getStore()?.test !== "setInterval") { + console.error("FAIL: setInterval callback lost context"); + clearInterval(interval); + process.exit(1); + } + count++; + if (count >= 2) { + clearInterval(interval); + process.exit(0); + } + }, 10); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-tls-connect.js b/test/js/node/async_hooks/async-context/async-context-tls-connect.js new file mode 100644 index 0000000000..0a3b31c990 --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-tls-connect.js @@ -0,0 +1,28 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const tls = require("tls"); + +const asyncLocalStorage = new AsyncLocalStorage(); + +asyncLocalStorage.run({ test: "tls.connect" }, () => { + const socket = tls.connect( + 443, + "example.com", + { + rejectUnauthorized: true, + }, + () => { + if (asyncLocalStorage.getStore()?.test !== "tls.connect") { + console.error("FAIL: tls.connect callback lost context"); + socket.destroy(); + process.exit(1); + } + socket.destroy(); + process.exit(0); + }, + ); + + socket.on("error", () => { + // Skip test if network is unavailable + process.exit(0); + }); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-util-promisify-custom.js b/test/js/node/async_hooks/async-context/async-context-util-promisify-custom.js new file mode 100644 index 0000000000..218f70aa69 --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-util-promisify-custom.js @@ -0,0 +1,27 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const util = require("util"); + +const asyncLocalStorage = new AsyncLocalStorage(); + +// Custom callback function +function customAsync(value, callback) { + setTimeout(() => { + callback(null, value * 2); + }, 10); +} + +const customPromise = util.promisify(customAsync); + +asyncLocalStorage.run({ test: "util.promisify.custom" }, async () => { + try { + const result = await customPromise(21); + if (asyncLocalStorage.getStore()?.test !== "util.promisify.custom") { + console.error("FAIL: util.promisify with custom function lost context"); + process.exit(1); + } + process.exit(0); + } catch (err) { + console.error("ERROR:", err); + process.exit(1); + } +}); diff --git a/test/js/node/async_hooks/async-context/async-context-util-promisify.js b/test/js/node/async_hooks/async-context/async-context-util-promisify.js new file mode 100644 index 0000000000..665059cf2b --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-util-promisify.js @@ -0,0 +1,22 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const util = require("util"); +const fs = require("fs"); + +const asyncLocalStorage = new AsyncLocalStorage(); + +// Test util.promisify with a built-in callback function +const readFilePromise = util.promisify(fs.readFile); + +asyncLocalStorage.run({ test: "util.promisify" }, async () => { + try { + await readFilePromise(__filename, "utf8"); + if (asyncLocalStorage.getStore()?.test !== "util.promisify") { + console.error("FAIL: util.promisify lost context"); + process.exit(1); + } + process.exit(0); + } catch (err) { + console.error("ERROR:", err); + process.exit(1); + } +}); diff --git a/test/js/node/async_hooks/async-context/async-context-vm-runInNewContext.js b/test/js/node/async_hooks/async-context/async-context-vm-runInNewContext.js new file mode 100644 index 0000000000..9d7b4bc0a1 --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-vm-runInNewContext.js @@ -0,0 +1,23 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const vm = require("vm"); + +const asyncLocalStorage = new AsyncLocalStorage(); + +asyncLocalStorage.run({ test: "vm.runInNewContext" }, () => { + const code = ` + setImmediate(() => { + if (asyncLocalStorage.getStore()?.test !== 'vm.runInNewContext') { + console.error('FAIL: vm.runInNewContext callback lost context'); + process.exit(1); + } + process.exit(0); + }); + `; + + vm.runInNewContext(code, { + asyncLocalStorage, + setImmediate, + console, + process, + }); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-worker_threads-message.js b/test/js/node/async_hooks/async-context/async-context-worker_threads-message.js new file mode 100644 index 0000000000..d92e02bab5 --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-worker_threads-message.js @@ -0,0 +1,32 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const { Worker, isMainThread, parentPort } = require("worker_threads"); + +const asyncLocalStorage = new AsyncLocalStorage(); + +if (isMainThread) { + asyncLocalStorage.run({ test: "worker_threads" }, () => { + const worker = new Worker(__filename); + + worker.on("message", msg => { + if (asyncLocalStorage.getStore()?.test !== "worker_threads") { + console.error("FAIL: worker message event lost context"); + process.exit(1); + } + worker.terminate(); + }); + + worker.on("exit", () => { + if (asyncLocalStorage.getStore()?.test !== "worker_threads") { + console.error("FAIL: worker exit event lost context"); + process.exit(1); + } + process.exit(0); + }); + + worker.postMessage("test"); + }); +} else { + parentPort.on("message", msg => { + parentPort.postMessage("response"); + }); +} diff --git a/test/js/node/async_hooks/async-context/async-context-zlib-brotliCompress.js b/test/js/node/async_hooks/async-context/async-context-zlib-brotliCompress.js new file mode 100644 index 0000000000..798f67d86b --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-zlib-brotliCompress.js @@ -0,0 +1,14 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const zlib = require("zlib"); + +const asyncLocalStorage = new AsyncLocalStorage(); + +asyncLocalStorage.run({ test: "zlib.brotliCompress" }, () => { + zlib.brotliCompress("test data", (err, compressed) => { + if (asyncLocalStorage.getStore()?.test !== "zlib.brotliCompress") { + console.error("FAIL: zlib.brotliCompress callback lost context"); + process.exit(1); + } + process.exit(0); + }); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-zlib-brotliDecompress.js b/test/js/node/async_hooks/async-context/async-context-zlib-brotliDecompress.js new file mode 100644 index 0000000000..f467565b02 --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-zlib-brotliDecompress.js @@ -0,0 +1,17 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const zlib = require("zlib"); + +const asyncLocalStorage = new AsyncLocalStorage(); + +asyncLocalStorage.run({ test: "zlib.brotliDecompress" }, () => { + // First compress data + const compressed = zlib.brotliCompressSync("test data"); + + zlib.brotliDecompress(compressed, (err, decompressed) => { + if (asyncLocalStorage.getStore()?.test !== "zlib.brotliDecompress") { + console.error("FAIL: zlib.brotliDecompress callback lost context"); + process.exit(1); + } + process.exit(0); + }); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-zlib-createGzip.js b/test/js/node/async_hooks/async-context/async-context-zlib-createGzip.js new file mode 100644 index 0000000000..6a0d80b029 --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-zlib-createGzip.js @@ -0,0 +1,34 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const zlib = require("zlib"); + +const asyncLocalStorage = new AsyncLocalStorage(); +let failed = false; + +asyncLocalStorage.run({ test: "zlib.createGzip" }, () => { + const gzip = zlib.createGzip(); + + gzip.on("data", chunk => { + if (asyncLocalStorage.getStore()?.test !== "zlib.createGzip") { + console.error("FAIL: zlib.createGzip data event lost context"); + failed = true; + } + }); + + gzip.on("end", () => { + if (asyncLocalStorage.getStore()?.test !== "zlib.createGzip") { + console.error("FAIL: zlib.createGzip end event lost context"); + failed = true; + } + process.exit(failed ? 1 : 0); + }); + + gzip.on("finish", () => { + if (asyncLocalStorage.getStore()?.test !== "zlib.createGzip") { + console.error("FAIL: zlib.createGzip finish event lost context"); + failed = true; + } + }); + + gzip.write("test data"); + gzip.end(); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-zlib-deflate.js b/test/js/node/async_hooks/async-context/async-context-zlib-deflate.js new file mode 100644 index 0000000000..15ea6e70b8 --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-zlib-deflate.js @@ -0,0 +1,14 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const zlib = require("zlib"); + +const asyncLocalStorage = new AsyncLocalStorage(); + +asyncLocalStorage.run({ test: "zlib.deflate" }, () => { + zlib.deflate("test data", (err, compressed) => { + if (asyncLocalStorage.getStore()?.test !== "zlib.deflate") { + console.error("FAIL: zlib.deflate callback lost context"); + process.exit(1); + } + process.exit(0); + }); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-zlib-gunzip.js b/test/js/node/async_hooks/async-context/async-context-zlib-gunzip.js new file mode 100644 index 0000000000..5c3ada6191 --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-zlib-gunzip.js @@ -0,0 +1,17 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const zlib = require("zlib"); + +const asyncLocalStorage = new AsyncLocalStorage(); + +asyncLocalStorage.run({ test: "zlib.gunzip" }, () => { + // First compress data + const compressed = zlib.gzipSync("test data"); + + zlib.gunzip(compressed, (err, decompressed) => { + if (asyncLocalStorage.getStore()?.test !== "zlib.gunzip") { + console.error("FAIL: zlib.gunzip callback lost context"); + process.exit(1); + } + process.exit(0); + }); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-zlib-gzip.js b/test/js/node/async_hooks/async-context/async-context-zlib-gzip.js new file mode 100644 index 0000000000..20decb021f --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-zlib-gzip.js @@ -0,0 +1,14 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const zlib = require("zlib"); + +const asyncLocalStorage = new AsyncLocalStorage(); + +asyncLocalStorage.run({ test: "zlib.gzip" }, () => { + zlib.gzip("test data", (err, compressed) => { + if (asyncLocalStorage.getStore()?.test !== "zlib.gzip") { + console.error("FAIL: zlib.gzip callback lost context"); + process.exit(1); + } + process.exit(0); + }); +}); diff --git a/test/js/node/async_hooks/async-context/async-context-zlib-inflate.js b/test/js/node/async_hooks/async-context/async-context-zlib-inflate.js new file mode 100644 index 0000000000..1a41995d2d --- /dev/null +++ b/test/js/node/async_hooks/async-context/async-context-zlib-inflate.js @@ -0,0 +1,17 @@ +const { AsyncLocalStorage } = require("async_hooks"); +const zlib = require("zlib"); + +const asyncLocalStorage = new AsyncLocalStorage(); + +asyncLocalStorage.run({ test: "zlib.inflate" }, () => { + // First compress data + const compressed = zlib.deflateSync("test data"); + + zlib.inflate(compressed, (err, decompressed) => { + if (asyncLocalStorage.getStore()?.test !== "zlib.inflate") { + console.error("FAIL: zlib.inflate callback lost context"); + process.exit(1); + } + process.exit(0); + }); +}); diff --git a/test/js/third_party/next-auth/next-auth.test.ts b/test/js/third_party/next-auth/next-auth.test.ts index 25397559c6..9ba6204e06 100644 --- a/test/js/third_party/next-auth/next-auth.test.ts +++ b/test/js/third_party/next-auth/next-auth.test.ts @@ -4,10 +4,11 @@ import { bunEnv, bunRun, runBunInstall, tmpdirSync } from "harness"; import { join } from "path"; describe("next-auth", () => { it("should be able to call server action multiple times using auth middleware #18977", async () => { - const testDir = tmpdirSync("next-auth"); + const testDir = tmpdirSync("next-auth-" + Date.now()); cpSync(join(import.meta.dir, "fixture"), testDir, { recursive: true, + force: true, filter: src => { if (src.includes("node_modules")) { return false; @@ -21,6 +22,7 @@ describe("next-auth", () => { await runBunInstall(bunEnv, testDir, { savesLockfile: false }); + console.log(testDir); const result = bunRun(join(testDir, "server.js"), { AUTH_SECRET: "I7Jiq12TSMlPlAzyVAT+HxYX7OQb/TTqIbfTTpr1rg8=", }); @@ -28,5 +30,5 @@ describe("next-auth", () => { expect(result.stdout).toBeDefined(); const lines = result.stdout?.split("\n") ?? []; expect(lines[lines.length - 1]).toMatch(/request sent/); - }, 30_000); + }, 90_000); }); diff --git a/test/no-validate-exceptions.txt b/test/no-validate-exceptions.txt index 7927ad3a70..d234c32610 100644 --- a/test/no-validate-exceptions.txt +++ b/test/no-validate-exceptions.txt @@ -269,6 +269,7 @@ test/js/junit-reporter/junit.test.js test/js/node/assert/assert-promise.test.ts test/js/node/assert/assert.spec.ts test/js/node/async_hooks/AsyncLocalStorage.test.ts +test/js/node/async_hooks/AsyncLocalStorage-tracking.test.ts test/js/node/buffer-concat.test.ts test/js/node/buffer.test.js test/js/node/child_process/child-process-exec.test.ts