Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use callbacks instead of a promise that calls a callback for node:fs functions that expect callbacks #14109

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions src/bun.js/bindings/bindings.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2162,6 +2162,67 @@ pub const AbortSignal = extern opaque {
pub const Extern = [_][]const u8{ "create", "ref", "unref", "signal", "abortReason", "aborted", "addListener", "fromJS", "toJS", "cleanNativeBindings" };
};

pub const AsyncTask = union(Type) {
promise: JSPromise.Strong,
callback: JSC.Strong,

pub const Type = enum {
promise,
callback,
};

pub const Argument = union(Type) {
promise: void,
callback: JSValue,
};

pub const empty = AsyncTask{ .promise = .{} };

pub fn init(global: *JSC.JSGlobalObject, k: Argument) AsyncTask {
return switch (k) {
.promise => AsyncTask{ .promise = JSPromise.Strong.init(global) },
.callback => |callback| AsyncTask{ .callback = JSC.Strong.create(callback, global) },
};
}

pub fn value(this: *const AsyncTask) JSValue {
switch (this.*) {
.promise => |*promise| return promise.value(),
.callback => |*callback| return callback.get() orelse .zero,
}
}

pub fn deinit(this: *AsyncTask) void {
switch (this.*) {
.promise => |*promise| promise.deinit(),
.callback => |*callback| callback.deinit(),
}
}

pub fn reject(this: *AsyncTask, globalThis: *JSC.JSGlobalObject, val: JSC.JSValue) void {
switch (this.*) {
.promise => |*promise| promise.reject(globalThis, val),
.callback => |*callback| {
callback.swap().call(globalThis, .undefined, &.{
val,
}) catch |err| globalThis.takeException(err);
},
}
}

pub fn resolve(this: *AsyncTask, globalThis: *JSC.JSGlobalObject, val: JSC.JSValue) void {
switch (this.*) {
.promise => |*promise| promise.resolve(globalThis, val),
.callback => |*callback| {
callback.swap().call(globalThis, .undefined, &.{
.null,
val,
}) catch |err| globalThis.takeException(err);
},
}
}
};

pub const JSPromise = extern struct {
pub const shim = Shimmer("JSC", "JSPromise", @This());
bytes: shim.Bytes,
Expand Down
78 changes: 48 additions & 30 deletions src/bun.js/node/node_fs.zig
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ pub const Async = struct {

comptime bun.assert(Environment.isWindows);
return struct {
promise: JSC.JSPromise.Strong,
async_task: JSC.AsyncTask,
args: ArgumentType,
globalObject: *JSC.JSGlobalObject,
req: uv.fs_t = std.mem.zeroes(uv.fs_t),
Expand All @@ -174,9 +174,9 @@ pub const Async = struct {

pub usingnamespace bun.New(@This());

pub fn create(globalObject: *JSC.JSGlobalObject, this: *JSC.Node.NodeJSFS, args: ArgumentType, vm: *JSC.VirtualMachine) JSC.JSValue {
pub fn create(globalObject: *JSC.JSGlobalObject, this: *JSC.Node.NodeJSFS, args: ArgumentType, vm: *JSC.VirtualMachine, callback_argument: JSC.JSValue) JSC.JSValue {
var task = Task.new(.{
.promise = JSC.JSPromise.Strong.init(globalObject),
.async_task = JSC.AsyncTask.init(globalObject, if (this.is_callback_api) .{ .callback = callback_argument } else .promise),
.args = args,
.result = undefined,
.globalObject = globalObject,
Expand Down Expand Up @@ -213,7 +213,7 @@ pub const Async = struct {
log("uv close({}) SKIPPED", .{fd});
task.result = Maybe(Return.Close).success;
task.globalObject.bunVM().eventLoop().enqueueTask(JSC.Task.init(task));
return task.promise.value();
return task.async_task.value();
}

const rc = uv.uv_fs_close(loop, &task.req, fd, &uv_callback);
Expand Down Expand Up @@ -267,7 +267,7 @@ pub const Async = struct {
else => comptime unreachable,
}

return task.promise.value();
return task.async_task.value();
}

fn uv_callback(req: *uv.fs_t) callconv(.C) void {
Expand Down Expand Up @@ -296,8 +296,10 @@ pub const Async = struct {
break :brk out;
},
};
var promise_value = this.promise.value();
var promise = this.promise.get();
var promise_value = this.async_task.value();
var promise = this.async_task;
this.async_task = JSC.AsyncTask.empty;
defer promise.deinit();
promise_value.ensureStillAlive();

const tracker = this.tracker;
Expand Down Expand Up @@ -326,15 +328,15 @@ pub const Async = struct {
} else {
this.args.deinit();
}
this.promise.deinit();
this.async_task.deinit();
this.destroy();
}
};
}

fn NewAsyncFSTask(comptime ReturnType: type, comptime ArgumentType: type, comptime Function: anytype) type {
return struct {
promise: JSC.JSPromise.Strong,
async_task: JSC.AsyncTask,
args: ArgumentType,
globalObject: *JSC.JSGlobalObject,
task: JSC.WorkPoolTask = .{ .callback = &workPoolCallback },
Expand All @@ -348,14 +350,15 @@ pub const Async = struct {

pub fn create(
globalObject: *JSC.JSGlobalObject,
_: *JSC.Node.NodeJSFS,
node_fs: *JSC.Node.NodeJSFS,
args: ArgumentType,
vm: *JSC.VirtualMachine,
callback_argument: JSC.JSValue,
) JSC.JSValue {
var task = bun.new(
Task,
Task{
.promise = JSC.JSPromise.Strong.init(globalObject),
.async_task = JSC.AsyncTask.init(globalObject, if (node_fs.is_callback_api) .{ .callback = callback_argument } else .promise),
.args = args,
.result = undefined,
.globalObject = globalObject,
Expand All @@ -367,7 +370,7 @@ pub const Async = struct {
task.tracker.didSchedule(globalObject);
JSC.WorkPool.schedule(&task.task);

return task.promise.value();
return task.async_task.value();
}

fn workPoolCallback(task: *JSC.WorkPoolTask) void {
Expand Down Expand Up @@ -396,8 +399,10 @@ pub const Async = struct {
break :brk out;
},
};
var promise_value = this.promise.value();
var promise = this.promise.get();
var promise_value = this.async_task.value();
var promise = this.async_task;
defer promise.deinit();
this.async_task = JSC.AsyncTask.empty;
promise_value.ensureStillAlive();

const tracker = this.tracker;
Expand Down Expand Up @@ -426,7 +431,7 @@ pub const Async = struct {
} else {
this.args.deinit();
}
this.promise.deinit();
this.async_task.deinit();
bun.destroy(this);
}
};
Expand All @@ -440,7 +445,7 @@ pub fn NewAsyncCpTask(comptime is_shell: bool) type {
const ShellTask = bun.shell.Interpreter.Builtin.Cp.ShellCpTask;
const ShellTaskT = if (is_shell) *ShellTask else u0;
return struct {
promise: JSC.JSPromise.Strong = .{},
async_task: JSC.AsyncTask = JSC.AsyncTask.empty,
args: Arguments.Cp,
evtloop: JSC.EventLoopHandle,
task: JSC.WorkPoolTask = .{ .callback = &workPoolCallback },
Expand Down Expand Up @@ -547,13 +552,14 @@ pub fn NewAsyncCpTask(comptime is_shell: bool) type {

pub fn create(
globalObject: *JSC.JSGlobalObject,
_: *JSC.Node.NodeJSFS,
node_fs: *JSC.Node.NodeJSFS,
cp_args: Arguments.Cp,
vm: *JSC.VirtualMachine,
arena: bun.ArenaAllocator,
callback_argument: JSC.JSValue,
) JSC.JSValue {
const task = createWithShellTask(globalObject, cp_args, vm, arena, 0, true);
return task.promise.value();
const task = createWithShellTask(globalObject, cp_args, vm, arena, 0, if (node_fs.is_callback_api) .{ .callback = callback_argument } else .promise);
return task.async_task.value();
}

pub fn createWithShellTask(
Expand All @@ -562,12 +568,12 @@ pub fn NewAsyncCpTask(comptime is_shell: bool) type {
vm: *JSC.VirtualMachine,
arena: bun.ArenaAllocator,
shelltask: ShellTaskT,
comptime enable_promise: bool,
async_task_type: ?JSC.AsyncTask.Argument,
) *ThisAsyncCpTask {
var task = bun.new(
ThisAsyncCpTask,
ThisAsyncCpTask{
.promise = if (comptime enable_promise) JSC.JSPromise.Strong.init(globalObject) else .{},
.async_task = if (async_task_type) |task_type| JSC.AsyncTask.init(globalObject, task_type) else JSC.AsyncTask.empty,
.args = cp_args,
.has_result = .{ .raw = false },
.result = undefined,
Expand Down Expand Up @@ -665,8 +671,10 @@ pub fn NewAsyncCpTask(comptime is_shell: bool) type {
break :brk out;
},
};
var promise_value = this.promise.value();
var promise = this.promise.get();
var promise_value = this.async_task.value();
var promise = this.async_task;
defer promise.deinit();
this.async_task = JSC.AsyncTask.empty;
promise_value.ensureStillAlive();

const tracker = this.tracker;
Expand All @@ -689,7 +697,7 @@ pub fn NewAsyncCpTask(comptime is_shell: bool) type {
this.deinitialized = true;
if (comptime !is_shell) this.ref.unref(this.evtloop);
this.args.deinit();
this.promise.deinit();
this.async_task.deinit();
this.arena.deinit();
bun.destroy(this);
}
Expand Down Expand Up @@ -920,7 +928,7 @@ pub fn NewAsyncCpTask(comptime is_shell: bool) type {
}

pub const AsyncReaddirRecursiveTask = struct {
promise: JSC.JSPromise.Strong,
async_task: JSC.AsyncTask,
args: Arguments.Readdir,
globalObject: *JSC.JSGlobalObject,
task: JSC.WorkPoolTask = .{ .callback = &workPoolCallback },
Expand Down Expand Up @@ -1024,11 +1032,19 @@ pub const AsyncReaddirRecursiveTask = struct {

pub fn create(
globalObject: *JSC.JSGlobalObject,
node_fs: *JSC.Node.NodeJSFS,
args: Arguments.Readdir,
vm: *JSC.VirtualMachine,
callback_argument: JSC.JSValue,
) JSC.JSValue {
var task = AsyncReaddirRecursiveTask.new(.{
.promise = JSC.JSPromise.Strong.init(globalObject),
.async_task = JSC.AsyncTask.init(
globalObject,
if (node_fs.is_callback_api)
.{ .callback = callback_argument }
else
.promise,
),
.args = args,
.has_result = .{ .raw = false },
.globalObject = globalObject,
Expand All @@ -1047,7 +1063,7 @@ pub const AsyncReaddirRecursiveTask = struct {

JSC.WorkPool.schedule(&task.task);

return task.promise.value();
return task.async_task.value();
}

pub fn performWork(this: *AsyncReaddirRecursiveTask, basename: [:0]const u8, buf: *bun.PathBuffer, comptime is_root: bool) void {
Expand Down Expand Up @@ -1223,8 +1239,10 @@ pub const AsyncReaddirRecursiveTask = struct {

break :brk out;
};
var promise_value = this.promise.value();
var promise = this.promise.get();
var promise_value = this.async_task.value();
var promise = this.async_task;
defer promise.deinit();
this.async_task = JSC.AsyncTask.empty;
promise_value.ensureStillAlive();

const tracker = this.tracker;
Expand Down Expand Up @@ -1252,7 +1270,7 @@ pub const AsyncReaddirRecursiveTask = struct {
this.args.deinit();
bun.default_allocator.free(this.root_path.slice());
this.clearResultList();
this.promise.deinit();
this.async_task.deinit();
this.destroy();
}
};
Expand Down
37 changes: 27 additions & 10 deletions src/bun.js/node/node_fs_binding.zig
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,17 @@ fn call(comptime FunctionEnum: NodeFSFunctionEnum) NodeFSFunction {
const Arguments = comptime function.params[1].type.?;
const NodeBindingClosure = struct {
pub fn bind(this: *JSC.Node.NodeJSFS, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) JSC.JSValue {
var arguments = callframe.arguments(8);
var arguments = callframe.arguments(9);
var arguments_slice = arguments.slice();
var callback_argument = JSC.JSValue.zero;
if (this.is_callback_api) {
if (arguments_slice.len > 0) {
callback_argument = arguments_slice[arguments_slice.len - 1].withAsyncContextIfNeeded(globalObject);
arguments_slice = arguments_slice[0 .. arguments_slice.len - 1];
}
}

var slice = ArgumentsSlice.init(globalObject.bunVM(), arguments.slice());
var slice = ArgumentsSlice.init(globalObject.bunVM(), arguments_slice);
slice.will_be_async = true;
var exceptionref: JSC.C.JSValueRef = null;
const args = if (comptime Arguments != void)
Expand All @@ -119,15 +127,15 @@ fn call(comptime FunctionEnum: NodeFSFunctionEnum) NodeFSFunction {

const Task = @field(JSC.Node.Async, @tagName(FunctionEnum));
if (comptime FunctionEnum == .cp) {
return Task.create(globalObject, this, args, globalObject.bunVM(), slice.arena);
return Task.create(globalObject, this, args, globalObject.bunVM(), slice.arena, callback_argument);
} else {
if (comptime FunctionEnum == .readdir) {
if (args.recursive) {
return JSC.Node.Async.readdir_recursive.create(globalObject, args, globalObject.bunVM());
return JSC.Node.Async.readdir_recursive.create(globalObject, this, args, globalObject.bunVM(), callback_argument);
}
}

return Task.create(globalObject, this, args, globalObject.bunVM());
return Task.create(globalObject, this, args, globalObject.bunVM(), callback_argument);
}
}
};
Expand All @@ -136,6 +144,7 @@ fn call(comptime FunctionEnum: NodeFSFunctionEnum) NodeFSFunction {

pub const NodeJSFS = struct {
node_fs: JSC.Node.NodeFS = .{},
is_callback_api: bool = false,

pub usingnamespace JSC.Codegen.JSNodeJSFS;
pub usingnamespace bun.New(@This());
Expand Down Expand Up @@ -256,13 +265,21 @@ pub const NodeJSFS = struct {
};

pub fn createBinding(globalObject: *JSC.JSGlobalObject) JSC.JSValue {
const module = NodeJSFS.new(.{});

const promises = NodeJSFS.new(.{
.is_callback_api = false,
});
const callbacks = NodeJSFS.new(.{
.is_callback_api = true,
});
const vm = globalObject.bunVM();
if (vm.standalone_module_graph != null)
module.node_fs.vm = vm;
if (vm.standalone_module_graph != null) {
promises.node_fs.vm = vm;
}

return module.toJS(globalObject);
const array = JSC.JSValue.createEmptyObject(globalObject, 2);
array.putIndex(globalObject, 0, promises.toJS(globalObject));
array.putIndex(globalObject, 1, callbacks.toJS(globalObject));
return array;
}

pub fn createMemfdForTesting(globalObject: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame) JSC.JSValue {
Expand Down
Loading
Loading