fix(crawler): don't hold browser mutex across Chromium close()

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-07-01 07:18:49 +02:00
parent 48f0439273
commit 2af421893f
4 changed files with 33 additions and 13 deletions

2
backend/Cargo.lock generated
View File

@@ -1558,7 +1558,7 @@ checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
[[package]] [[package]]
name = "mangalord" name = "mangalord"
version = "0.90.8" version = "0.90.9"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"argon2", "argon2",

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "mangalord" name = "mangalord"
version = "0.90.8" version = "0.90.9"
edition = "2021" edition = "2021"
default-run = "mangalord" default-run = "mangalord"

View File

@@ -234,12 +234,20 @@ impl BrowserManager {
await_drain(&self.active, drain_deadline).await; await_drain(&self.active, drain_deadline).await;
self.set_phase(RestartPhase::Restarting); self.set_phase(RestartPhase::Restarting);
let relaunch = { // Take the dead handle out under the lock, release the lock, THEN run
// the (slow) Chromium teardown — so a worker that raced past the drain
// can't block on `acquire()` behind one dead browser's close(). Mirrors
// the idle reaper's take-drop-close ordering. Re-acquire to relaunch.
let dead = {
let mut guard = self.inner.lock().await; let mut guard = self.inner.lock().await;
guard.shared = None; guard.shared = None;
if let Some(handle) = guard.handle.take() { guard.handle.take()
let _ = handle.close().await; };
} if let Some(handle) = dead {
let _ = handle.close().await;
}
let relaunch = {
let mut guard = self.inner.lock().await;
self.launch_into(&mut guard).await self.launch_into(&mut guard).await
}; };
@@ -257,9 +265,14 @@ impl BrowserManager {
/// Used on daemon shutdown. After this returns the next acquire will /// Used on daemon shutdown. After this returns the next acquire will
/// re-launch from scratch. /// re-launch from scratch.
pub async fn shutdown(&self) { pub async fn shutdown(&self) {
let mut guard = self.inner.lock().await; // Take-then-drop-then-close: don't hold the lock across Chromium
guard.shared = None; // teardown (see `invalidate` / the idle reaper).
if let Some(handle) = guard.handle.take() { let handle = {
let mut guard = self.inner.lock().await;
guard.shared = None;
guard.handle.take()
};
if let Some(handle) = handle {
let _ = handle.close().await; let _ = handle.close().await;
} }
} }
@@ -278,9 +291,16 @@ impl BrowserManager {
/// Idempotent: calling on an already-invalidated manager is a /// Idempotent: calling on an already-invalidated manager is a
/// no-op. /// no-op.
pub async fn invalidate(&self) { pub async fn invalidate(&self) {
let mut guard = self.inner.lock().await; // Take the handle out under the lock, then release the lock BEFORE the
guard.shared = None; // slow Chromium close() — otherwise every other worker's `acquire()`
if let Some(handle) = guard.handle.take() { // serializes behind one dead browser's teardown. Matches the idle
// reaper's ordering at the bottom of this file.
let handle = {
let mut guard = self.inner.lock().await;
guard.shared = None;
guard.handle.take()
};
if let Some(handle) = handle {
let _ = handle.close().await; let _ = handle.close().await;
tracing::warn!("BrowserManager: handle invalidated — next acquire will relaunch"); tracing::warn!("BrowserManager: handle invalidated — next acquire will relaunch");
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "mangalord-frontend", "name": "mangalord-frontend",
"version": "0.90.8", "version": "0.90.9",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {