fix: ignore local watcher access events
The peer CLI could flood LocalGamesUpdated events when run from the Docker harness. The local monitor rescans game roots, and some bind-mounted filesystems report those read/close operations back as notify access events. Treating those non-mutating events as real library changes queued another rescan, making the headless CLI unusable for manual peer-to-peer testing. Ignore access events before mapping paths to game IDs. Create, modify, remove, and rename events still flow through the existing per-game rescan gate, while fallback scans continue to reconcile missed writes. Test Plan: - just fmt - just test - just clippy Refs: manual peer-cli P2P testing
This commit is contained in:
@@ -7,7 +7,7 @@ use std::{
|
|||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
use notify::{Config, Event, RecommendedWatcher, RecursiveMode, Watcher};
|
use notify::{Config, Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher};
|
||||||
use tokio::sync::{RwLock, mpsc::UnboundedSender};
|
use tokio::sync::{RwLock, mpsc::UnboundedSender};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -219,6 +219,10 @@ async fn handle_watch_event(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if matches!(event.kind, EventKind::Access(_)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let game_dir = ctx.game_dir.read().await.clone();
|
let game_dir = ctx.game_dir.read().await.clone();
|
||||||
let ids = event
|
let ids = event
|
||||||
.paths
|
.paths
|
||||||
@@ -338,7 +342,10 @@ mod tests {
|
|||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
use notify::EventKind;
|
use notify::{
|
||||||
|
EventKind,
|
||||||
|
event::{AccessKind, AccessMode},
|
||||||
|
};
|
||||||
use tokio::sync::{RwLock, mpsc};
|
use tokio::sync::{RwLock, mpsc};
|
||||||
use tokio_util::{sync::CancellationToken, task::TaskTracker};
|
use tokio_util::{sync::CancellationToken, task::TaskTracker};
|
||||||
|
|
||||||
@@ -470,6 +477,40 @@ mod tests {
|
|||||||
assert!(gate.pending.read().await.is_empty());
|
assert!(gate.pending.read().await.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn access_watch_event_is_ignored() {
|
||||||
|
let temp = TempDir::new("lanspread-local-monitor");
|
||||||
|
write_file(&temp.path().join("game").join("version.ini"), b"20250101");
|
||||||
|
let ctx = test_ctx(
|
||||||
|
temp.path().to_path_buf(),
|
||||||
|
HashSet::from(["game".to_string()]),
|
||||||
|
);
|
||||||
|
let gate = RescanGate::default();
|
||||||
|
let (tx, mut rx) = mpsc::unbounded_channel();
|
||||||
|
|
||||||
|
handle_watch_event(
|
||||||
|
&ctx,
|
||||||
|
&tx,
|
||||||
|
&gate,
|
||||||
|
Ok(
|
||||||
|
Event::new(EventKind::Access(AccessKind::Close(AccessMode::Read)))
|
||||||
|
.add_path(temp.path().join("game").join("version.ini")),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
ctx.task_tracker.close();
|
||||||
|
ctx.task_tracker.wait().await;
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
tokio::time::timeout(Duration::from_millis(50), rx.recv())
|
||||||
|
.await
|
||||||
|
.is_err(),
|
||||||
|
"access events should not schedule a UI update"
|
||||||
|
);
|
||||||
|
assert!(gate.running.read().await.is_empty());
|
||||||
|
assert!(gate.pending.read().await.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn burst_watch_events_collapse_to_two_rescans_for_same_game() {
|
async fn burst_watch_events_collapse_to_two_rescans_for_same_game() {
|
||||||
let temp = TempDir::new("lanspread-local-monitor");
|
let temp = TempDir::new("lanspread-local-monitor");
|
||||||
|
|||||||
Reference in New Issue
Block a user