fix(js): drive the event loop cancellation-safely to avoid isolate leaks
Several event-loop operations bounded their runtime with tokio::time::timeout(budget, run_event_loop()) or a select! against a timer. When the timer wins, tokio drops the run_event_loop future mid-poll; deno_core enters the isolate while polling, so the drop leaves the isolate entered on the OS thread. Under concurrent CDP work a second page's execute_script then trips heap->isolate() == TryGetCurrent() and aborts the process (#430). Drive the loop with poll_event_loop inside a poll_fn instead. It creates and drops its own isolate scope per call, so stopping between ticks never leaves the isolate entered. Convert resolve_promises_until, run_event_loop_bounded, resolve_promises, the mod_evaluate settle in load_module and load_inline_module, and the two navigation settle loops in page.rs. Add poll_event_loop_once as the cancellation-safe single-tick primitive the settle loops use. Keeps the obstacle course at 33/33. This is hardening toward #430; the residual concurrency abort still needs the thread-per-runtime refactor that v8_lock.rs calls the "Option 2" fix. Refs #430.
This commit is contained in:
1 parent
72d66bc5c0
commit
45df429d99
2 files changed
+168
-80
No files matched your search
@@ -763,13 +763,13 @@ impl Page {
|
||||
if tokio::time::Instant::now() >= deadline {
|
||||
break;
|
||||
}
|
||||
let result = tokio::time::timeout(
|
||||
tokio::time::Duration::from_millis(10),
|
||||
js.run_event_loop(),
|
||||
).await;
|
||||
|
||||
match result {
|
||||
Ok(Ok(())) => {
|
||||
// Cancellation-safe single tick: a per-poll `tokio::time::timeout`
|
||||
// wrapping `run_event_loop` would drop that future mid-poll and
|
||||
// leave this isolate entered on the thread, aborting the process
|
||||
// under concurrent pages (#430). `poll_event_loop_once` exits the
|
||||
// isolate before returning, so the tick is safe to bound.
|
||||
match js.poll_event_loop_once().await {
|
||||
Some(Ok(())) => {
|
||||
if self.http_client.active_requests() == 0 {
|
||||
idle_count += 1;
|
||||
if idle_count >= 2 {
|
||||
@@ -781,9 +781,10 @@ impl Page {
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(1)).await;
|
||||
}
|
||||
}
|
||||
Ok(Err(_)) => break,
|
||||
Err(_) => {
|
||||
Some(Err(_)) => break,
|
||||
None => {
|
||||
idle_count = 0;
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(1)).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1226,10 +1227,11 @@ impl Page {
|
||||
}
|
||||
|
||||
if let Some(js) = &mut self.js {
|
||||
let _ = tokio::time::timeout(
|
||||
tokio::time::Duration::from_millis(50),
|
||||
js.run_event_loop(),
|
||||
).await;
|
||||
// Cancellation-safe tick (see #430): never drop a
|
||||
// `run_event_loop` future mid-poll, which leaks the entered
|
||||
// isolate. `poll_event_loop_once` exits the isolate per tick.
|
||||
let _ = js.poll_event_loop_once().await;
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(1)).await;
|
||||
} else {
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user