Skip to content
Open
9 changes: 9 additions & 0 deletions src/runtime/server/RequestContext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3585,6 +3585,12 @@ where
if let Some(server) = self.server {
// SAFETY: BACKREF
let server = &*server;
// The "error" may be the worker's TerminationException raised
// inside on_request's fetch-handler call; entering the user
// error() handler with it pending trips assertNoException().
Comment thread
robobun marked this conversation as resolved.
if server.vm().script_execution_status() != bun_jsc::ScriptExecutionStatus::Running {
return;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
let on_error = server.config().on_error;
if !on_error.is_empty() && !self.flags.has_called_error_handler() {
self.flags.set_has_called_error_handler(true);
Expand All @@ -3595,6 +3601,9 @@ where
&[value],
)
.unwrap_or_else(|err| server.global_this().take_exception(err));
if server.global_this().has_exception() {
return;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
let _keep = jsc::EnsureStillAlive(result);
if !result.is_empty_or_undefined_or_null() {
if let Some(err) = result.to_error() {
Expand Down
43 changes: 25 additions & 18 deletions src/runtime/server/ServerWebSocket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,7 @@ impl ServerWebSocket {
let global_object = handler.global_object();
let on_open_handler = handler.on_open;
let on_error = handler.on_error;
if vm.is_shutting_down() {
if vm.script_execution_status() != bun_jsc::ScriptExecutionStatus::Running {
bun_output::scoped_log!(WebSocketServer, "onOpen called after script execution");
ws.close();
return;
Expand Down Expand Up @@ -493,27 +493,26 @@ impl ServerWebSocket {
let global_object = self.handler().global_object();
// This is the start of a task.
let vm = self.handler().vm();
if vm.is_shutting_down() {
if vm.script_execution_status() != bun_jsc::ScriptExecutionStatus::Running {
bun_output::scoped_log!(WebSocketServer, "onMessage called after script execution");
ws.close();
return;
}

let _loop_guard = vm.enter_event_loop_scope();

let payload = match opcode {
Opcode::Text => jsc::bun_string_jsc::create_utf8_for_js(global_object, message),
Opcode::Binary => self.binary_to_js(global_object, message),
_ => unreachable!(),
};
let Ok(payload) = payload else { return };
Comment thread
robobun marked this conversation as resolved.
let arguments = [
self.this_value
.get()
.try_get()
.unwrap_or(JSValue::UNDEFINED),
match opcode {
Opcode::Text => jsc::bun_string_jsc::create_utf8_for_js(global_object, message)
.unwrap_or(JSValue::ZERO), // TODO: properly propagate exception upwards
Opcode::Binary => self
.binary_to_js(global_object, message)
.unwrap_or(JSValue::ZERO), // TODO: properly propagate exception upwards
_ => unreachable!(),
},
payload,
];

let mut corker = Corker {
Expand Down Expand Up @@ -562,7 +561,9 @@ impl ServerWebSocket {
bun_output::scoped_log!(WebSocketServer, "onDrain");
let handler = self.handler();
let vm = handler.vm();
if self.is_closed() || vm.is_shutting_down() {
if self.is_closed()
|| vm.script_execution_status() != bun_jsc::ScriptExecutionStatus::Running
{
return;
}

Expand Down Expand Up @@ -610,21 +611,25 @@ impl ServerWebSocket {
let cb = handler.on_ping;
let on_error = handler.on_error;
let vm = handler.vm();
if cb.is_empty_or_undefined_or_null() || vm.is_shutting_down() {
if cb.is_empty_or_undefined_or_null()
|| vm.script_execution_status() != bun_jsc::ScriptExecutionStatus::Running
{
return;
}
let global_this = handler.global_object();

// This is the start of a task.
let _loop_guard = vm.enter_event_loop_scope();

let Ok(payload) = self.binary_to_js(global_this, data) else {
return;
};
let args = [
self.this_value
.get()
.try_get()
.unwrap_or(JSValue::UNDEFINED),
self.binary_to_js(global_this, data)
.unwrap_or(JSValue::ZERO), // TODO: properly propagate exception upwards
payload,
];
if let Err(e) = cb.call(global_this, JSValue::UNDEFINED, &args) {
let err = global_this.take_exception(e);
Expand All @@ -646,20 +651,22 @@ impl ServerWebSocket {
let global_this = handler.global_object();
let vm = handler.vm();

if vm.is_shutting_down() {
if vm.script_execution_status() != bun_jsc::ScriptExecutionStatus::Running {
return;
}

// This is the start of a task.
let _loop_guard = vm.enter_event_loop_scope();

let Ok(payload) = self.binary_to_js(global_this, data) else {
return;
};
let args = [
self.this_value
.get()
.try_get()
.unwrap_or(JSValue::UNDEFINED),
self.binary_to_js(global_this, data)
.unwrap_or(JSValue::ZERO), // TODO: properly propagate exception upwards
payload,
];
if let Err(e) = cb.call(global_this, JSValue::UNDEFINED, &args) {
let err = global_this.take_exception(e);
Expand Down Expand Up @@ -723,7 +730,7 @@ impl ServerWebSocket {
});

let vm = handler.vm();
if vm.is_shutting_down() {
if vm.script_execution_status() != bun_jsc::ScriptExecutionStatus::Running {
return;
}

Expand Down
21 changes: 18 additions & 3 deletions src/runtime/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -554,7 +554,15 @@ impl<const SSL: bool, const DEBUG: bool> NewServer<SSL, DEBUG> {
/// still live (its WriteBarrier slots still root the handlers); only
/// `Finalized` means the slots are gone and the `config` shadows may point
/// at freed cells. Dispatch trampolines answer 503+close on `None`.
///
/// Also `None` once a worker's terminate() has armed the trap: a previous
/// request's handler in the same poll sweep may have left the sticky
/// TerminationException pending, and every request-family entry calls this
/// before its first JS entry.
Comment thread
robobun marked this conversation as resolved.
pub(crate) fn js_value_for_dispatch(&self) -> Option<JSValue> {
if self.vm().script_execution_status() != bun_jsc::ScriptExecutionStatus::Running {
return None;
}
Comment thread
robobun marked this conversation as resolved.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
self.js_value.try_get()
}
}
Expand Down Expand Up @@ -672,6 +680,13 @@ impl<const SSL: bool, const DEBUG: bool> NewServer<SSL, DEBUG> {
// S008: `Response<SSL>` is a ZST opaque — safe `*mut → &mut` deref.
let resp_ref = bun_opaque::opaque_deref_mut(resp);

// Belt-and-braces for `prepare_and_save_js_request_context` (bake),
// which reaches here without the `js_value_for_dispatch()` gate.
Comment thread
robobun marked this conversation as resolved.
if server.vm().script_execution_status() != bun_jsc::ScriptExecutionStatus::Running {
server_body::respond_stopped_503(resp_ref);
return None;
}

// We need to register the handler immediately since uSockets will not buffer.
//
// We first validate the self-reported request body length so that
Expand Down Expand Up @@ -1756,9 +1771,9 @@ impl<const SSL: bool, const DEBUG: bool> NewServer<SSL, DEBUG> {
}
if self.pending_requests == 0 && !self.has_listener() && !self.has_active_web_sockets() {
// Make the wrapper collectible. Dispatch still works while it is
// `Weak` (its WriteBarrier slots still root the handlers); the
// `js_value_for_dispatch` gate only trips once the wrapper is
// actually finalized.
// `Weak` (its WriteBarrier slots still root the handlers); see
// `js_value_for_dispatch` for the conditions under which the gate
// trips.
Comment thread
robobun marked this conversation as resolved.
self.js_value.downgrade();
if let Some(ws) = self.config.websocket.as_mut() {
ws.handler.app = None;
Expand Down
7 changes: 7 additions & 0 deletions src/runtime/shell/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1199,6 +1199,13 @@ impl Interpreter {
let global_this = self
.global_this_ref()
.expect("global_this set on Js event-loop path");
if global_this.bun_vm().script_execution_status()
!= bun_jsc::ScriptExecutionStatus::Running
{
self.keep_alive.with_mut(|k| k.disable());
self.deref_root_shell_and_io_if_needed(true);
return Yield::done();
}
let buffered_stdout = self.get_buffered_stdout(global_this);
let buffered_stderr = self.get_buffered_stderr(global_this);
self.keep_alive.with_mut(|k| k.disable());
Expand Down
6 changes: 3 additions & 3 deletions src/runtime/socket/Handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ impl Handlers {

pub(crate) fn resolve_promise(&self, value: JSValue) -> JsResult<()> {
let vm = self.vm;
if vm.is_shutting_down() {
if vm.script_execution_status() != bun_jsc::ScriptExecutionStatus::Running {
return Ok(());
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

Expand All @@ -231,7 +231,7 @@ impl Handlers {

pub(crate) fn reject_promise(&self, value: JSValue) -> JsResult<bool> {
let vm = self.vm;
if vm.is_shutting_down() {
if vm.script_execution_status() != bun_jsc::ScriptExecutionStatus::Running {
return Ok(true);
}

Expand Down Expand Up @@ -277,7 +277,7 @@ impl Handlers {

pub(crate) fn call_error_handler(&self, this_value: JSValue, args: &[JSValue; 2]) -> bool {
let vm = self.vm;
if vm.is_shutting_down() {
if vm.script_execution_status() != bun_jsc::ScriptExecutionStatus::Running {
return false;
}

Expand Down
9 changes: 5 additions & 4 deletions src/runtime/socket/Listener.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1688,7 +1688,7 @@ pub struct WindowsNamedPipeListeningContext {
pub(crate) listener: Option<bun_ptr::BackRef<Listener>>,
pub global_this: GlobalRef,
/// JSC_BORROW: process-lifetime singleton; `&'static` so call sites read
/// `self.vm.is_shutting_down()` without a raw-pointer deref.
/// `self.vm.<method>()` without a raw-pointer deref.
pub(crate) vm: &'static VirtualMachine,
pub ctx: Option<NonNull<boring_sys::SSL_CTX>>, // server reuses the same ctx
}
Expand All @@ -1713,7 +1713,8 @@ impl WindowsNamedPipeListeningContext {
// SAFETY: `this` is the `data` pointer libuv hands back; it was set to a
// live heap `WindowsNamedPipeListeningContext` in `listen_named_pipe`.
let this_ref = unsafe { &mut *this };
let shutting_down = this_ref.vm.is_shutting_down();
let shutting_down =
this_ref.vm.script_execution_status() != bun_jsc::ScriptExecutionStatus::Running;
Comment thread
robobun marked this conversation as resolved.
if status != uv::ReturnCode::ZERO || shutting_down || this_ref.listener.is_none() {
// connection dropped or vm is shutting down or we are deiniting/closing
return;
Expand Down Expand Up @@ -1915,7 +1916,7 @@ pub(crate) extern "C" fn us_dispatch_socket_server_name(
return core::ptr::null_mut();
}
let handlers = tls.get_handlers();
if handlers.vm.is_shutting_down() {
if handlers.vm.script_execution_status() != bun_jsc::ScriptExecutionStatus::Running {
return core::ptr::null_mut();
}
let callback = handlers.on_server_name();
Expand Down Expand Up @@ -2008,7 +2009,7 @@ extern "C" fn us_dispatch_server_name(
// duration of this synchronous handshake dispatch.
let listener = unsafe { bun_ptr::ThisPtr::new(listener_ptr) };
let handlers = &listener.handlers;
if handlers.vm.is_shutting_down() {
if handlers.vm.script_execution_status() != bun_jsc::ScriptExecutionStatus::Running {
return core::ptr::null_mut();
}
let callback = handlers.on_server_name();
Expand Down
5 changes: 4 additions & 1 deletion src/runtime/socket/UpgradedDuplex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ impl UpgradedDuplex {
// `vm` is always set via `from()`; `None` only in the zeroed placeholder
// state, which never reaches here.
let Some(vm) = self.vm else { return };
if vm.is_shutting_down() {
if vm.script_execution_status() != bun_jsc::ScriptExecutionStatus::Running {
return;
}
let duplex = self.origin;
Expand All @@ -229,6 +229,9 @@ impl UpgradedDuplex {
// Best-effort probe: consume the exception and fall through.
Err(err) => drop(global.take_exception(err)),
}
if global.has_exception() {
return;
}
}

let name = if msg_more { "write" } else { "end" };
Expand Down
24 changes: 15 additions & 9 deletions src/runtime/socket/socket_body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,11 @@ extern "C" fn select_alpn_callback(
{
let handlers = this.get_handlers();
let callback = handlers.on_alpn_callback();
if !callback.is_empty() && !handlers.vm.is_shutting_down() && !in_.is_null() && inlen > 0 {
if !callback.is_empty()
&& handlers.vm.script_execution_status() == jsc::ScriptExecutionStatus::Running
&& !in_.is_null()
&& inlen > 0
{
Comment thread
robobun marked this conversation as resolved.
let scope = handlers.enter();
let global = handlers.global_object;
let this_value = this.get_this_value(&global);
Expand Down Expand Up @@ -838,7 +842,7 @@ impl<const SSL: bool> NewSocket<SSL> {
log!("handleError");
let handlers = self.get_handlers();
let vm = handlers.vm;
if vm.is_shutting_down() {
if vm.script_execution_status() != jsc::ScriptExecutionStatus::Running {
return;
}
// the handlers must be kept alive for the duration of the function call
Expand Down Expand Up @@ -879,7 +883,7 @@ impl<const SSL: bool> NewSocket<SSL> {
}

let vm = handlers.vm;
if vm.is_shutting_down() {
if vm.script_execution_status() != jsc::ScriptExecutionStatus::Running {
return;
}
// Hold the socket alive for the rest of the dispatch: `internal_flush`
Expand Down Expand Up @@ -971,7 +975,7 @@ impl<const SSL: bool> NewSocket<SSL> {
if callback.is_empty() || this.flags.get().contains(Flags::FINALIZING) {
return;
}
if handlers.vm.is_shutting_down() {
if handlers.vm.script_execution_status() != jsc::ScriptExecutionStatus::Running {
return;
}

Expand Down Expand Up @@ -1615,7 +1619,9 @@ impl<const SSL: bool> NewSocket<SSL> {

let callback = handlers.on_end();
let vm = handlers.vm;
if callback.is_empty() || vm.is_shutting_down() {
if callback.is_empty()
|| vm.script_execution_status() != jsc::ScriptExecutionStatus::Running
{
this.poll_ref.with_mut(|p| p.unref(js_loop_ctx()));

// If you don't handle TCP fin, we assume you're done.
Expand Down Expand Up @@ -1758,8 +1764,8 @@ impl<const SSL: bool> NewSocket<SSL> {
let mut is_open = false;

if handlers.vm.script_execution_status() != jsc::ScriptExecutionStatus::Running {
// `on_close` is single-JS-entry, so the native close it routes
// through here just takes the trap and returns.
// `on_close` skips its JS dispatch under the same gate, so the
// native close is still safe here.
Comment thread
robobun marked this conversation as resolved.
if reject_unauthorized {
this.reject_unauthorized_connection();
}
Expand Down Expand Up @@ -2036,7 +2042,7 @@ impl<const SSL: bool> NewSocket<SSL> {
return Ok(());
}

if vm.is_shutting_down() {
if vm.script_execution_status() != jsc::ScriptExecutionStatus::Running {
drop(cleanup);
return Ok(());
}
Expand Down Expand Up @@ -2112,7 +2118,7 @@ impl<const SSL: bool> NewSocket<SSL> {
if callback.is_empty() || this.flags.get().contains(Flags::FINALIZING) {
return;
}
if handlers.vm.is_shutting_down() {
if handlers.vm.script_execution_status() != jsc::ScriptExecutionStatus::Running {
return;
}

Expand Down
Loading
Loading