Skip to content
Open
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
13 changes: 5 additions & 8 deletions src/jsc/JSSecrets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ bun_opaque::opaque_ffi! { pub struct SecretsJobOptions; }
// to the cell. `deinit` consumes/frees the C++ allocation and so stays
// `unsafe fn` (double-free precondition).
unsafe extern "C" {
safe fn Bun__SecretsJobOptions__runTask(ctx: &mut SecretsJobOptions, global: &JSGlobalObject);
safe fn Bun__SecretsJobOptions__runTask(ctx: &mut SecretsJobOptions);
safe fn Bun__SecretsJobOptions__runFromJS(
ctx: &mut SecretsJobOptions,
global: &JSGlobalObject,
Expand All @@ -32,21 +32,20 @@ impl Drop for SecretsOptions {
/// `Bun.secrets.{get,set,delete}` off the JS thread.
pub(crate) struct SecretsJob {
options: SecretsOptions,
global: crate::JsPtr<JSGlobalObject>,
}

impl crate::JobContext for SecretsJob {
type OffThread = Self;
type Js = Strong;
/// The credential store may sit on a user prompt indefinitely; the job owns all it hands it.
type Vm = crate::Unborrowed;

fn run(
this: &mut Self,
vm: &crate::vm_handle::Borrow,
_: &crate::Unborrowed,
done: crate::Completion<Self>,
) -> Option<crate::Completion<Self>> {
// SAFETY: the creating global, alive under the borrow; C++ only threads it through.
let global = unsafe { this.global.under_borrow(vm) };
Bun__SecretsJobOptions__runTask(SecretsJobOptions::opaque_mut(this.options.0), global);
Bun__SecretsJobOptions__runTask(SecretsJobOptions::opaque_mut(this.options.0));
Some(done)
}

Expand Down Expand Up @@ -79,8 +78,6 @@ extern "C" fn Bun__Secrets__scheduleJob(
&cx,
SecretsJob {
options: SecretsOptions(options),
// SAFETY: the creating global outlives every borrow of its VM.
global: unsafe { crate::JsPtr::new(core::ptr::NonNull::from(global)) },
},
Strong::create(promise, global),
);
Expand Down
8 changes: 8 additions & 0 deletions src/jsc/VmHandle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,11 @@ impl VmHandle {
(self.0.hot.state.load(Ordering::SeqCst) == State::Open as u8).then_some(b)
}

/// Whether `close()` has run (anything posted from now on is refused). Any thread; a snapshot.
pub fn is_closed(&self) -> bool {
self.0.hot.state.load(Ordering::SeqCst) == State::Closed as u8
}

// ── embedded work ─────────────────────────────────────────────────────
//
// Pool work whose storage is a field of a JS-owned object (a transpile
Expand Down Expand Up @@ -801,6 +806,9 @@ impl LoopHandle {
pub fn borrow_if_running(&self) -> Option<Borrow> {
self.vm.borrow_if_running()
}
pub fn is_closed(&self) -> bool {
self.vm.is_closed()
}
pub fn accepting_work(&self) -> bool {
self.vm.accepting_work()
}
Expand Down
2 changes: 1 addition & 1 deletion src/jsc/bindings/JSSecrets.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ struct SecretsJobOptions {
extern "C" {

// Runs on the threadpool - does the actual platform API work
void Bun__SecretsJobOptions__runTask(SecretsJobOptions* opts, JSGlobalObject* global)
void Bun__SecretsJobOptions__runTask(SecretsJobOptions* opts)
{
// Already have CString fields, pass them directly to platform APIs
switch (opts->op) {
Expand Down
46 changes: 40 additions & 6 deletions src/jsc/job.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
//! meanwhile. Which thread may touch which part of it is in the types:
//!
//! * [`JobContext::OffThread`] is what the pool body sees. It is `Send`, and it
//! runs under a VM [`Borrow`] the carrier takes for it, so the VM's teardown
//! runs under a VM [`Borrow`] if its [`JobContext::Vm`] says so; teardown then
//! waits for a body that is mid-flight and a body never starts against a VM
//! that is already closed. JS-backed memory it needs is reachable only through
//! [`JsPtr`], i.e. only while that borrow (or a [`JsThread`]) is in hand.
Expand Down Expand Up @@ -234,14 +234,17 @@ pub trait JobContext: Sized + 'static {
type OffThread: Send;
type Js: JsAffine;

/// Pool thread, under a VM borrow the carrier holds for the whole call.
/// [`Borrow`] if `OffThread` reaches VM-owned memory during `run`, else [`Unborrowed`].
type Vm: VmHold;

/// Pool thread, with [`Self::Vm`] held by the carrier for the whole call.
/// Return `done` to complete now; keep it (e.g. across async I/O that
/// finishes on another thread) and call [`Completion::finish`] later to
/// complete then. Work that outlives this call runs under no borrow and
/// must touch only `off`.
fn run(
off: &mut Self::OffThread,
vm: &Borrow,
vm: &Self::Vm,
done: Completion<Self>,
) -> Option<Completion<Self>>;

Expand All @@ -250,6 +253,35 @@ pub trait JobContext: Sized + 'static {
fn then(off: Self::OffThread, js: Self::Js, cx: &JsThread<'_>) -> JsResult<()>;
}

mod sealed {
pub trait Sealed {}
impl Sealed for super::Borrow {}
impl Sealed for super::Unborrowed {}
}

/// The two things [`JobContext::run`] can hold on the VM: [`Borrow`] or [`Unborrowed`].
pub trait VmHold: sealed::Sealed + Sized {
/// `None` once the VM is closed, in which case the body does not run.
fn acquire(handle: &LoopHandle) -> Option<Self>;
}

impl VmHold for Borrow {
#[inline]
fn acquire(handle: &LoopHandle) -> Option<Self> {
handle.borrow()
}
}

/// The [`JobContext::Vm`] of a body that owns everything it touches: teardown does not wait for it.
pub struct Unborrowed(());

impl VmHold for Unborrowed {
#[inline]
fn acquire(handle: &LoopHandle) -> Option<Self> {
(!handle.is_closed()).then_some(Unborrowed(()))
}
}

/// The type-erased head of every [`Job<C>`]: dispatch entries (one task tag
/// serves every `C`) and the VM's live-job links.
#[repr(C)]
Expand Down Expand Up @@ -383,11 +415,12 @@ impl<C: JobContext> Job<C> {
// SAFETY: live job, exclusively the pool's for this callback.
let handle = unsafe { (*this).loop_handle.clone() };
let done = Completion(NonNull::new(this).expect("job"));
let Some(vm) = handle.borrow() else {
let Some(vm) = C::Vm::acquire(&handle) else {
// VM already gone: nothing ran; `finish` releases.
return done.finish();
};
// SAFETY: as above; the borrow keeps the VM (and any JsPtr target) alive.
// SAFETY: as above; a `Borrow` keeps the VM (and any JsPtr target)
// alive, and an `Unborrowed` body touches only `off`.
if let Some(done) = C::run(unsafe { &mut (*this).off }, &vm, done) {
drop(vm);
done.finish();
Expand Down Expand Up @@ -541,7 +574,8 @@ pub enum Never {}
impl JobContext for Never {
type OffThread = ();
type Js = ();
fn run(_: &mut (), _: &Borrow, done: Completion<Self>) -> Option<Completion<Self>> {
type Vm = Unborrowed;
fn run(_: &mut (), _: &Unborrowed, done: Completion<Self>) -> Option<Completion<Self>> {
Some(done)
}
fn then(_: (), _: (), _: &JsThread<'_>) -> JsResult<()> {
Expand Down
2 changes: 1 addition & 1 deletion src/jsc/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1388,7 +1388,7 @@ pub use self::event_loop::{
JsTerminatedResult, ManagedTask, MiniEventLoop, PosixSignalHandle, PosixSignalTask, Task,
WorkPool, WorkPoolTask,
};
pub use self::job::{Completion, Job, JobContext, JsPtr, JsSide, JsThread, Protected};
pub use self::job::{Completion, Job, JobContext, JsPtr, JsSide, JsThread, Protected, Unborrowed};
#[cfg(unix)]
pub type PlatformEventLoop = bun_uws::Loop;
#[cfg(not(unix))]
Expand Down
4 changes: 3 additions & 1 deletion src/runtime/api/Archive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -687,9 +687,11 @@ pub struct AsyncTask<C: TaskContext>(core::marker::PhantomData<C>);
impl<C: TaskContext> bun_jsc::JobContext for AsyncTask<C> {
type OffThread = C;
type Js = JSPromiseStrong;
/// A context owns everything `run` touches: a store ref and copied arguments.
type Vm = bun_jsc::Unborrowed;
fn run(
ctx: &mut C,
_vm: &bun_jsc::vm_handle::Borrow,
_: &bun_jsc::Unborrowed,
done: bun_jsc::Completion<Self>,
) -> Option<bun_jsc::Completion<Self>> {
ctx.run();
Expand Down
2 changes: 2 additions & 0 deletions src/runtime/api/BunObject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2855,6 +2855,8 @@ pub mod JSZstd {
impl jsc::JobContext for ZstdJob {
type OffThread = Self;
type Js = jsc::JSPromiseStrong;
/// `buffer` may be the caller's JS buffer, read below.
type Vm = jsc::vm_handle::Borrow;

fn run(
this: &mut Self,
Expand Down
1 change: 1 addition & 0 deletions src/runtime/api/JSTranspiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,7 @@ pub(crate) struct TransformJs {
impl jsc::JobContext for TransformTask {
type OffThread = Self;
type Js = TransformJs;
type Vm = jsc::vm_handle::Borrow;
fn run(
this: &mut Self,
vm: &jsc::vm_handle::Borrow,
Expand Down
3 changes: 2 additions & 1 deletion src/runtime/api/glob.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,10 +246,11 @@ impl WalkTaskErr {
impl JobContext for WalkTask {
type OffThread = Self;
type Js = WalkJs;
type Vm = bun_jsc::Unborrowed;

fn run(
this: &mut Self,
_vm: &bun_jsc::vm_handle::Borrow,
_: &bun_jsc::Unborrowed,
done: bun_jsc::Completion<Self>,
) -> Option<bun_jsc::Completion<Self>> {
let result = match this.walker.walk() {
Expand Down
2 changes: 2 additions & 0 deletions src/runtime/crypto/PBKDF2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,8 @@ pub(crate) struct Pbkdf2Job {
impl JobContext for Pbkdf2Job {
type OffThread = Self;
type Js = JSPromiseStrong;
/// The password and salt may be the caller's JS buffers, read by `run`.
type Vm = bun_jsc::vm_handle::Borrow;

fn run(
this: &mut Self,
Expand Down
3 changes: 2 additions & 1 deletion src/runtime/crypto/PasswordObject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -567,9 +567,10 @@ impl<Op: PasswordOp> Drop for PasswordJob<Op> {
impl<Op: PasswordOp> bun_jsc::JobContext for PasswordJob<Op> {
type OffThread = Self;
type Js = JSPromiseStrong;
type Vm = bun_jsc::Unborrowed;
fn run(
this: &mut Self,
_vm: &bun_jsc::vm_handle::Borrow,
_: &bun_jsc::Unborrowed,
done: bun_jsc::Completion<Self>,
) -> Option<bun_jsc::Completion<Self>> {
this.value = Some(this.op.compute(&this.password));
Expand Down
4 changes: 3 additions & 1 deletion src/runtime/dns_jsc/dns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1010,9 +1010,11 @@ pub mod get_addr_info_request {
impl bun_jsc::JobContext for LibcLookup {
type OffThread = Self;
type Js = LibcRequest;
/// `getaddrinfo` waits on the resolver as long as it likes; the query is an owned copy.
type Vm = bun_jsc::Unborrowed;
fn run(
this: &mut Self,
_vm: &bun_jsc::vm_handle::Borrow,
_: &bun_jsc::Unborrowed,
done: bun_jsc::Completion<Self>,
) -> Option<bun_jsc::Completion<Self>> {
this.backend.run();
Expand Down
2 changes: 2 additions & 0 deletions src/runtime/image/Image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1465,6 +1465,8 @@ impl Drop for PendingTask {
impl jsc::JobContext for PipelineTask {
type OffThread = Self;
type Js = PipelineJs;
/// `input` borrows a pinned JS buffer or the `Image`'s own bytes (see the `Send` note above).
type Vm = jsc::vm_handle::Borrow;
Comment thread
robobun marked this conversation as resolved.
fn run(
this: &mut Self,
_vm: &jsc::vm_handle::Borrow,
Expand Down
3 changes: 3 additions & 0 deletions src/runtime/node/node_crypto_binding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ macro_rules! extern_crypto_job {
impl JobContext for ExternJob {
type OffThread = Self;
type Js = Strong;
type Vm = Borrow;

fn run(
this: &mut Self,
Expand Down Expand Up @@ -234,6 +235,7 @@ pub mod random {
impl JobContext for RandomFillJob {
type OffThread = Self;
type Js = RandomFillJs;
type Vm = Borrow;

fn run(
this: &mut Self,
Expand Down Expand Up @@ -1061,6 +1063,7 @@ mod _impl {
impl JobContext for ScryptJob {
type OffThread = Self;
type Js = ScryptJs;
type Vm = Borrow;

fn run(
this: &mut Self,
Expand Down
4 changes: 4 additions & 0 deletions src/runtime/node/node_fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1251,6 +1251,8 @@ mod _async_tasks {
{
type OffThread = Self;
type Js = AsyncFSJs;
/// `args` may alias the caller's buffers (`Buffer` paths, `write` data, `read` targets).
type Vm = bun_jsc::vm_handle::Borrow;

fn run(
this: &mut Self,
Expand Down Expand Up @@ -2182,6 +2184,8 @@ mod _async_tasks {
impl bun_jsc::JobContext for AsyncReaddirRecursiveTask {
type OffThread = Self;
type Js = AsyncFSJs;
/// Still carries its protected `args` (possibly a `Buffer` path), like [`AsyncFSTask`].
type Vm = bun_jsc::vm_handle::Borrow;

fn run(
this: &mut Self,
Expand Down
1 change: 0 additions & 1 deletion src/runtime/webcore/Blob.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3671,7 +3671,6 @@ impl BlobExt for Blob {
}
}

path_or_fd.to_thread_safe();
core::mem::replace(
path_or_fd,
PathOrFileDescriptor::Path(crate::webcore::node_types::PathLike::String(
Expand Down
2 changes: 2 additions & 0 deletions src/runtime/webcore/CompressionStreamCoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -833,6 +833,8 @@ pub struct CompressionAsyncJs {
impl bun_jsc::JobContext for CompressionAsyncCtx {
type OffThread = Self;
type Js = CompressionAsyncJs;
/// `input` may be the pinned chunk's JS bytes, read below.
type Vm = bun_jsc::vm_handle::Borrow;

fn run(
this: &mut Self,
Expand Down
22 changes: 18 additions & 4 deletions src/runtime/webcore/blob/Store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use crate::webcore::s3::client::{
S3Credentials, S3CredentialsWithOptions, S3DeleteResult, S3ListObjectsOptions,
S3ListObjectsResult,
};
use bun_core::{ZigString, strings};
use bun_core::{ZigString, ZigStringSlice, strings};
use bun_http_types::MimeType::MimeType;
use bun_url::URL;

Expand Down Expand Up @@ -110,6 +110,17 @@ fn mime_from_path_ext(sliced: &[u8]) -> Option<MimeType> {
bun_http_types::MimeType::by_extension_no_default(ext)
}

/// File jobs read and release stores without a VM borrow, so a store keeps nothing of the JS heap.
fn own_path(path: &mut PathLike) {
if let PathLike::Buffer(buffer) = &*path {
// Documented as a copy; dropping the `Buffer` below unpins it, here on the JS thread.
let copy = bun_core::handle_oom(ZigStringSlice::init_dupe(buffer.slice()));
*path = PathLike::EncodedSlice(copy);
return;
}
path.to_thread_safe();
}

impl StoreExt for Store {
/// Caller is responsible for derefing the Store.
fn to_any_blob(&mut self) -> Option<super::Any> {
Expand All @@ -128,8 +139,7 @@ impl StoreExt for Store {
credentials: S3Credentials,
) -> Result<Box<Store>, crate::Error> {
let mut path = pathlike;
// this actually protects/refs the pathlike
path.to_thread_safe();
own_path(&mut path);

// Compute the extension-derived fallback before moving `path` into the
// Store so we don't need to clone the owned PathLike.
Expand All @@ -144,9 +154,13 @@ impl StoreExt for Store {
}

fn init_file(
pathlike: PathOrFileDescriptor,
mut pathlike: PathOrFileDescriptor,
mime_type: Option<MimeType>,
) -> Result<Box<Store>, crate::Error> {
if let PathOrFileDescriptor::Path(path) = &mut pathlike {
own_path(path);
}

// Compute the extension-derived fallback before moving `pathlike` into
// the Store so we don't need to clone the owned PathOrFileDescriptor.
let mime_type = mime_type.or_else(|| match &pathlike {
Expand Down
4 changes: 3 additions & 1 deletion src/runtime/webcore/blob/copy_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,11 @@ unsafe impl Send for CopyFile {}
impl jsc::JobContext for CopyFile {
type OffThread = Self;
type Js = jsc::JSPromiseStrong;
/// Blocks for the whole copy (a FIFO source waits for a writer); touches only store state.
type Vm = jsc::Unborrowed;
fn run(
this: &mut Self,
_vm: &jsc::vm_handle::Borrow,
_: &jsc::Unborrowed,
done: bun_jsc::Completion<Self>,
) -> Option<bun_jsc::Completion<Self>> {
this.run_async();
Expand Down
4 changes: 3 additions & 1 deletion src/runtime/webcore/blob/read_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,9 +224,11 @@ impl bun_jsc::JobContext for ReadFile {
/// Where the bytes go: completed by `then`, or cancelled when the VM releases the JS sides of
/// its live jobs at teardown (a refused or unrun read then frees only this off-thread part).
type Js = ReadFileCompletionFns;
/// Touches only the stores' own state (see the `Send` note above).
type Vm = bun_jsc::Unborrowed;
fn run(
this: &mut Self,
_vm: &bun_jsc::vm_handle::Borrow,
_: &bun_jsc::Unborrowed,
done: bun_jsc::Completion<Self>,
) -> Option<bun_jsc::Completion<Self>> {
// Starts the read; finishes from the io loop via the token.
Expand Down
Loading