Skip to content

Commit 5707665

Browse files
authored
fix: beforeunload event didn't trigger on the early drop state (#455)
* fix: `beforeunload` event didn't trigger on the early drop state * stamp: polishing * stamp: rid comments
1 parent 18d8f69 commit 5707665

File tree

4 files changed

+128
-71
lines changed

4 files changed

+128
-71
lines changed

crates/base/src/deno_runtime.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@ pub enum WillTerminateReason {
269269
CPU,
270270
Memory,
271271
WallClock,
272+
EarlyDrop,
272273
}
273274

274275
pub struct DenoRuntime<RuntimeContext = DefaultRuntimeContext> {

crates/base/src/rt_worker/supervisor/mod.rs

Lines changed: 54 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -26,40 +26,6 @@ use crate::{
2626

2727
use super::{worker_ctx::TerminationToken, worker_pool::SupervisorPolicy};
2828

29-
#[repr(C)]
30-
pub struct V8HandleTerminationData {
31-
pub should_terminate: bool,
32-
pub isolate_memory_usage_tx: Option<oneshot::Sender<IsolateMemoryStats>>,
33-
}
34-
35-
pub extern "C" fn v8_handle_termination(isolate: &mut v8::Isolate, data: *mut std::ffi::c_void) {
36-
let mut boxed_data: Box<V8HandleTerminationData>;
37-
38-
unsafe {
39-
boxed_data = Box::from_raw(data as *mut V8HandleTerminationData);
40-
}
41-
42-
// log memory usage
43-
let mut heap_stats = v8::HeapStatistics::default();
44-
45-
isolate.get_heap_statistics(&mut heap_stats);
46-
47-
let usage = IsolateMemoryStats {
48-
used_heap_size: heap_stats.used_heap_size(),
49-
external_memory: heap_stats.external_memory(),
50-
};
51-
52-
if let Some(usage_tx) = boxed_data.isolate_memory_usage_tx.take() {
53-
if usage_tx.send(usage).is_err() {
54-
error!("failed to send isolate memory usage - receiver may have been dropped");
55-
}
56-
}
57-
58-
if boxed_data.should_terminate {
59-
isolate.terminate_execution();
60-
}
61-
}
62-
6329
#[repr(C)]
6430
pub struct IsolateMemoryStats {
6531
pub used_heap_size: usize,
@@ -172,20 +138,73 @@ async fn create_wall_clock_beforeunload_alert(wall_clock_limit_ms: u64, pct: Opt
172138
}
173139
}
174140

141+
#[repr(C)]
142+
pub struct V8HandleTerminationData {
143+
pub should_terminate: bool,
144+
pub isolate_memory_usage_tx: Option<oneshot::Sender<IsolateMemoryStats>>,
145+
}
146+
147+
pub extern "C" fn v8_handle_termination(isolate: &mut v8::Isolate, data: *mut std::ffi::c_void) {
148+
let mut data = unsafe { Box::from_raw(data as *mut V8HandleTerminationData) };
149+
150+
// log memory usage
151+
let mut heap_stats = v8::HeapStatistics::default();
152+
153+
isolate.get_heap_statistics(&mut heap_stats);
154+
155+
let usage = IsolateMemoryStats {
156+
used_heap_size: heap_stats.used_heap_size(),
157+
external_memory: heap_stats.external_memory(),
158+
};
159+
160+
if let Some(usage_tx) = data.isolate_memory_usage_tx.take() {
161+
if usage_tx.send(usage).is_err() {
162+
error!("failed to send isolate memory usage - receiver may have been dropped");
163+
}
164+
}
165+
166+
if data.should_terminate {
167+
isolate.terminate_execution();
168+
}
169+
}
170+
175171
extern "C" fn v8_handle_wall_clock_beforeunload(
176172
isolate: &mut v8::Isolate,
177173
_data: *mut std::ffi::c_void,
178174
) {
179175
if let Err(err) = MaybeDenoRuntime::<()>::Isolate(isolate)
180176
.dispatch_beforeunload_event(WillTerminateReason::WallClock)
181177
{
182-
warn!(
178+
error!(
183179
"found an error while dispatching the beforeunload event: {}",
184180
err
185181
);
186182
}
187183
}
188184

185+
#[repr(C)]
186+
pub struct V8HandleEarlyRetireData {
187+
token: CancellationToken,
188+
}
189+
190+
extern "C" fn v8_handle_early_drop_beforeunload(
191+
isolate: &mut v8::Isolate,
192+
data: *mut std::ffi::c_void,
193+
) {
194+
let data = unsafe { Box::from_raw(data as *mut V8HandleEarlyRetireData) };
195+
196+
if let Err(err) = MaybeDenoRuntime::<()>::Isolate(isolate)
197+
.dispatch_beforeunload_event(WillTerminateReason::EarlyDrop)
198+
{
199+
error!(
200+
"found an error while dispatching the beforeunload event: {}",
201+
err
202+
);
203+
} else {
204+
data.token.cancel();
205+
}
206+
}
207+
189208
#[instrument(level = "debug", skip_all)]
190209
extern "C" fn v8_handle_early_retire(isolate: &mut v8::Isolate, _data: *mut std::ffi::c_void) {
191210
isolate.low_memory_notification();

crates/base/src/rt_worker/supervisor/strategy_per_worker.rs

Lines changed: 73 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ use std::{future::pending, sync::atomic::Ordering, time::Duration};
44
use std::thread::ThreadId;
55

66
use event_worker::events::ShutdownReason;
7-
use log::error;
7+
use log::{error, info};
88
use sb_workers::context::{Timing, TimingStatus, UserWorkerMsgs};
9+
use tokio_util::sync::CancellationToken;
910

1011
use crate::rt_worker::supervisor::{
11-
create_wall_clock_beforeunload_alert, v8_handle_early_retire,
12-
v8_handle_wall_clock_beforeunload, wait_cpu_alarm, CPUUsage, Tokens,
12+
create_wall_clock_beforeunload_alert, v8_handle_early_drop_beforeunload,
13+
v8_handle_early_retire, v8_handle_wall_clock_beforeunload, wait_cpu_alarm, CPUUsage, Tokens,
14+
V8HandleEarlyRetireData,
1315
};
1416

1517
use super::{v8_handle_termination, Arguments, CPUUsageMetrics, V8HandleTerminationData};
@@ -57,12 +59,13 @@ pub async fn supervise(args: Arguments) -> (ShutdownReason, i64) {
5759
let mut is_worker_entered = false;
5860
let mut is_wall_clock_beforeunload_armed = false;
5961
let mut is_cpu_time_soft_limit_reached = false;
60-
let mut is_termination_requested = false;
62+
let mut is_waiting_for_termination = false;
6163
let mut have_all_reqs_been_acknowledged = false;
6264

6365
let mut cpu_usage_metrics_rx = cpu_usage_metrics_rx.unwrap();
6466
let mut cpu_usage_ms = 0i64;
6567

68+
let mut complete_reason = None::<ShutdownReason>;
6669
let mut wall_clock_alerts = 0;
6770
let mut req_ack_count = 0usize;
6871

@@ -97,6 +100,25 @@ pub async fn supervise(args: Arguments) -> (ShutdownReason, i64) {
97100
guard.raise();
98101
};
99102

103+
let early_drop_token = CancellationToken::new();
104+
let early_drop_fut = early_drop_token.cancelled();
105+
106+
let mut dispatch_early_drop_beforeunload_fn = Some({
107+
let token = early_drop_token.clone();
108+
|| {
109+
let data_ptr_mut = Box::into_raw(Box::new(V8HandleEarlyRetireData { token }));
110+
111+
if !thread_safe_handle.request_interrupt(
112+
v8_handle_early_drop_beforeunload,
113+
data_ptr_mut as *mut std::ffi::c_void,
114+
) {
115+
drop(unsafe { Box::from_raw(data_ptr_mut) });
116+
} else {
117+
waker.wake();
118+
}
119+
}
120+
});
121+
100122
let terminate_fn = {
101123
let thread_safe_handle = thread_safe_handle.clone();
102124
move || {
@@ -115,23 +137,25 @@ pub async fn supervise(args: Arguments) -> (ShutdownReason, i64) {
115137

116138
tokio::pin!(wall_clock_duration_alert);
117139
tokio::pin!(wall_clock_beforeunload_alert);
140+
tokio::pin!(early_drop_fut);
118141

119-
let result = 'scope: loop {
142+
loop {
120143
tokio::select! {
121144
_ = supervise.cancelled() => {
122-
break 'scope (ShutdownReason::TerminationRequested, cpu_usage_ms);
145+
complete_reason = Some(ShutdownReason::TerminationRequested);
123146
}
124147

125148
_ = async {
126149
match termination.as_ref() {
127150
Some(token) => token.inbound.cancelled().await,
128151
None => pending().await,
129152
}
130-
}, if !is_termination_requested => {
131-
is_termination_requested = true;
153+
}, if !is_waiting_for_termination => {
154+
is_waiting_for_termination = true;
132155
if promise_metrics.have_all_promises_been_resolved() {
133-
terminate_fn();
134-
break 'scope (ShutdownReason::TerminationRequested, cpu_usage_ms);
156+
if let Some(func) = dispatch_early_drop_beforeunload_fn.take() {
157+
func();
158+
}
135159
}
136160
}
137161

@@ -164,9 +188,8 @@ pub async fn supervise(args: Arguments) -> (ShutdownReason, i64) {
164188

165189
if !cpu_timer_param.is_disabled() {
166190
if cpu_usage_ms >= hard_limit_ms as i64 {
167-
terminate_fn();
168191
error!("CPU time hard limit reached: isolate: {:?}", key);
169-
break 'scope (ShutdownReason::CPUTime, cpu_usage_ms);
192+
complete_reason = Some(ShutdownReason::CPUTime);
170193
} else if cpu_usage_ms >= soft_limit_ms as i64 && !is_cpu_time_soft_limit_reached {
171194
early_retire_fn();
172195
error!("CPU time soft limit reached: isolate: {:?}", key);
@@ -177,17 +200,18 @@ pub async fn supervise(args: Arguments) -> (ShutdownReason, i64) {
177200
if have_all_reqs_been_acknowledged
178201
&& promise_metrics.have_all_promises_been_resolved()
179202
{
180-
terminate_fn();
181-
error!("early termination due to the last request being completed: isolate: {:?}", key);
182-
break 'scope (ShutdownReason::EarlyDrop, cpu_usage_ms);
203+
if let Some(func) = dispatch_early_drop_beforeunload_fn.take() {
204+
func();
205+
}
183206
}
184207

185208
} else if is_cpu_time_soft_limit_reached
186209
&& have_all_reqs_been_acknowledged
187210
&& promise_metrics.have_all_promises_been_resolved()
188211
{
189-
terminate_fn();
190-
break 'scope (ShutdownReason::EarlyDrop, cpu_usage_ms);
212+
if let Some(func) = dispatch_early_drop_beforeunload_fn.take() {
213+
func();
214+
}
191215
}
192216
}
193217
}
@@ -206,14 +230,13 @@ pub async fn supervise(args: Arguments) -> (ShutdownReason, i64) {
206230
if have_all_reqs_been_acknowledged
207231
&& promise_metrics.have_all_promises_been_resolved()
208232
{
209-
terminate_fn();
210-
error!("early termination due to the last request being completed: isolate: {:?}", key);
211-
break 'scope (ShutdownReason::EarlyDrop, cpu_usage_ms);
233+
if let Some(func) = dispatch_early_drop_beforeunload_fn.take() {
234+
func();
235+
}
212236
}
213237
} else {
214-
terminate_fn();
215238
error!("CPU time hard limit reached: isolate: {:?}", key);
216-
break 'scope (ShutdownReason::CPUTime, cpu_usage_ms);
239+
complete_reason = Some(ShutdownReason::CPUTime);
217240
}
218241
}
219242
}
@@ -237,9 +260,9 @@ pub async fn supervise(args: Arguments) -> (ShutdownReason, i64) {
237260
continue;
238261
}
239262

240-
terminate_fn();
241-
error!("early termination due to the last request being completed: isolate: {:?}", key);
242-
break 'scope (ShutdownReason::EarlyDrop, cpu_usage_ms);
263+
if let Some(func) = dispatch_early_drop_beforeunload_fn.take() {
264+
func();
265+
}
243266
}
244267

245268
_ = wall_clock_duration_alert.tick(), if !is_wall_clock_limit_disabled => {
@@ -253,9 +276,8 @@ pub async fn supervise(args: Arguments) -> (ShutdownReason, i64) {
253276
} else {
254277
let is_in_flight_req_exists = req_ack_count != demand.load(Ordering::Acquire);
255278

256-
terminate_fn();
257279
error!("wall clock duration reached: isolate: {:?} (in_flight_req_exists = {})", key, is_in_flight_req_exists);
258-
break 'scope (ShutdownReason::WallClockTime, cpu_usage_ms);
280+
complete_reason = Some(ShutdownReason::WallClockTime);
259281
}
260282
}
261283

@@ -273,18 +295,34 @@ pub async fn supervise(args: Arguments) -> (ShutdownReason, i64) {
273295
}
274296

275297
Some(_) = memory_limit_rx.recv() => {
276-
terminate_fn();
277298
error!("memory limit reached for the worker: isolate: {:?}", key);
278-
break 'scope (ShutdownReason::Memory, cpu_usage_ms);
299+
complete_reason = Some(ShutdownReason::Memory);
279300
}
280-
}
281-
};
282301

283-
match result {
284-
(ShutdownReason::EarlyDrop, cpu_usage_ms) if is_termination_requested => {
285-
(ShutdownReason::TerminationRequested, cpu_usage_ms)
302+
_ = &mut early_drop_fut => {
303+
info!("early termination has been triggered: isolate: {:?}", key);
304+
complete_reason = Some(ShutdownReason::EarlyDrop);
305+
}
286306
}
287307

288-
result => result,
308+
match complete_reason.take() {
309+
Some(ShutdownReason::EarlyDrop) => {
310+
terminate_fn();
311+
return (
312+
if is_waiting_for_termination {
313+
ShutdownReason::TerminationRequested
314+
} else {
315+
ShutdownReason::EarlyDrop
316+
},
317+
cpu_usage_ms,
318+
);
319+
}
320+
321+
Some(result) => {
322+
terminate_fn();
323+
return (result, cpu_usage_ms);
324+
}
325+
None => continue,
326+
}
289327
}
290328
}

crates/base/test_cases/main/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ Deno.serve((req: Request) => {
3737
const workerTimeoutMs = parseIntFromHeadersOrDefault(req, "x-worker-timeout-ms", 10 * 60 * 1000);
3838
const cpuTimeSoftLimitMs = parseIntFromHeadersOrDefault(req, "x-cpu-time-soft-limit-ms", 10 * 60 * 1000);
3939
const cpuTimeHardLimitMs = parseIntFromHeadersOrDefault(req, "x-cpu-time-hard-limit-ms", 10 * 60 * 1000);
40-
console.log(cpuTimeSoftLimitMs);
4140
const noModuleCache = false;
4241
const importMapPath = null;
4342
const envVarsObj = Deno.env.toObject();

0 commit comments

Comments
 (0)