diff options
author | Risan <risan@google.com> | 2019-04-24 04:38:55 +0900 |
---|---|---|
committer | Qijiang Fan <fqj@google.com> | 2020-06-05 12:51:49 +0900 |
commit | a44401bbafc6a6865b019701905e97e2e5ddcfaa (patch) | |
tree | b7c96b7f014773d5ef5bdba34935f327b71d629b /base | |
parent | f84438323a5d4b658b1f2e1511640bff4960f233 (diff) | |
download | libchrome-a44401bbafc6a6865b019701905e97e2e5ddcfaa.tar.gz |
Limit inotify_watches per FilePathWatcher instance
This prevents a FilePathWatcher instance to exhaust the inotify watches
limit (/proc/sys/fs/inotify/max_user_watches. The limit is based on the
kernel limit/predicted number of FilePathWatcher instance (for example,
at one time, in my eve there are 7 instance. Then, I round it up to the
next next 2^k just so that it is large enough for audio client).
In case the limit is hit, an error is reported through callback.
correctly, divided by #predicted instances, and then error is reported
accordingly.
Bug: 921357
Test: Manually log everywhere to make sure that the proc entry is read
Change-Id: Ib26d93e6d9c478a71759fb8ce0b76dce914f4567
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1548974
Reviewed-by: François Doray <fdoray@chromium.org>
Reviewed-by: Yusuke Sato <yusukes@chromium.org>
Commit-Queue: Yusuke Sato <yusukes@chromium.org>
Cr-Commit-Position: refs/heads/master@{#653307}
CrOS-Libchrome-Original-Commit: e2de5aed7754abbc4e3b9083476dbf86b270bdf6
Diffstat (limited to 'base')
-rw-r--r-- | base/files/file_path_watcher_linux.cc | 73 |
1 files changed, 72 insertions, 1 deletions
diff --git a/base/files/file_path_watcher_linux.cc b/base/files/file_path_watcher_linux.cc index 22732f2864..3f5eb8e2e0 100644 --- a/base/files/file_path_watcher_linux.cc +++ b/base/files/file_path_watcher_linux.cc @@ -13,6 +13,7 @@ #include <unistd.h> #include <algorithm> +#include <fstream> #include <map> #include <memory> #include <set> @@ -43,9 +44,39 @@ namespace base { namespace { +// The /proc path to max_user_watches. +constexpr char kInotifyMaxUserWatchesPath[] = + "/proc/sys/fs/inotify/max_user_watches"; + +// This is a soft limit. If there are more than |kExpectedFilePathWatches| +// FilePathWatchers for a user, than they might affect each other's inotify +// watchers limit. +constexpr int kExpectedFilePathWatchers = 16; + +// The default max inotify watchers limit per user, if reading +// /proc/sys/fs/inotify/max_user_watches fails. +constexpr int kDefaultInotifyMaxUserWatches = 8192; + class FilePathWatcherImpl; class InotifyReader; +// Get the maximum number of inotify watches can be used by a FilePathWatcher +// instance. This is based on /proc/sys/fs/inotify/max_user_watches entry. +int GetMaxNumberOfInotifyWatches() { + const static int max = []() { + int max_number_of_inotify_watches = 0; + + std::ifstream in(kInotifyMaxUserWatchesPath); + if (!in.is_open() || !(in >> max_number_of_inotify_watches)) { + LOG(ERROR) << "Failed to read " << kInotifyMaxUserWatchesPath; + return kDefaultInotifyMaxUserWatches / kExpectedFilePathWatchers; + } + + return max_number_of_inotify_watches / kExpectedFilePathWatchers; + }(); + return max; +} + class InotifyReaderThreadDelegate final : public PlatformThread::Delegate { public: explicit InotifyReaderThreadDelegate(int inotify_fd) @@ -67,6 +98,7 @@ class InotifyReader { public: using Watch = int; // Watch descriptor used by AddWatch() and RemoveWatch(). static constexpr Watch kInvalidWatch = -1; + static constexpr Watch kWatchLimitExceeded = -2; // Watch directory |path| for changes. |watcher| will be notified on each // change. Returns |kInvalidWatch| on failure. @@ -125,6 +157,14 @@ class FilePathWatcherImpl : public FilePathWatcher::PlatformDelegate { bool deleted, bool is_dir); + // Increase the number of inotify watches associated to this + // FilePathWatcherImpl instance. + bool IncreaseWatch(); + + // Decrease the number of inotify watches associated to this + // FilePathWatcherImpl instance. + void DecreaseWatch(); + private: void OnFilePathChangedOnOriginSequence(InotifyReader::Watch fired_watch, const FilePath::StringType& child, @@ -200,6 +240,9 @@ class FilePathWatcherImpl : public FilePathWatcher::PlatformDelegate { // |target_| and always stores an empty next component name in |subdir|. std::vector<WatchEntry> watches_; + // The number of inotify watches currently associated with this instance. + int number_of_inotify_watches_ = 0; + std::unordered_map<InotifyReader::Watch, FilePath> recursive_paths_by_watch_; std::map<FilePath, InotifyReader::Watch> recursive_watches_by_path_; @@ -296,13 +339,19 @@ InotifyReader::Watch InotifyReader::AddWatch( AutoLock auto_lock(lock_); ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::WILL_BLOCK); + + if (!watcher->IncreaseWatch()) + return kWatchLimitExceeded; Watch watch = inotify_add_watch(inotify_fd_, path.value().c_str(), IN_ATTRIB | IN_CREATE | IN_DELETE | IN_CLOSE_WRITE | IN_MOVE | IN_ONLYDIR); - if (watch == kInvalidWatch) + if (watch == kInvalidWatch) { + // This watch shouldn't be counted. + watcher->DecreaseWatch(); return kInvalidWatch; + } watchers_[watch].insert(watcher); @@ -316,6 +365,7 @@ void InotifyReader::RemoveWatch(Watch watch, FilePathWatcherImpl* watcher) { AutoLock auto_lock(lock_); watchers_[watch].erase(watcher); + watcher->DecreaseWatch(); if (watchers_[watch].empty()) { watchers_.erase(watch); @@ -453,6 +503,21 @@ void FilePathWatcherImpl::OnFilePathChangedOnOriginSequence( } } +bool FilePathWatcherImpl::IncreaseWatch() { + if (number_of_inotify_watches_ >= GetMaxNumberOfInotifyWatches()) { + // Notify that error happened since we are hitting the inotify watches + // limit. + callback_.Run(target_, true /* error */); + return false; + } + ++number_of_inotify_watches_; + return true; +} + +void FilePathWatcherImpl::DecreaseWatch() { + --number_of_inotify_watches_; +} + bool FilePathWatcherImpl::Watch(const FilePath& path, bool recursive, const FilePathWatcher::Callback& callback) { @@ -506,6 +571,8 @@ void FilePathWatcherImpl::UpdateWatches() { watch_entry.watch = InotifyReader::kInvalidWatch; watch_entry.linkname.clear(); watch_entry.watch = g_inotify_reader.Get().AddWatch(path, this); + if (watch_entry.watch == InotifyReader::kWatchLimitExceeded) + break; if (watch_entry.watch == InotifyReader::kInvalidWatch) { // Ignore the error code (beyond symlink handling) to attempt to add // watches on accessible children of unreadable directories. Note that @@ -587,6 +654,8 @@ void FilePathWatcherImpl::UpdateRecursiveWatchesForPath(const FilePath& path) { // Add new watches. InotifyReader::Watch watch = g_inotify_reader.Get().AddWatch(current, this); + if (watch == InotifyReader::kWatchLimitExceeded) + break; TrackWatchForRecursion(watch, current); } else { // Update existing watches. @@ -594,6 +663,8 @@ void FilePathWatcherImpl::UpdateRecursiveWatchesForPath(const FilePath& path) { DCHECK_NE(InotifyReader::kInvalidWatch, old_watch); InotifyReader::Watch watch = g_inotify_reader.Get().AddWatch(current, this); + if (watch == InotifyReader::kWatchLimitExceeded) + break; if (watch != old_watch) { g_inotify_reader.Get().RemoveWatch(old_watch, this); recursive_paths_by_watch_.erase(old_watch); |