summaryrefslogtreecommitdiff
path: root/base
diff options
context:
space:
mode:
authorRisan <risan@google.com>2019-04-24 04:38:55 +0900
committerQijiang Fan <fqj@google.com>2020-06-05 12:51:49 +0900
commita44401bbafc6a6865b019701905e97e2e5ddcfaa (patch)
treeb7c96b7f014773d5ef5bdba34935f327b71d629b /base
parentf84438323a5d4b658b1f2e1511640bff4960f233 (diff)
downloadlibchrome-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.cc73
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);