Skip to content

Commit 394ef18

Browse files
authored
feat(inotify): notify a user if the max_user_watches has been reached implicitly (#698)
* feat(inotify): notify a user if the `max_user_watches` has been reached implicitly There was no way to detect the problem if it caused by implicitly watched directory (e.g. recursive watch). This commit gives a user the ability to handle the problem and react somehow. For example, gui app can show a notification with some instructions how to increase the value * chore: changelog for (#698)
1 parent 04473de commit 394ef18

File tree

2 files changed

+78
-1
lines changed

2 files changed

+78
-1
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Changelog
22

3+
## notify 8.2.0 (unreleased)
4+
- FEATURE: notify a user if the `max_user_watches` has been reached implicitly (`INotifyWatcher`) [#698]
5+
6+
[#698]: https://github.com/notify-rs/notify/pull/698
7+
38
## notify 8.1.0 (2025-07-03)
49
- FEATURE: added support for the [`flume`](https://docs.rs/flume) crate
510
- FIX: kqueue-backend: do not double unwatch top-level directory when recursively unwatching [#683]

notify/src/inotify.rs

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,19 @@ impl EventLoop {
388388
}
389389

390390
for path in add_watches {
391-
self.add_watch(path, true, false).ok();
391+
if let Err(add_watch_error) = self.add_watch(path, true, false) {
392+
// The handler should be notified if we have reached the limit.
393+
// Otherwise, the user might expect that a recursive watch
394+
// is continuing to work correctly, but it's not.
395+
if let ErrorKind::MaxFilesWatch = add_watch_error.kind {
396+
self.event_handler.handle_event(Err(add_watch_error));
397+
398+
// After that kind of a error we should stop adding watches,
399+
// because the limit has already reached and all next calls
400+
// will return us only the same error.
401+
break;
402+
}
403+
}
392404
}
393405
}
394406

@@ -629,3 +641,63 @@ fn native_error_type_on_missing_path() {
629641
})
630642
))
631643
}
644+
645+
/// Runs manually.
646+
///
647+
/// * Save actual value of the limit: `MAX_USER_WATCHES=$(sysctl -n fs.inotify.max_user_watches)`
648+
/// * Run the test.
649+
/// * Set the limit to 0: `sudo sysctl fs.inotify.max_user_watches=0` while test is running
650+
/// * Wait for the test to complete
651+
/// * Restore the limit `sudo sysctl fs.inotify.max_user_watches=$MAX_USER_WATCHES`
652+
#[test]
653+
#[ignore = "requires changing sysctl fs.inotify.max_user_watches while test is running"]
654+
fn recurcive_watch_calls_handler_if_creating_a_file_raises_max_files_watch() {
655+
use std::time::Duration;
656+
657+
let tmpdir = tempfile::tempdir().unwrap();
658+
let (tx, rx) = std::sync::mpsc::channel();
659+
let (proc_changed_tx, proc_changed_rx) = std::sync::mpsc::channel();
660+
let proc_path = Path::new("/proc/sys/fs/inotify/max_user_watches");
661+
let mut watcher = INotifyWatcher::new(
662+
move |result: Result<Event>| match result {
663+
Ok(event) => {
664+
if event.paths.first().is_some_and(|path| path == proc_path) {
665+
proc_changed_tx.send(()).unwrap();
666+
}
667+
}
668+
Err(e) => tx.send(e).unwrap(),
669+
},
670+
Config::default(),
671+
)
672+
.unwrap();
673+
674+
watcher
675+
.watch(tmpdir.path(), RecursiveMode::Recursive)
676+
.unwrap();
677+
watcher
678+
.watch(proc_path, RecursiveMode::NonRecursive)
679+
.unwrap();
680+
681+
// give the time to set the limit
682+
proc_changed_rx
683+
.recv_timeout(Duration::from_secs(30))
684+
.unwrap();
685+
686+
let child_dir = tmpdir.path().join("child");
687+
std::fs::create_dir(child_dir).unwrap();
688+
689+
let result = rx.recv_timeout(Duration::from_millis(500));
690+
691+
assert!(
692+
matches!(
693+
&result,
694+
Ok(Error {
695+
kind: ErrorKind::MaxFilesWatch,
696+
paths: _,
697+
})
698+
),
699+
"expected {:?}, found: {:#?}",
700+
ErrorKind::MaxFilesWatch,
701+
result
702+
);
703+
}

0 commit comments

Comments
 (0)