feat(cdp): bound every CDP command with a shared V8 watchdog (#250)
A hung page (a runaway Runtime.evaluate, a synchronous DOM op) holds the process-wide V8 lock and wedges every other session forever. The one-shot CLI has a process-level hard deadline for this; the long-running server cannot force-exit, so terminate just the offending isolate instead. dispatch() arms a watchdog on the session page's isolate after taking the V8 lock and disarms it after the handler; on overrun it terminates the isolate and clears the termination flag so the session keeps working. OBSCURA_CDP_COMMAND_TIMEOUT_MS tunes the bound (default 60s, 0 disables); navigation already self-bounds well under it. The watchdog is a single long-lived shared thread (cdp_watchdog), not a thread per command: the V8 lock serializes commands so at most one is ever armed, and arm/disarm are a mutex plus notify with no measurable per-command cost. Also fixes a lost-wakeup in the per-call watchdog (spawn_watchdog) where disarming a fast call before the watchdog thread started blocked for the full budget. Verified: a hung Runtime.evaluate is terminated at the budget and the session recovers; CDP integration 37/37, obstacle course 33/33; per-command latency unchanged (watchdog on equals off within noise).
This commit is contained in:
1 parent
32ddf93885
commit
1a0644f7bc
5 files changed
+255
-34
No files matched your search
@@ -1131,6 +1131,22 @@ impl Page {
|
||||
self.dom.as_ref()
|
||||
}
|
||||
|
||||
/// V8 isolate handle for this page's runtime, if it has been initialized.
|
||||
/// Lets the CDP dispatcher arm a per-command watchdog (which bounds any one
|
||||
/// command so a hung page cannot hold the process-wide V8 lock forever)
|
||||
/// without taking `&mut self`.
|
||||
pub fn isolate_handle(&self) -> Option<obscura_js::runtime::IsolateHandle> {
|
||||
self.js.as_ref().map(|js| js.isolate_handle())
|
||||
}
|
||||
|
||||
/// Clear a V8 termination left by a per-command watchdog so the next command
|
||||
/// on this page can run. No-op if the runtime is absent or not terminating.
|
||||
pub fn cancel_v8_termination(&mut self) {
|
||||
if let Some(js) = self.js.as_mut() {
|
||||
js.cancel_termination();
|
||||
}
|
||||
}
|
||||
|
||||
/// Like [`Self::evaluate`] but bounded by a V8 watchdog so a runaway
|
||||
/// expression cannot hang the process. A non-zero `timeout` of zero falls
|
||||
/// back to the unbounded path.
|
||||
|
||||
Reference in new issue
Block a user