1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
//! This is a slow, dynamically-allocated one-off task
//! Use it when you can't add to jsc.Task directly and managing the lifetime of the Task struct is overly complex
use core::ffi::c_void;
use core::ptr::NonNull;
use crate::{JsResult, Task};
pub struct ManagedTask {
// TODO(port): lifetime — opaque userdata pointer round-tripped through `new`/`run`
pub ctx: Option<NonNull<c_void>>,
pub callback: fn(*mut c_void) -> JsResult<()>,
pub cleanup: Option<fn(*mut c_void)>,
}
impl ManagedTask {
pub fn task(this: *mut ManagedTask) -> Task {
// PORT NOTE: Zig `Task.init(this)` mapped variant type → tag at comptime.
// Per §Dispatch (tag+ptr), name the tag explicitly.
Task::new(crate::task_tag::ManagedTask, this.cast())
}
/// # Safety
/// `this` must be the live `*mut ManagedTask` returned by `heap::alloc` in
/// `new()`; ownership transfers — `this` is freed (via `heap::take`) before
/// return on both Ok and Err paths.
pub unsafe fn run(this: *mut ManagedTask) -> JsResult<()> {
// Zig: @setRuntimeSafety(false) — no Rust equivalent; bounds/overflow checks
// are already off in release and there is nothing to elide here.
// SAFETY: `this` was produced by `heap::alloc` in `new` (Zig:
// `bun.default_allocator.create`). Reconstituting the Box here mirrors
// Zig's `defer bun.default_allocator.destroy(this)` — it drops at scope
// exit on both the Ok and Err paths.
let this = unsafe { bun_core::heap::take(this) };
let callback = this.callback;
let ctx = this.ctx;
callback(ctx.unwrap().as_ptr())
}
pub fn cancel(&mut self) {
fn noop(_: *mut c_void) -> JsResult<()> {
Ok(())
}
self.callback = noop;
}
// PORT NOTE: reshaped for borrowck / const-generics limitation.
// Zig `pub fn New(comptime Type, comptime Callback) type { return struct { init, wrap } }`
// cannot be expressed in stable Rust because a fn value is not a valid const-generic
// parameter. The `wrap` trampoline (which `@ptrCast`/`@alignCast` the opaque ctx back
// to `*Type` and `@call(bun.callmod_inline, Callback, ...)`) is folded away by storing
// the type-erased fn pointer directly — `fn(*mut T)` and `fn(*mut c_void)` share ABI.
// Callers: `ManagedTask.New(T, cb).init(ctx)` → `ManagedTask::new(ctx, cb)`.
// PERF(port): was comptime monomorphization (callmod_inline).
pub fn new<T>(ctx: *mut T, callback: fn(*mut T) -> JsResult<()>) -> Task {
let managed = bun_core::heap::into_raw(Box::new(ManagedTask {
// SAFETY: `fn(*mut T) -> R` and `fn(*mut c_void) -> R` have identical
// ABI for all `T: Sized`; `run` passes back the exact pointer stored
// in `ctx` below, so the callee observes its original `*mut T`.
callback: unsafe {
bun_ptr::cast_fn_ptr::<fn(*mut T) -> JsResult<()>, fn(*mut c_void) -> JsResult<()>>(
callback,
)
},
ctx: NonNull::new(ctx.cast::<c_void>()),
cleanup: None,
}));
ManagedTask::task(managed)
}
pub fn new_owned<T>(ctx: *mut T, callback: fn(*mut T) -> JsResult<()>) -> Task {
fn drop_ctx<T>(p: *mut c_void) {
// SAFETY: `p` is the `heap::into_raw(Box<T>)` stored in `ctx` by `new_owned`.
unsafe { bun_core::heap::destroy(p.cast::<T>()) };
}
let managed = bun_core::heap::into_raw(Box::new(ManagedTask {
// SAFETY: same fn-pointer ABI cast as `new`.
callback: unsafe {
bun_ptr::cast_fn_ptr::<fn(*mut T) -> JsResult<()>, fn(*mut c_void) -> JsResult<()>>(
callback,
)
},
ctx: NonNull::new(ctx.cast::<c_void>()),
cleanup: Some(drop_ctx::<T>),
}));
ManagedTask::task(managed)
}
}
// ported from: src/event_loop/ManagedTask.zig