-
Notifications
You must be signed in to change notification settings - Fork 229
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add method for native watcher initialization with fallback #441
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
use notify::{RecursiveMode, Result, Watcher, Config}; | ||
use std::path::Path; | ||
|
||
/// Initialize multiple backends via `recommended_watcher_fallback` | ||
fn fallback_init() -> Result<()> { | ||
fn event_fn(res: Result<notify::Event>) { | ||
match res { | ||
Ok(event) => println!("event: {:?}", event), | ||
Err(e) => println!("watch error: {:?}", e), | ||
} | ||
} | ||
|
||
let mut watcher1 = notify::recommended_watcher_fallback(event_fn, Config::default())?; | ||
// we will just use the same watcher kind again here | ||
let mut watcher2 = notify::recommended_watcher_fallback(event_fn, Config::default())?; | ||
watcher1.watch(Path::new("."), RecursiveMode::Recursive)?; | ||
watcher2.watch(Path::new("."), RecursiveMode::Recursive)?; | ||
Ok(()) | ||
} | ||
|
||
/// Initialize multiple backends via recommended_watcher | ||
fn direct_init() -> Result<()> { | ||
fn event_fn(res: Result<notify::Event>) { | ||
match res { | ||
Ok(event) => println!("event: {:?}", event), | ||
Err(e) => println!("watch error: {:?}", e), | ||
} | ||
} | ||
|
||
let mut watcher1 = notify::RecommendedWatcher::new(event_fn, Config::default())?; | ||
// we will just use the same watcher kind again here | ||
let mut watcher2 = notify::RecommendedWatcher::new(event_fn, Config::default())?; | ||
watcher1.watch(Path::new("."), RecursiveMode::Recursive)?; | ||
watcher2.watch(Path::new("."), RecursiveMode::Recursive)?; | ||
Ok(()) | ||
} | ||
|
||
fn main() -> Result<()> { | ||
direct_init()?; | ||
fallback_init()?; | ||
Ok(()) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -46,6 +46,8 @@ | |
//! Docker on macos M1 [throws](https://github.com/notify-rs/notify/issues/423) `Function not implemented (os error 38)`. | ||
//! You have to manually use the [PollWatcher], as the native backend isn't available inside the emulation. | ||
//! | ||
//! [recommended_watcher_fallback] works around this issue automatically. | ||
//! | ||
//! ### MacOS, FSEvents and unowned files | ||
//! | ||
//! Due to the inner security model of FSEvents (see [FileSystemEventSecurity](https://developer.apple.com/library/mac/documentation/Darwin/Conceptual/FSEvents_ProgGuide/FileSystemEventSecurity/FileSystemEventSecurity.html)), | ||
|
@@ -282,6 +284,8 @@ pub enum WatcherKind { | |
ReadDirectoryChangesWatcher, | ||
/// Fake watcher for testing | ||
NullWatcher, | ||
/// If invoked on [WatcherFallback] created via [recommended_watcher_fallback] | ||
UnknownFallback, | ||
} | ||
|
||
/// Type that can deliver file activity notifications | ||
|
@@ -339,15 +343,23 @@ pub trait Watcher { | |
} | ||
|
||
/// The recommended `Watcher` implementation for the current platform | ||
/// | ||
/// Please use [recommended_watcher_fallback] if you want to be failsafe | ||
#[cfg(target_os = "linux")] | ||
pub type RecommendedWatcher = INotifyWatcher; | ||
/// The recommended `Watcher` implementation for the current platform | ||
/// | ||
/// Please use [recommended_watcher_fallback] if you want to be failsafe | ||
#[cfg(all(target_os = "macos", not(feature = "macos_kqueue")))] | ||
pub type RecommendedWatcher = FsEventWatcher; | ||
/// The recommended `Watcher` implementation for the current platform | ||
/// | ||
/// Please use [recommended_watcher_fallback] if you want to be failsafe | ||
#[cfg(target_os = "windows")] | ||
pub type RecommendedWatcher = ReadDirectoryChangesWatcher; | ||
/// The recommended `Watcher` implementation for the current platform | ||
/// | ||
/// Please use [recommended_watcher_fallback] if you want to be failsafe | ||
#[cfg(any( | ||
target_os = "freebsd", | ||
target_os = "openbsd", | ||
|
@@ -357,6 +369,8 @@ pub type RecommendedWatcher = ReadDirectoryChangesWatcher; | |
))] | ||
pub type RecommendedWatcher = KqueueWatcher; | ||
/// The recommended `Watcher` implementation for the current platform | ||
/// | ||
/// Please use [recommended_watcher_fallback] if you want to be failsafe | ||
#[cfg(not(any( | ||
target_os = "linux", | ||
target_os = "macos", | ||
|
@@ -370,16 +384,108 @@ pub type RecommendedWatcher = PollWatcher; | |
|
||
/// Convenience method for creating the `RecommendedWatcher` for the current platform in | ||
/// _immediate_ mode. | ||
/// | ||
/// Please note that unlike [recommended_watcher_fallback] this does not guard against unavailable APIs. | ||
/// | ||
/// It is not more than calling `RecommendedWatcher::new` | ||
/// | ||
/// See [`Watcher::new_immediate`](trait.Watcher.html#tymethod.new_immediate). | ||
/// See [`Watcher::new`](trait.Watcher.html#tymethod.new). | ||
#[deprecated = "See recommended_watcher_fallback, does not guard against unavailable APIs"] | ||
pub fn recommended_watcher<F>(event_handler: F) -> Result<RecommendedWatcher> | ||
where | ||
F: EventHandler, | ||
{ | ||
// All recommended watchers currently implement `new`, so just call that. | ||
// TODO: v6 implement fallback, using recommended_watcher_fallback | ||
RecommendedWatcher::new(event_handler, Config::default()) | ||
} | ||
|
||
/// Method for creating the `RecommendedWatcher` for the current platform or falling back. | ||
/// | ||
/// Unlike [recommended_watcher] and [RecommendedWatcher] this falls back if the recommended API is not available. | ||
/// | ||
/// Example use case is [#423](https://github.com/notify-rs/notify/issues/423) where Docker with QEMU on M1 chips does not expose the inotify OS API. | ||
/// | ||
/// See also [`Watcher::new`](trait.Watcher.html#tymethod.new). | ||
pub fn recommended_watcher_fallback<F>(event_handler: F, config: Config) -> Result<WatcherFallback> | ||
where | ||
F: EventHandler + Copy, | ||
{ | ||
// All recommended watchers currently implement `new`, so just call that. | ||
match RecommendedWatcher::new(event_handler, config.clone()) { | ||
Ok(v) => Ok(WatcherFallback::Native(v)), | ||
Err(e) => { | ||
match &e.kind { | ||
#[allow(unused)] | ||
ErrorKind::Io(io_error) => { | ||
#[cfg(target_os = "linux")] | ||
if io_error.raw_os_error() == Some(38) { | ||
return Ok(WatcherFallback::Fallback(PollWatcher::new(event_handler, config)?)); | ||
} | ||
}, | ||
_ => (), | ||
} | ||
return Err(e); | ||
}, | ||
} | ||
} | ||
|
||
/// Wrapper for a fallback initialized watcher. | ||
/// | ||
/// Can be used like a Watcher. | ||
/// | ||
/// See [`recommended_watcher_fallback`](recommended_watcher_fallback) | ||
#[derive(Debug)] | ||
pub enum WatcherFallback { | ||
/// Native watcher, no error occured | ||
Native(RecommendedWatcher), | ||
/// Fallback watcher, known platform issue occured | ||
/// | ||
/// For example the Docker with Linux on MacOS M1 bug | ||
Fallback(PollWatcher) | ||
} | ||
|
||
impl WatcherFallback { | ||
/// Returns the watcher inside boxed | ||
pub fn take_boxed(self) -> Box<dyn Watcher>{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. personally I would prefer that this implemented There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. that's a very good idea! and it would make the enum more worthwhile (because otherwise I don't actually see much of a value over directly boxing it, except for claiming the allocation-free throne, while not being useful at all) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok I tried this.. And it gets awkward.
Edit: But we can implement |
||
match self { | ||
WatcherFallback::Native(v) => Box::new(v), | ||
WatcherFallback::Fallback(v) => Box::new(v), | ||
} | ||
} | ||
|
||
/// Returns the [WatcherKind] used | ||
pub fn kind(&self) -> WatcherKind { | ||
match self { | ||
// we can assume these kinds due to the way we initialize it | ||
WatcherFallback::Native(_) => { | ||
<RecommendedWatcher as Watcher>::kind() | ||
}, | ||
WatcherFallback::Fallback(_) => WatcherKind::PollWatcher, | ||
} | ||
} | ||
} | ||
|
||
impl std::ops::Deref for WatcherFallback { | ||
type Target = dyn Watcher; | ||
|
||
fn deref(&self) -> &Self::Target { | ||
match self { | ||
WatcherFallback::Native(v) => v, | ||
WatcherFallback::Fallback(v) => v, | ||
} | ||
} | ||
} | ||
|
||
impl std::ops::DerefMut for WatcherFallback { | ||
fn deref_mut(&mut self) -> &mut Self::Target { | ||
match self { | ||
WatcherFallback::Native(v) => v, | ||
WatcherFallback::Fallback(v) => v, | ||
} | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a documented meaning to 38 or indeed other codes somewhere? I suspect we should fallback in more cases but I have no idea how to find out which ones should be an error and which should fallback.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, here is the manpage though I prefer this list
Problem is that this does not tell us which of these could be emitted by our specific syscall.