perf(cdp): scope the V8 lock per connection to run connections in parallel

With every connection on its own thread (previous commit), the process-wide V8
lock served no safety purpose across connections yet forced all of them to
serialize on one mutex, so throughput did not scale with concurrency. Move the
lock into CdpContext so each connection has its own: a connection's nav task and
command dispatch still coordinate on one page's isolate at a time on its thread,
but different connections no longer block each other. Delete the now-unused
process-wide obscura_js::v8_lock.

Measured on a local CPU-bound fixture, page throughput stays flat (~75/s from
concurrency 1 to 8) with the global lock and scales to about 2.5x at concurrency
8 with the per-connection lock, with no crash at any concurrency. Obstacle
course 33/33, obscura-cdp 76/76.

Refs #430.
This commit is contained in:
sycatle committed 2026-07-20 16:37:50 +02:00
1 parent 6c7b2b0cec
commit 9065f383de
4 files changed
+21 -34

No files matched your search

+7 -5
View File
@@ -673,13 +673,15 @@ async fn process_with_interception(
let (nav_done_tx, mut nav_done_rx) = mpsc::channel::<(obscura_browser::Page, Result<(), String>)>(1);
let url_owned = url.to_string();
let nav_v8_lock = ctx.v8_lock.clone();
tokio::task::spawn_local(async move {
// Issue #19: serialize V8 work across pages. The interception path
// spawns navigation here while the parent task continues to pump
// CDP messages via `dispatch` (which also acquires this lock); both
// sides must coordinate or V8 aborts the process at concurrency >= 5.
let _v8_guard = obscura_js::v8_lock::global().lock().await;
// Issue #19: serialize this connection's V8 work across its pages. This
// nav task runs while the connection's processor keeps pumping other CDP
// messages via `dispatch` (which takes the same per-connection lock), so
// both sides coordinate on one page's isolate at a time on this thread.
// The lock is per-connection, so other connections are unaffected (#430).
let _v8_guard = nav_v8_lock.lock_owned().await;
// Preloads (addBinding shims, addScriptToEvaluateOnNewDocument sources)
// must run BEFORE the page's own scripts (CDP contract). Hand them
// to the page so navigate_single can inject them at the right point.