Restart FSEventStream at the last seen event when "dropped" is reported

This commit is contained in:
Antonio Scandurra 2022-05-23 09:33:10 +02:00
parent f3bc4feaf0
commit 663173d2f5

View File

@ -8,7 +8,7 @@ use std::{
ffi::{c_void, CStr, OsStr}, ffi::{c_void, CStr, OsStr},
os::unix::ffi::OsStrExt, os::unix::ffi::OsStrExt,
path::{Path, PathBuf}, path::{Path, PathBuf},
slice, ptr, slice,
sync::Arc, sync::Arc,
time::Duration, time::Duration,
}; };
@ -21,7 +21,6 @@ pub struct Event {
} }
pub struct EventStream { pub struct EventStream {
stream: fs::FSEventStreamRef,
lifecycle: Arc<Mutex<Lifecycle>>, lifecycle: Arc<Mutex<Lifecycle>>,
state: Box<State>, state: Box<State>,
} }
@ -31,6 +30,7 @@ struct State {
paths: cf::CFMutableArrayRef, paths: cf::CFMutableArrayRef,
callback: Option<Box<dyn FnMut(Vec<Event>) -> bool>>, callback: Option<Box<dyn FnMut(Vec<Event>) -> bool>>,
last_valid_event_id: Option<fs::FSEventStreamEventId>, last_valid_event_id: Option<fs::FSEventStreamEventId>,
stream: fs::FSEventStreamRef,
} }
impl Drop for State { impl Drop for State {
@ -73,11 +73,12 @@ impl EventStream {
cf::CFRelease(cf_url); cf::CFRelease(cf_url);
} }
let state = Box::new(State { let mut state = Box::new(State {
latency, latency,
paths: cf_paths, paths: cf_paths,
callback: None, callback: None,
last_valid_event_id: None, last_valid_event_id: None,
stream: ptr::null_mut(),
}); });
let stream_context = fs::FSEventStreamContext { let stream_context = fs::FSEventStreamContext {
version: 0, version: 0,
@ -97,11 +98,11 @@ impl EventStream {
| fs::kFSEventStreamCreateFlagNoDefer | fs::kFSEventStreamCreateFlagNoDefer
| fs::kFSEventStreamCreateFlagWatchRoot, | fs::kFSEventStreamCreateFlagWatchRoot,
); );
state.stream = stream;
let lifecycle = Arc::new(Mutex::new(Lifecycle::New)); let lifecycle = Arc::new(Mutex::new(Lifecycle::New));
( (
EventStream { EventStream {
stream,
lifecycle: lifecycle.clone(), lifecycle: lifecycle.clone(),
state, state,
}, },
@ -125,10 +126,16 @@ impl EventStream {
Lifecycle::Stopped => return, Lifecycle::Stopped => return,
} }
} }
fs::FSEventStreamScheduleWithRunLoop(self.stream, run_loop, cf::kCFRunLoopDefaultMode); fs::FSEventStreamScheduleWithRunLoop(
fs::FSEventStreamStart(self.stream); self.state.stream,
run_loop,
cf::kCFRunLoopDefaultMode,
);
fs::FSEventStreamStart(self.state.stream);
cf::CFRunLoopRun(); cf::CFRunLoopRun();
fs::FSEventStreamRelease(self.stream); fs::FSEventStreamStop(self.state.stream);
fs::FSEventStreamInvalidate(self.state.stream);
fs::FSEventStreamRelease(self.state.stream);
} }
} }
@ -154,30 +161,83 @@ impl EventStream {
let paths = slice::from_raw_parts(event_paths, num); let paths = slice::from_raw_parts(event_paths, num);
let flags = slice::from_raw_parts_mut(e_ptr, num); let flags = slice::from_raw_parts_mut(e_ptr, num);
let ids = slice::from_raw_parts_mut(i_ptr, num); let ids = slice::from_raw_parts_mut(i_ptr, num);
let mut stream_restarted = false;
let mut events = Vec::with_capacity(num); // Sometimes FSEvents reports a "dropped" event, an indication that either the kernel
for p in 0..num { // or our code couldn't keep up with the sheer volume of file-system events that were
let path_c_str = CStr::from_ptr(paths[p]); // generated. If we observed a valid event before this happens, we'll try to read the
let path = PathBuf::from(OsStr::from_bytes(path_c_str.to_bytes())); // file-system journal by stopping the current stream and creating a new one starting at
if let Some(flag) = StreamFlags::from_bits(flags[p]) { // such event. Otherwise, we'll let invoke the callback with the dropped event, which
if flag.contains(StreamFlags::HISTORY_DONE) { // will likely perform a re-scan of one of the root directories.
events.clear(); if flags
} else { .iter()
events.push(Event { .copied()
event_id: ids[p], .filter_map(StreamFlags::from_bits)
flags: flag, .any(|flags| {
path, flags.contains(StreamFlags::USER_DROPPED)
}); || flags.contains(StreamFlags::KERNEL_DROPPED)
} })
} else { {
debug_assert!(false, "unknown flag set for fs event: {}", flags[p]); if let Some(last_valid_event_id) = state.last_valid_event_id.take() {
fs::FSEventStreamStop(state.stream);
fs::FSEventStreamInvalidate(state.stream);
fs::FSEventStreamRelease(state.stream);
let stream_context = fs::FSEventStreamContext {
version: 0,
info,
retain: None,
release: None,
copy_description: None,
};
let stream = fs::FSEventStreamCreate(
cf::kCFAllocatorDefault,
Self::trampoline,
&stream_context,
state.paths,
last_valid_event_id,
state.latency.as_secs_f64(),
fs::kFSEventStreamCreateFlagFileEvents
| fs::kFSEventStreamCreateFlagNoDefer
| fs::kFSEventStreamCreateFlagWatchRoot,
);
state.stream = stream;
fs::FSEventStreamScheduleWithRunLoop(
state.stream,
cf::CFRunLoopGetCurrent(),
cf::kCFRunLoopDefaultMode,
);
fs::FSEventStreamStart(state.stream);
stream_restarted = true;
} }
} }
if !events.is_empty() { if !stream_restarted {
if !callback(events) { let mut events = Vec::with_capacity(num);
fs::FSEventStreamStop(stream_ref); for p in 0..num {
cf::CFRunLoopStop(cf::CFRunLoopGetCurrent()); if let Some(flag) = StreamFlags::from_bits(flags[p]) {
if !flag.contains(StreamFlags::HISTORY_DONE) {
let path_c_str = CStr::from_ptr(paths[p]);
let path = PathBuf::from(OsStr::from_bytes(path_c_str.to_bytes()));
let event = Event {
event_id: ids[p],
flags: flag,
path,
};
state.last_valid_event_id = Some(event.event_id);
events.push(event);
}
} else {
debug_assert!(false, "unknown flag set for fs event: {}", flags[p]);
}
}
if !events.is_empty() {
if !callback(events) {
fs::FSEventStreamStop(stream_ref);
cf::CFRunLoopStop(cf::CFRunLoopGetCurrent());
}
} }
} }
} }