| From 9ae1384af2cdd16539e9caaed69b095737e2c272 Mon Sep 17 00:00:00 2001 |
| From: Qijiang Fan <fqj@chromium.org> |
| Date: Tue, 17 Dec 2019 17:32:35 +0900 |
| Subject: [PATCH] backport upstream patch to fix watcher leak. |
| |
| https://chromium-review.googlesource.com/c/chromium/src/+/695914 |
| |
| Change-Id: I91928fc00e9845ff75c49c272ff774ff9810f4eb |
| --- |
| base/files/file_descriptor_watcher_posix.cc | 104 +++++++++++++++----- |
| base/threading/thread_restrictions.h | 4 + |
| 2 files changed, 82 insertions(+), 26 deletions(-) |
| |
| diff --git a/base/files/file_descriptor_watcher_posix.cc b/base/files/file_descriptor_watcher_posix.cc |
| index b26bf6c..98d2cec 100644 |
| --- a/base/files/file_descriptor_watcher_posix.cc |
| +++ b/base/files/file_descriptor_watcher_posix.cc |
| @@ -5,6 +5,7 @@ |
| #include "base/files/file_descriptor_watcher_posix.h" |
| |
| #include "base/bind.h" |
| +#include "base/callback_helpers.h" |
| #include "base/lazy_instance.h" |
| #include "base/logging.h" |
| #include "base/memory/ptr_util.h" |
| @@ -12,6 +13,7 @@ |
| #include "base/message_loop/message_pump_for_io.h" |
| #include "base/sequenced_task_runner.h" |
| #include "base/single_thread_task_runner.h" |
| +#include "base/synchronization/waitable_event.h" |
| #include "base/threading/sequenced_task_runner_handle.h" |
| #include "base/threading/thread_checker.h" |
| #include "base/threading/thread_local.h" |
| @@ -27,21 +29,7 @@ LazyInstance<ThreadLocalPointer<MessageLoopForIO>>::Leaky |
| |
| } // namespace |
| |
| -FileDescriptorWatcher::Controller::~Controller() { |
| - DCHECK(sequence_checker_.CalledOnValidSequence()); |
| - |
| - // Delete |watcher_| on the MessageLoopForIO. |
| - // |
| - // If the MessageLoopForIO is deleted before Watcher::StartWatching() runs, |
| - // |watcher_| is leaked. If the MessageLoopForIO is deleted after |
| - // Watcher::StartWatching() runs but before the DeleteSoon task runs, |
| - // |watcher_| is deleted from Watcher::WillDestroyCurrentMessageLoop(). |
| - message_loop_for_io_task_runner_->DeleteSoon(FROM_HERE, watcher_.release()); |
| - |
| - // Since WeakPtrs are invalidated by the destructor, RunCallback() won't be |
| - // invoked after this returns. |
| -} |
| - |
| +// Move watcher above to prevent delete incomplete type at delete watcher later. |
| class FileDescriptorWatcher::Controller::Watcher |
| : public MessagePumpForIO::FdWatcher, |
| public MessageLoopCurrent::DestructionObserver { |
| @@ -90,6 +78,58 @@ class FileDescriptorWatcher::Controller::Watcher |
| DISALLOW_COPY_AND_ASSIGN(Watcher); |
| }; |
| |
| +FileDescriptorWatcher::Controller::~Controller() { |
| + DCHECK(sequence_checker_.CalledOnValidSequence()); |
| + |
| + if (message_loop_for_io_task_runner_->BelongsToCurrentThread()) { |
| + // If the MessageLoopForIO and the Controller live on the same thread. |
| + watcher_.reset(); |
| + } else { |
| + // Synchronously wait until |watcher_| is deleted on the MessagePumpForIO |
| + // thread. This ensures that the file descriptor is never accessed after |
| + // this destructor returns. |
| + // |
| + // Use a ScopedClosureRunner to ensure that |done| is signaled even if the |
| + // thread doesn't run any more tasks (if PostTask returns true, it means |
| + // that the task was queued, but it doesn't mean that a RunLoop will run the |
| + // task before the queue is deleted). |
| + // |
| + // We considered associating "generations" to file descriptors to avoid the |
| + // synchronous wait. For example, if the IO thread gets a "cancel" for fd=6, |
| + // generation=1 after getting a "start watching" for fd=6, generation=2, it |
| + // can ignore the "Cancel". However, "generations" didn't solve this race: |
| + // |
| + // T1 (client) Start watching fd = 6 with WatchReadable() |
| + // Stop watching fd = 6 |
| + // Close fd = 6 |
| + // Open a new file, fd = 6 gets reused. |
| + // T2 (io) Watcher::StartWatching() |
| + // Incorrectly starts watching fd = 6 which now refers to a |
| + // different file than when WatchReadable() was called. |
| + WaitableEvent done(WaitableEvent::ResetPolicy::MANUAL, |
| + WaitableEvent::InitialState::NOT_SIGNALED); |
| + message_loop_for_io_task_runner_->PostTask( |
| + FROM_HERE, BindOnce( |
| + [](Watcher *watcher, ScopedClosureRunner closure) { |
| + // Since |watcher| is a raw pointer, it isn't deleted |
| + // if this callback is deleted before it gets to run. |
| + delete watcher; |
| + // |closure| runs at the end of this scope. |
| + }, |
| + Unretained(watcher_.release()), |
| + // TODO(fqj): change to BindOnce after some uprev. |
| + ScopedClosureRunner(Bind(&WaitableEvent::Signal, |
| + Unretained(&done))))); |
| + // TODO(fqj): change to ScopedAllowBaseSyncPrimitivesOutsideBlockingScope |
| + // after uprev to r586297 |
| + base::ThreadRestrictions::ScopedAllowWait allow_wait __attribute__((unused)); |
| + done.Wait(); |
| + } |
| + |
| + // Since WeakPtrs are invalidated by the destructor, RunCallback() won't be |
| + // invoked after this returns. |
| +} |
| + |
| FileDescriptorWatcher::Controller::Watcher::Watcher( |
| WeakPtr<Controller> controller, |
| MessagePumpForIO::Mode mode, |
| @@ -150,11 +190,19 @@ void FileDescriptorWatcher::Controller::Watcher:: |
| WillDestroyCurrentMessageLoop() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| - // A Watcher is owned by a Controller. When the Controller is deleted, it |
| - // transfers ownership of the Watcher to a delete task posted to the |
| - // MessageLoopForIO. If the MessageLoopForIO is deleted before the delete task |
| - // runs, the following line takes care of deleting the Watcher. |
| - delete this; |
| + if (callback_task_runner_->RunsTasksInCurrentSequence()) { |
| + // |controller_| can be accessed directly when Watcher runs on the same |
| + // thread. |
| + controller_->watcher_.reset(); |
| + } else { |
| + // If the Watcher and the Controller live on different threads, delete |
| + // |this| synchronously. Pending tasks bound to an unretained Watcher* will |
| + // not run since this loop is dead. The associated Controller still |
| + // technically owns this via unique_ptr but it never uses it directly and |
| + // will ultimately send it to this thread for deletion (and that also won't |
| + // run since the loop being dead). |
| + delete this; |
| + } |
| } |
| |
| FileDescriptorWatcher::Controller::Controller(MessagePumpForIO::Mode mode, |
| @@ -172,12 +220,16 @@ FileDescriptorWatcher::Controller::Controller(MessagePumpForIO::Mode mode, |
| |
| void FileDescriptorWatcher::Controller::StartWatching() { |
| DCHECK(sequence_checker_.CalledOnValidSequence()); |
| - // It is safe to use Unretained() below because |watcher_| can only be deleted |
| - // by a delete task posted to |message_loop_for_io_task_runner_| by this |
| - // Controller's destructor. Since this delete task hasn't been posted yet, it |
| - // can't run before the task posted below. |
| - message_loop_for_io_task_runner_->PostTask( |
| - FROM_HERE, BindOnce(&Watcher::StartWatching, Unretained(watcher_.get()))); |
| + if (message_loop_for_io_task_runner_->BelongsToCurrentThread()) { |
| + watcher_->StartWatching(); |
| + } else { |
| + // It is safe to use Unretained() below because |watcher_| can only be deleted |
| + // by a delete task posted to |message_loop_for_io_task_runner_| by this |
| + // Controller's destructor. Since this delete task hasn't been posted yet, it |
| + // can't run before the task posted below. |
| + message_loop_for_io_task_runner_->PostTask( |
| + FROM_HERE, Bind(&Watcher::StartWatching, Unretained(watcher_.get()))); |
| + } |
| } |
| |
| void FileDescriptorWatcher::Controller::RunCallback() { |
| diff --git a/base/threading/thread_restrictions.h b/base/threading/thread_restrictions.h |
| index 705ba4d..7532f85 100644 |
| --- a/base/threading/thread_restrictions.h |
| +++ b/base/threading/thread_restrictions.h |
| @@ -147,6 +147,7 @@ namespace internal { |
| class TaskTracker; |
| } |
| |
| +class FileDescriptorWatcher; |
| class GetAppOutputScopedAllowBaseSyncPrimitives; |
| class SimpleThread; |
| class StackSamplingProfiler; |
| @@ -479,6 +480,9 @@ class BASE_EXPORT ThreadRestrictions { |
| friend class ui::CommandBufferLocal; |
| friend class ui::GpuState; |
| |
| + // Chrome OS libchrome |
| + friend class base::FileDescriptorWatcher; |
| + |
| // END ALLOWED USAGE. |
| // BEGIN USAGE THAT NEEDS TO BE FIXED. |
| friend class ::chromeos::BlockingMethodCaller; // http://crbug.com/125360 |
| -- |
| 2.24.1.735.g03f4e72817-goog |
| |