Skip to content
Closed
Show file tree
Hide file tree
Changes from 5 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
10 changes: 2 additions & 8 deletions src/jsc/JSPromise.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,16 +241,10 @@ impl JSPromise {
}
}

/// Safe `status()` for the common `*mut JSPromise`-stored case
/// (`vm.pending_internal_promise` etc.). `JSPromise` is a GC-managed JSC
/// heap cell; pointers to it are kept alive by the VM's strong-ref slots,
/// not by Rust ownership. Centralizes the per-call-site
/// `unsafe { (*p).status() }` deref so callers don't open-code it.
/// `status()` for a non-null `*mut JSPromise` that a GC slot keeps alive
/// (`vm.pending_internal_promise()` etc.).
Comment thread
robobun marked this conversation as resolved.
Resolved
#[inline]
pub(crate) fn status_ptr(p: *mut JSPromise) -> Status {
// `p` is a non-null GC-managed cell tracked by the VM (caller obtained
// it from a strong-ref VM field or a fresh
// `JSInternalPromise__resolvedPromise` return value).
JSPromise::opaque_ref(p).status()
}

Expand Down
85 changes: 40 additions & 45 deletions src/jsc/VirtualMachine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,8 +292,6 @@ pub struct VirtualMachine {
pub rare_data: Option<Box<RareData>>,
pub proxy_env_storage: crate::rare_data::ProxyEnvStorage,
pub(crate) resolved_path_dups: Vec<Box<[u8]>>,
pub pending_internal_promise: Option<*mut JSInternalPromise>,
pub pending_internal_promise_is_protected: bool,
pub pending_internal_promise_reported_at: u32,
pub(crate) hot_reload_deferred: bool,
pub entry_point_result: EntryPointResult,
Expand Down Expand Up @@ -459,6 +457,14 @@ unsafe extern "C" {
safe fn Bun__closeAllNodeSqliteDatabasesForTermination(global: &JSGlobalObject);
safe fn Bun__WebView__closeAllForTermination();
safe fn Zig__GlobalObject__prepareForDestruction(global: &JSGlobalObject);
// safe: a `WriteBarrier` slot on the global; null clears it.
safe fn Bun__GlobalObject__pendingInternalPromise(
global: &JSGlobalObject,
) -> *mut JSInternalPromise;
safe fn Bun__GlobalObject__setPendingInternalPromise(
global: &JSGlobalObject,
promise: *mut JSInternalPromise,
);
safe fn Zig__GlobalObject__forbidExecution(global: &JSGlobalObject);
safe fn Zig__GlobalObject__stopActiveDOMObjectsForTestIsolation(global: &JSGlobalObject);
safe fn Zig__GlobalObject__retireForTestIsolation(global: &JSGlobalObject);
Expand Down Expand Up @@ -3413,19 +3419,15 @@ impl VirtualMachine {
// SAFETY: hook contract.
let p = unsafe { (hooks.load_preloads)(self) }?;
if !p.is_null() {
JSValue::from_cell(p).ensure_still_alive();
JSValue::from_cell(p).protect();
self.pending_internal_promise = Some(p);
self.pending_internal_promise_is_protected = true;
self.set_pending_internal_promise(Some(p));
return Ok(p);
}
}

// Check if Module.runMain was patched.
if self.has_patched_run_main {
bun_core::hint::cold();
self.pending_internal_promise = None;
self.pending_internal_promise_is_protected = false;
self.set_pending_internal_promise(None);
let global_ref = self.global();
let argv1 = bun_string_jsc::create_utf8_for_js(global_ref, MAIN_FILE_NAME)
.map_err(|_| crate::CrateError::JSError)?;
Expand All @@ -3435,7 +3437,7 @@ impl VirtualMachine {
.map_err(|_| crate::CrateError::JSError)?;
// If the override stored a promise itself, use that; otherwise
// wrap its return value.
if let Some(stored) = self.pending_internal_promise {
if let Some(stored) = self.pending_internal_promise() {
return Ok(stored);
}
// `Promise.resolve(ret)` reads `ret.constructor` / `ret.then`,
Expand All @@ -3444,8 +3446,7 @@ impl VirtualMachine {
JSC__JSInternalPromise__resolvedPromise(global_ref, ret)
})
.map_err(|_| crate::CrateError::JSError)?;
self.pending_internal_promise = Some(resolved);
self.pending_internal_promise_is_protected = false;
self.set_pending_internal_promise(Some(resolved));
return Ok(resolved);
}
}
Expand All @@ -3472,9 +3473,7 @@ impl VirtualMachine {
p
};

self.pending_internal_promise = Some(promise);
self.pending_internal_promise_is_protected = false;
JSValue::from_cell(promise).ensure_still_alive();
self.set_pending_internal_promise(Some(promise));
Ok(promise)
} else {
self.entry_evaluation_started = false;
Expand All @@ -3484,13 +3483,26 @@ impl VirtualMachine {
jsc::JSModuleLoader::load_and_evaluate_module_ptr(global, Some(&main_str))
.map(NonNull::as_ptr)
.ok_or(crate::CrateError::JSError)?;
self.pending_internal_promise = Some(promise);
self.pending_internal_promise_is_protected = false;
JSValue::from_cell(promise).ensure_still_alive();
self.set_pending_internal_promise(Some(promise));
Ok(promise)
}
}

/// The promise for the current entry-point load. The watcher loops poll it.
#[inline]
pub fn pending_internal_promise(&self) -> Option<*mut JSInternalPromise> {
NonNull::new(Bun__GlobalObject__pendingInternalPromise(self.global())).map(NonNull::as_ptr)
}

/// Stores the entry-point promise in a GC slot on the global, which keeps
/// it alive: once it settles nothing else refers to it.
Comment thread
robobun marked this conversation as resolved.
Resolved
pub fn set_pending_internal_promise(&mut self, promise: Option<*mut JSInternalPromise>) {
Bun__GlobalObject__setPendingInternalPromise(
self.global(),
promise.unwrap_or(core::ptr::null_mut()),
);
}

/// `loadEntryPoint(entry_path)` — `reload_entry_point` + spin until the
/// returned promise settles.
pub fn load_entry_point(
Expand All @@ -3502,15 +3514,15 @@ impl VirtualMachine {
// pending_internal_promise can change if hot module reloading is enabled
if self.is_watcher_enabled() {
loop {
let Some(p) = self.pending_internal_promise else {
let Some(p) = self.pending_internal_promise() else {
break;
};
// SAFETY: `p` is a live JSC heap cell tracked by the VM.
if crate::JSPromise::status_ptr(p) != crate::js_promise::Status::Pending {
break;
}
self.event_loop_mut().tick();
let Some(p) = self.pending_internal_promise else {
let Some(p) = self.pending_internal_promise() else {
break;
};
// SAFETY: see above.
Expand All @@ -3526,7 +3538,7 @@ impl VirtualMachine {
let _ = self.wait_for_promise(jsc::AnyPromise::Internal(promise));
}

Ok(self.pending_internal_promise.unwrap_or(promise))
Ok(self.pending_internal_promise().unwrap_or(promise))
}
}

Expand Down Expand Up @@ -4392,7 +4404,7 @@ impl VirtualMachine {

/// After a hot reload, surfaces the entry-point promise's rejection (if any) and re-arms the watcher.
pub fn report_exception_in_hot_reloaded_module_if_needed(&mut self) {
let promise = match self.pending_internal_promise {
let promise = match self.pending_internal_promise() {
Some(p) => p,
None => {
self.add_main_to_watcher_if_needed();
Expand Down Expand Up @@ -4503,7 +4515,7 @@ impl VirtualMachine {
bun_core::reload_process(should_clear_terminal, false);
}

if let Some(p) = self.pending_internal_promise {
if let Some(p) = self.pending_internal_promise() {
// SAFETY: `p` is a live JSC heap cell tracked by the VM.
match crate::JSPromise::status_ptr(p) {
crate::js_promise::Status::Pending => {
Expand Down Expand Up @@ -4541,12 +4553,6 @@ impl VirtualMachine {
// the JSC module loader registry.
self.global().reload().expect("Failed to reload");
self.hot_reload_counter += 1;
if self.pending_internal_promise_is_protected {
if let Some(p) = self.pending_internal_promise {
JSValue::from_cell(p).unprotect();
}
self.pending_internal_promise_is_protected = false;
}
// reload_entry_point() stores into pending_internal_promise on every return path.
let main = self.main;
// Note: reshaped for borrowck — copy the `RawSlice` first to avoid
Expand Down Expand Up @@ -5493,10 +5499,7 @@ impl VirtualMachine {
// SAFETY: hook contract.
let p = unsafe { (hooks.load_preloads)(self) }?;
if !p.is_null() {
JSValue::from_cell(p).ensure_still_alive();
self.pending_internal_promise = Some(p);
JSValue::from_cell(p).protect();
self.pending_internal_promise_is_protected = true;
self.set_pending_internal_promise(Some(p));
return Ok(p);
}
}
Expand All @@ -5508,9 +5511,7 @@ impl VirtualMachine {
let promise = jsc::JSModuleLoader::load_and_evaluate_module_ptr(global, Some(&main_str))
.map(NonNull::as_ptr)
.ok_or(crate::CrateError::JSError)?;
self.pending_internal_promise = Some(promise);
self.pending_internal_promise_is_protected = false;
JSValue::from_cell(promise).ensure_still_alive();
self.set_pending_internal_promise(Some(promise));
Ok(promise)
}

Expand Down Expand Up @@ -5543,15 +5544,15 @@ impl VirtualMachine {
// pending_internal_promise can change if hot module reloading is enabled
if self.is_watcher_enabled() {
loop {
let Some(p) = self.pending_internal_promise else {
let Some(p) = self.pending_internal_promise() else {
break;
};
// SAFETY: `p` is a live JSC heap cell tracked by the VM.
if crate::JSPromise::status_ptr(p) != crate::js_promise::Status::Pending {
break;
}
self.event_loop_mut().tick();
let Some(p) = self.pending_internal_promise else {
let Some(p) = self.pending_internal_promise() else {
break;
};
// SAFETY: see above.
Expand All @@ -5570,7 +5571,7 @@ impl VirtualMachine {
// Pre-arm the waker so this settled-promise tick cannot park (#36450).
self.wakeup();
self.auto_tick();
Ok(self.pending_internal_promise.unwrap())
Ok(self.pending_internal_promise().unwrap())
}

/// Tracks a listening socket so watch-mode reloads can close it.
Expand Down Expand Up @@ -5735,13 +5736,7 @@ impl VirtualMachine {
self.entry_point_result.value.deinit();
self.entry_point_result.cjs_set_value = false;
self.entry_point_result.evaluated_as_cjs = false;
if let Some(promise) = self.pending_internal_promise {
if self.pending_internal_promise_is_protected {
JSValue::from_cell(promise).unprotect();
self.pending_internal_promise_is_protected = false;
}
self.pending_internal_promise = None;
}
// The entry-point promise slot is on the global and goes with it.
self.has_patched_run_main = false;
self.set_main(b"");
self.main_hash = 0;
Expand Down
18 changes: 18 additions & 0 deletions src/jsc/bindings/ZigGlobalObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4631,6 +4631,24 @@ void GlobalObject::setNodeWorkerEntryEvaluatedHook(JSObject* hook)
m_nodeWorkerEntryEvaluatedHook.clear();
}

void GlobalObject::setPendingInternalPromise(JSC::JSPromise* promise)
{
if (promise)
m_pendingInternalPromise.set(vm(), this, promise);
else
m_pendingInternalPromise.clear();
}

extern "C" JSC::JSPromise* Bun__GlobalObject__pendingInternalPromise(Zig::GlobalObject* globalObject)
{
return globalObject->pendingInternalPromise();
}

extern "C" void Bun__GlobalObject__setPendingInternalPromise(Zig::GlobalObject* globalObject, JSC::JSPromise* promise)
{
globalObject->setPendingInternalPromise(promise);
}

extern "C" void Bun__InspectorConnection__disconnectAllOnExit(Zig::GlobalObject*);

void GlobalObject::setNodeParentPort(WebCore::MessagePort* port)
Expand Down
4 changes: 4 additions & 0 deletions src/jsc/bindings/ZigGlobalObject.h
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,8 @@ class GlobalObject : public Bun::GlobalScope {
/* node:worker_threads worker: { stdin?, stdout, stderr } MessagePorts from the parent Worker; */ \
/* process.stdin/stdout/stderr are built over these lazily (BunProcess.cpp constructStd*). */ \
V(private, WriteBarrier<JSObject>, m_nodeWorkerStdioPorts) \
/* The current entry-point load's promise (VirtualMachine::pending_internal_promise). */ \
V(private, WriteBarrier<JSC::JSPromise>, m_pendingInternalPromise) \
V(private, LazyPropertyOfGlobalObject<Structure>, m_moduleGraphFrameStructure) \
\
/* The original, unmodified Error.prepareStackTrace. */ \
Expand Down Expand Up @@ -779,6 +781,8 @@ class GlobalObject : public Bun::GlobalScope {
void nodeWorkerEntryDidSettle();
JSObject* nodeWorkerEntryEvaluatedHook() { return m_nodeWorkerEntryEvaluatedHook.get(); }
void setNodeWorkerEntryEvaluatedHook(JSObject* hook);
JSC::JSPromise* pendingInternalPromise() { return m_pendingInternalPromise.get(); }
void setPendingInternalPromise(JSC::JSPromise* promise);

Bun::MarkdownTagStrings& markdownTagStrings() { return m_markdownTagStrings; }
#include "ZigGeneratedClasses+lazyStructureHeader.h"
Expand Down
5 changes: 2 additions & 3 deletions src/runtime/hw_exports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,8 @@ pub fn set_override_module_run_main_promise(
vm: &mut VirtualMachine,
promise: *mut JSInternalPromise,
) {
if vm.pending_internal_promise.is_none() {
vm.pending_internal_promise = Some(promise);
vm.pending_internal_promise_is_protected = false;
if vm.pending_internal_promise().is_none() {
vm.set_pending_internal_promise(Some(promise));
}
}

Expand Down
12 changes: 8 additions & 4 deletions src/runtime/jsc_hooks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -677,7 +677,7 @@ unsafe fn load_preloads(vm: *mut VirtualMachine) -> bun_jsc::CrateResult<*mut JS
// SAFETY: per fn contract — `vm` is the live per-thread VM.
unsafe { (*vm).is_in_preload = true };
// Note: copy the raw ptr into a guard-owned local so the defer body
// doesn't borrow the fn param — later `(*vm).pending_internal_promise = …`
// doesn't borrow the fn param — later `(*vm).set_pending_internal_promise(…)`
// would otherwise alias the guard's capture.
let vm_for_guard = vm;
scopeguard::defer! {
Expand Down Expand Up @@ -789,7 +789,7 @@ unsafe fn load_preloads(vm: *mut VirtualMachine) -> bun_jsc::CrateResult<*mut JS
};

// SAFETY: per fn contract.
unsafe { (*vm).pending_internal_promise = Some(promise) };
unsafe { (*vm).set_pending_internal_promise(Some(promise)) };
let _protected = JSValue::from_cell(promise).protected();

// ── wait ────────────────────────────────────────────────────────
Expand All @@ -806,7 +806,9 @@ unsafe fn load_preloads(vm: *mut VirtualMachine) -> bun_jsc::CrateResult<*mut JS
// SAFETY: `pending_internal_promise` was set just above (or
// swapped by HMR to another live cell); `status()` is a
// read-only FFI call on a live JSC heap cell.
let pip = unsafe { &*vm }.pending_internal_promise.unwrap_or(promise);
let pip = unsafe { &*vm }
.pending_internal_promise()
.unwrap_or(promise);
// SAFETY: `pip` is a live JSC heap cell (set just above or
// the protected `promise` fallback).
if unsafe { &*pip }.status() != PromiseStatus::Pending {
Expand All @@ -815,7 +817,9 @@ unsafe fn load_preloads(vm: *mut VirtualMachine) -> bun_jsc::CrateResult<*mut JS
// SAFETY: `el` is the live per-thread event loop.
unsafe { (*el).tick() };
// SAFETY: per fn contract — `vm` is the live per-thread VM.
let pip = unsafe { &*vm }.pending_internal_promise.unwrap_or(promise);
let pip = unsafe { &*vm }
.pending_internal_promise()
.unwrap_or(promise);
// SAFETY: `pip` is a live JSC heap cell (see above).
if unsafe { &*pip }.status() == PromiseStatus::Pending {
// SAFETY: per fn contract — short-lived `&mut *vm` for the
Expand Down
Loading
Loading