Skip to content

Commit 62051c8

Browse files
authored
fix(base): triggering beforeunload event may cause Uncaught null (#520)
* fix(base): triggering `beforeunload` event may cause `Uncaught null` * chore: add an integration test
1 parent 44922b1 commit 62051c8

File tree

6 files changed

+110
-24
lines changed

6 files changed

+110
-24
lines changed

crates/base/src/deno_runtime.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1328,10 +1328,12 @@ where
13281328
if accumulated_cpu_time_ns >= threshold_ns {
13291329
beforeunload_cpu_threshold.store(None);
13301330

1331-
if let Err(err) = MaybeDenoRuntime::DenoRuntime(&mut this)
1332-
.dispatch_beforeunload_event(WillTerminateReason::CPU)
1333-
{
1334-
return Poll::Ready(Err(err));
1331+
if !state.is_terminated() {
1332+
if let Err(err) = MaybeDenoRuntime::DenoRuntime(&mut this)
1333+
.dispatch_beforeunload_event(WillTerminateReason::CPU)
1334+
{
1335+
return Poll::Ready(Err(err));
1336+
}
13351337
}
13361338
}
13371339
}
@@ -1351,7 +1353,7 @@ where
13511353
if total_malloced_bytes >= threshold_bytes {
13521354
beforeunload_mem_threshold.store(None);
13531355

1354-
if !mem_state.is_exceeded() {
1356+
if !state.is_terminated() && !mem_state.is_exceeded() {
13551357
if let Err(err) = MaybeDenoRuntime::DenoRuntime(&mut this)
13561358
.dispatch_beforeunload_event(WillTerminateReason::Memory)
13571359
{

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,13 +210,17 @@ pub async fn supervise(args: Arguments) -> (ShutdownReason, i64) {
210210
});
211211

212212
let terminate_fn = {
213+
let state = state.runtime.clone();
213214
let thread_safe_handle = thread_safe_handle.clone();
214215
move |should_terminate: bool| {
215216
let data_ptr_mut = Box::into_raw(Box::new(V8HandleTerminationData {
216217
should_terminate,
217218
isolate_memory_usage_tx: Some(isolate_memory_usage_tx),
218219
}));
219220

221+
if should_terminate {
222+
state.terminated.raise();
223+
}
220224
if !thread_safe_handle
221225
.request_interrupt(v8_handle_termination, data_ptr_mut as *mut std::ffi::c_void)
222226
{
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { FFmpeg } from 'npm:@ffmpeg.wasm/main';
2+
import { default as core, type FFmpegCoreConstructor } from 'npm:@ffmpeg.wasm/core-st';
3+
4+
const ff = await FFmpeg.create({
5+
core: core as FFmpegCoreConstructor,
6+
});
7+
8+
Deno.serve(async (_req) => {
9+
const resp = await fetch(
10+
'https://raw.githubusercontent.com/ffmpegwasm/testdata/master/Big_Buck_Bunny_180_10s.webm',
11+
);
12+
const buf = new Uint8Array(await resp.arrayBuffer());
13+
14+
ff.fs.writeFile('meow.webm', buf);
15+
16+
await ff.run('-i', 'meow.webm', 'meow.mp4');
17+
const data = ff.fs.readFile('meow.mp4');
18+
19+
return new Response(data, {
20+
headers: {
21+
'content-type': 'video/mp4',
22+
},
23+
});
24+
});

crates/base/test_cases/main/index.ts

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,31 +18,44 @@ Deno.serve((req: Request) => {
1818
console.log(req.url);
1919
const url = new URL(req.url);
2020
const { pathname } = url;
21-
const path_parts = pathname.split("/");
21+
const path_parts = pathname.split('/');
2222
const service_name = path_parts[1];
2323

24-
if (!service_name || service_name === "") {
25-
const error = { msg: "missing function name in request" }
24+
if (!service_name || service_name === '') {
25+
const error = { msg: 'missing function name in request' };
2626
return new Response(
2727
JSON.stringify(error),
28-
{ status: 400, headers: { "Content-Type": "application/json" } },
29-
)
28+
{ status: 400, headers: { 'Content-Type': 'application/json' } },
29+
);
3030
}
3131

3232
const servicePath = `./test_cases/${service_name}`;
3333
console.error(`serving the request with ${servicePath}`);
3434

3535
const createWorker = async () => {
36-
const memoryLimitMb = parseIntFromHeadersOrDefault(req, "x-memory-limit-mb", 150);
37-
const workerTimeoutMs = parseIntFromHeadersOrDefault(req, "x-worker-timeout-ms", 10 * 60 * 1000);
38-
const cpuTimeSoftLimitMs = parseIntFromHeadersOrDefault(req, "x-cpu-time-soft-limit-ms", 10 * 60 * 1000);
39-
const cpuTimeHardLimitMs = parseIntFromHeadersOrDefault(req, "x-cpu-time-hard-limit-ms", 10 * 60 * 1000);
36+
const memoryLimitMb = parseIntFromHeadersOrDefault(req, 'x-memory-limit-mb', 150);
37+
const workerTimeoutMs = parseIntFromHeadersOrDefault(
38+
req,
39+
'x-worker-timeout-ms',
40+
10 * 60 * 1000,
41+
);
42+
const cpuTimeSoftLimitMs = parseIntFromHeadersOrDefault(
43+
req,
44+
'x-cpu-time-soft-limit-ms',
45+
10 * 60 * 1000,
46+
);
47+
const cpuTimeHardLimitMs = parseIntFromHeadersOrDefault(
48+
req,
49+
'x-cpu-time-hard-limit-ms',
50+
10 * 60 * 1000,
51+
);
4052
const noModuleCache = false;
4153
const importMapPath = null;
4254
const envVarsObj = Deno.env.toObject();
43-
const envVars = Object.keys(envVarsObj).map(k => [k, envVarsObj[k]]);
55+
const envVars = Object.keys(envVarsObj).map((k) => [k, envVarsObj[k]]);
4456
const context = {
45-
sourceMap: req.headers.get("x-context-source-map") == "true"
57+
sourceMap: req.headers.get('x-context-source-map') == 'true',
58+
useReadSyncFileAPI: req.headers.get('x-use-read-sync-file-api') == 'true',
4659
};
4760

4861
return await EdgeRuntime.userWorkers.create({
@@ -54,9 +67,9 @@ Deno.serve((req: Request) => {
5467
noModuleCache,
5568
importMapPath,
5669
envVars,
57-
context
70+
context,
5871
});
59-
}
72+
};
6073

6174
const callWorker = async () => {
6275
try {
@@ -69,13 +82,13 @@ Deno.serve((req: Request) => {
6982
// return await callWorker();
7083
// }
7184

72-
const error = { msg: e.toString() }
85+
const error = { msg: e.toString() };
7386
return new Response(
7487
JSON.stringify(error),
75-
{ status: 500, headers: { "Content-Type": "application/json" } },
88+
{ status: 500, headers: { 'Content-Type': 'application/json' } },
7689
);
7790
}
78-
}
91+
};
7992

8093
return callWorker();
81-
})
94+
});

crates/base/tests/integration_tests.rs

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use graph::{emitter::EmitterFactory, generate_binary_eszip, EszipPayloadKind};
66
use http_v02::{self as http, HeaderValue};
77
use hyper_v014 as hyper;
88
use reqwest_v011 as reqwest;
9-
use sb_event_worker::events::{LogLevel, WorkerEvents};
9+
use sb_event_worker::events::{LogLevel, ShutdownReason, WorkerEvents};
1010
use url::Url;
1111

1212
use std::{
@@ -2384,6 +2384,49 @@ async fn test_issue_456() {
23842384
tb.exit(Duration::from_secs(TESTBED_DEADLINE_SEC)).await;
23852385
}
23862386

2387+
#[tokio::test]
2388+
#[serial]
2389+
async fn test_issue_func_205() {
2390+
let (tx, mut rx) = mpsc::unbounded_channel();
2391+
let tb = TestBedBuilder::new("./test_cases/main")
2392+
.with_per_worker_policy(None)
2393+
.with_worker_event_sender(Some(tx))
2394+
.with_server_flags(ServerFlags {
2395+
beforeunload_wall_clock_pct: Some(90),
2396+
beforeunload_cpu_pct: Some(90),
2397+
beforeunload_memory_pct: Some(90),
2398+
..Default::default()
2399+
})
2400+
.build()
2401+
.await;
2402+
2403+
let resp = tb
2404+
.request(|b| {
2405+
b.uri("/issue-func-205")
2406+
.header("x-cpu-time-soft-limit-ms", HeaderValue::from_static("500"))
2407+
.header("x-cpu-time-hard-limit-ms", HeaderValue::from_static("1000"))
2408+
.header("x-use-read-sync-file-api", HeaderValue::from_static("true"))
2409+
.body(Body::empty())
2410+
.context("can't make request")
2411+
})
2412+
.await
2413+
.unwrap();
2414+
2415+
assert_eq!(resp.status().as_u16(), StatusCode::INTERNAL_SERVER_ERROR);
2416+
2417+
tb.exit(Duration::from_secs(TESTBED_DEADLINE_SEC)).await;
2418+
2419+
while let Some(ev) = rx.recv().await {
2420+
let WorkerEvents::Shutdown(ev) = ev.event else {
2421+
continue;
2422+
};
2423+
assert_eq!(ev.reason, ShutdownReason::CPUTime);
2424+
return;
2425+
}
2426+
2427+
unreachable!("test failed");
2428+
}
2429+
23872430
#[tokio::test]
23882431
#[serial]
23892432
async fn test_should_render_detailed_failed_to_create_graph_error() {

ext/event_worker/events.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ pub struct WorkerMemoryUsed {
2020
pub mem_check_captured: MemCheckState,
2121
}
2222

23-
#[derive(Serialize, Deserialize, Debug)]
23+
#[derive(Serialize, Deserialize, Debug, PartialEq)]
2424
pub enum ShutdownReason {
2525
EventLoopCompleted,
2626
WallClockTime,

0 commit comments

Comments
 (0)