diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2017-11-20 15:06:40 +0100 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2017-11-22 11:48:58 +0000 |
commit | daa093eea7c773db06799a13bd7e4e2e2a9f8f14 (patch) | |
tree | 96cc5e7b9194c1b29eab927730bfa419e7111c25 /chromium/base/message_loop | |
parent | be59a35641616a4cf23c4a13fa0632624b021c1b (diff) |
BASELINE: Update Chromium to 63.0.3239.58
Change-Id: Ia93b322a00ba4dd4004f3bcf1254063ba90e1605
Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
Diffstat (limited to 'chromium/base/message_loop')
22 files changed, 1325 insertions, 522 deletions
diff --git a/chromium/base/message_loop/incoming_task_queue.cc b/chromium/base/message_loop/incoming_task_queue.cc index a945abbb475..844a64f10e6 100644 --- a/chromium/base/message_loop/incoming_task_queue.cc +++ b/chromium/base/message_loop/incoming_task_queue.cc @@ -50,19 +50,20 @@ TimeTicks CalculateDelayedRuntime(TimeDelta delay) { } // namespace IncomingTaskQueue::IncomingTaskQueue(MessageLoop* message_loop) - : high_res_task_count_(0), - message_loop_(message_loop), - next_sequence_num_(0), - message_loop_scheduled_(false), - always_schedule_work_(AlwaysNotifyPump(message_loop_->type())), - is_ready_for_scheduling_(false) { -} - -bool IncomingTaskQueue::AddToIncomingQueue( - const tracked_objects::Location& from_here, - OnceClosure task, - TimeDelta delay, - bool nestable) { + : always_schedule_work_(AlwaysNotifyPump(message_loop->type())), + triage_tasks_(this), + delayed_tasks_(this), + deferred_tasks_(this), + message_loop_(message_loop) { + // The constructing sequence is not necessarily the running sequence in the + // case of base::Thread. + DETACH_FROM_SEQUENCE(sequence_checker_); +} + +bool IncomingTaskQueue::AddToIncomingQueue(const Location& from_here, + OnceClosure task, + TimeDelta delay, + Nestable nestable) { // Use CHECK instead of DCHECK to crash earlier. See http://crbug.com/711167 // for details. CHECK(task); @@ -91,29 +92,15 @@ bool IncomingTaskQueue::IsIdleForTesting() { return incoming_queue_.empty(); } -int IncomingTaskQueue::ReloadWorkQueue(TaskQueue* work_queue) { - // Make sure no tasks are lost. - DCHECK(work_queue->empty()); - - // Acquire all we can from the inter-thread queue with one lock acquisition. - AutoLock lock(incoming_queue_lock_); - if (incoming_queue_.empty()) { - // If the loop attempts to reload but there are no tasks in the incoming - // queue, that means it will go to sleep waiting for more work. If the - // incoming queue becomes nonempty we need to schedule it again. - message_loop_scheduled_ = false; - } else { - incoming_queue_.swap(*work_queue); - } - // Reset the count of high resolution tasks since our queue is now empty. - int high_res_tasks = high_res_task_count_; - high_res_task_count_ = 0; - return high_res_tasks; -} - void IncomingTaskQueue::WillDestroyCurrentMessageLoop() { - base::subtle::AutoWriteLock lock(message_loop_lock_); - message_loop_ = NULL; + { + AutoLock auto_lock(incoming_queue_lock_); + accept_new_tasks_ = false; + } + { + AutoLock auto_lock(message_loop_lock_); + message_loop_ = nullptr; + } } void IncomingTaskQueue::StartScheduling() { @@ -138,60 +125,251 @@ IncomingTaskQueue::~IncomingTaskQueue() { DCHECK(!message_loop_); } +void IncomingTaskQueue::RunTask(PendingTask* pending_task) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + task_annotator_.RunTask("MessageLoop::PostTask", pending_task); +} + +IncomingTaskQueue::TriageQueue::TriageQueue(IncomingTaskQueue* outer) + : outer_(outer) {} + +IncomingTaskQueue::TriageQueue::~TriageQueue() = default; + +const PendingTask& IncomingTaskQueue::TriageQueue::Peek() { + DCHECK_CALLED_ON_VALID_SEQUENCE(outer_->sequence_checker_); + ReloadFromIncomingQueueIfEmpty(); + DCHECK(!queue_.empty()); + return queue_.front(); +} + +PendingTask IncomingTaskQueue::TriageQueue::Pop() { + DCHECK_CALLED_ON_VALID_SEQUENCE(outer_->sequence_checker_); + ReloadFromIncomingQueueIfEmpty(); + DCHECK(!queue_.empty()); + PendingTask pending_task = std::move(queue_.front()); + queue_.pop(); + + if (pending_task.is_high_res) + --outer_->pending_high_res_tasks_; + + return pending_task; +} + +bool IncomingTaskQueue::TriageQueue::HasTasks() { + DCHECK_CALLED_ON_VALID_SEQUENCE(outer_->sequence_checker_); + ReloadFromIncomingQueueIfEmpty(); + return !queue_.empty(); +} + +void IncomingTaskQueue::TriageQueue::Clear() { + DCHECK_CALLED_ON_VALID_SEQUENCE(outer_->sequence_checker_); + // Previously, MessageLoop would delete all tasks including delayed and + // deferred tasks in a single round before attempting to reload from the + // incoming queue to see if more tasks remained. This gave it a chance to + // assess whether or not clearing should continue. As a result, while + // reloading is automatic for getting and seeing if tasks exist, it is not + // automatic for Clear(). + while (!queue_.empty()) { + PendingTask pending_task = std::move(queue_.front()); + queue_.pop(); + + if (pending_task.is_high_res) + --outer_->pending_high_res_tasks_; + + if (!pending_task.delayed_run_time.is_null()) { + outer_->delayed_tasks().Push(std::move(pending_task)); + } + } +} + +void IncomingTaskQueue::TriageQueue::ReloadFromIncomingQueueIfEmpty() { + if (queue_.empty()) { + // TODO(robliao): Since these high resolution tasks aren't yet in the + // delayed queue, they technically shouldn't trigger high resolution timers + // until they are. + outer_->pending_high_res_tasks_ += outer_->ReloadWorkQueue(&queue_); + } +} + +IncomingTaskQueue::DelayedQueue::DelayedQueue(IncomingTaskQueue* outer) + : outer_(outer) {} + +IncomingTaskQueue::DelayedQueue::~DelayedQueue() = default; + +void IncomingTaskQueue::DelayedQueue::Push(PendingTask pending_task) { + DCHECK_CALLED_ON_VALID_SEQUENCE(outer_->sequence_checker_); + + if (pending_task.is_high_res) + ++outer_->pending_high_res_tasks_; + + queue_.push(std::move(pending_task)); +} + +const PendingTask& IncomingTaskQueue::DelayedQueue::Peek() { + DCHECK_CALLED_ON_VALID_SEQUENCE(outer_->sequence_checker_); + DCHECK(!queue_.empty()); + return queue_.top(); +} + +PendingTask IncomingTaskQueue::DelayedQueue::Pop() { + DCHECK_CALLED_ON_VALID_SEQUENCE(outer_->sequence_checker_); + DCHECK(!queue_.empty()); + PendingTask delayed_task = std::move(const_cast<PendingTask&>(queue_.top())); + queue_.pop(); + + if (delayed_task.is_high_res) + --outer_->pending_high_res_tasks_; + + return delayed_task; +} + +bool IncomingTaskQueue::DelayedQueue::HasTasks() { + DCHECK_CALLED_ON_VALID_SEQUENCE(outer_->sequence_checker_); + // TODO(robliao): The other queues don't check for IsCancelled(). Should they? + while (!queue_.empty() && Peek().task.IsCancelled()) + Pop(); + + return !queue_.empty(); +} + +void IncomingTaskQueue::DelayedQueue::Clear() { + DCHECK_CALLED_ON_VALID_SEQUENCE(outer_->sequence_checker_); + while (!queue_.empty()) + Pop(); +} + +IncomingTaskQueue::DeferredQueue::DeferredQueue(IncomingTaskQueue* outer) + : outer_(outer) {} + +IncomingTaskQueue::DeferredQueue::~DeferredQueue() = default; + +void IncomingTaskQueue::DeferredQueue::Push(PendingTask pending_task) { + DCHECK_CALLED_ON_VALID_SEQUENCE(outer_->sequence_checker_); + + // TODO(robliao): These tasks should not count towards the high res task count + // since they are no longer in the delayed queue. + if (pending_task.is_high_res) + ++outer_->pending_high_res_tasks_; + + queue_.push(std::move(pending_task)); +} + +const PendingTask& IncomingTaskQueue::DeferredQueue::Peek() { + DCHECK_CALLED_ON_VALID_SEQUENCE(outer_->sequence_checker_); + DCHECK(!queue_.empty()); + return queue_.front(); +} + +PendingTask IncomingTaskQueue::DeferredQueue::Pop() { + DCHECK_CALLED_ON_VALID_SEQUENCE(outer_->sequence_checker_); + DCHECK(!queue_.empty()); + PendingTask deferred_task = std::move(queue_.front()); + queue_.pop(); + + if (deferred_task.is_high_res) + --outer_->pending_high_res_tasks_; + + return deferred_task; +} + +bool IncomingTaskQueue::DeferredQueue::HasTasks() { + DCHECK_CALLED_ON_VALID_SEQUENCE(outer_->sequence_checker_); + return !queue_.empty(); +} + +void IncomingTaskQueue::DeferredQueue::Clear() { + DCHECK_CALLED_ON_VALID_SEQUENCE(outer_->sequence_checker_); + while (!queue_.empty()) + Pop(); +} + bool IncomingTaskQueue::PostPendingTask(PendingTask* pending_task) { // Warning: Don't try to short-circuit, and handle this thread's tasks more // directly, as it could starve handling of foreign threads. Put every task // into this queue. + bool accept_new_tasks; + bool schedule_work = false; + { + AutoLock auto_lock(incoming_queue_lock_); + accept_new_tasks = accept_new_tasks_; + if (accept_new_tasks) + schedule_work = PostPendingTaskLockRequired(pending_task); + } - // Ensures |message_loop_| isn't destroyed while running. - base::subtle::AutoReadLock hold_message_loop(message_loop_lock_); - - if (!message_loop_) { + if (!accept_new_tasks) { + // Clear the pending task outside of |incoming_queue_lock_| to prevent any + // chance of self-deadlock if destroying a task also posts a task to this + // queue. + DCHECK(!schedule_work); pending_task->task.Reset(); return false; } - bool schedule_work = false; - { - AutoLock hold(incoming_queue_lock_); + // Wake up the message loop and schedule work. This is done outside + // |incoming_queue_lock_| to allow for multiple post tasks to occur while + // ScheduleWork() is running. For platforms (e.g. Android) that require one + // call to ScheduleWork() for each task, all pending tasks may serialize + // within the ScheduleWork() call. As a result, holding a lock to maintain the + // lifetime of |message_loop_| is less of a concern. + if (schedule_work) { + // Ensures |message_loop_| isn't destroyed while running. + AutoLock auto_lock(message_loop_lock_); + if (message_loop_) + message_loop_->ScheduleWork(); + } + + return true; +} + +bool IncomingTaskQueue::PostPendingTaskLockRequired(PendingTask* pending_task) { + incoming_queue_lock_.AssertAcquired(); #if defined(OS_WIN) - if (pending_task->is_high_res) - ++high_res_task_count_; + if (pending_task->is_high_res) + ++high_res_task_count_; #endif - // Initialize the sequence number. The sequence number is used for delayed - // tasks (to facilitate FIFO sorting when two tasks have the same - // delayed_run_time value) and for identifying the task in about:tracing. - pending_task->sequence_num = next_sequence_num_++; - - message_loop_->task_annotator()->DidQueueTask("MessageLoop::PostTask", - *pending_task); - - bool was_empty = incoming_queue_.empty(); - incoming_queue_.push(std::move(*pending_task)); - - if (is_ready_for_scheduling_ && - (always_schedule_work_ || (!message_loop_scheduled_ && was_empty))) { - schedule_work = true; - // After we've scheduled the message loop, we do not need to do so again - // until we know it has processed all of the work in our queue and is - // waiting for more work again. The message loop will always attempt to - // reload from the incoming queue before waiting again so we clear this - // flag in ReloadWorkQueue(). - message_loop_scheduled_ = true; - } + // Initialize the sequence number. The sequence number is used for delayed + // tasks (to facilitate FIFO sorting when two tasks have the same + // delayed_run_time value) and for identifying the task in about:tracing. + pending_task->sequence_num = next_sequence_num_++; + + task_annotator_.DidQueueTask("MessageLoop::PostTask", *pending_task); + + bool was_empty = incoming_queue_.empty(); + incoming_queue_.push(std::move(*pending_task)); + + if (is_ready_for_scheduling_ && + (always_schedule_work_ || (!message_loop_scheduled_ && was_empty))) { + // After we've scheduled the message loop, we do not need to do so again + // until we know it has processed all of the work in our queue and is + // waiting for more work again. The message loop will always attempt to + // reload from the incoming queue before waiting again so we clear this + // flag in ReloadWorkQueue(). + message_loop_scheduled_ = true; + return true; } + return false; +} - // Wake up the message loop and schedule work. This is done outside - // |incoming_queue_lock_| because signaling the message loop may cause this - // thread to be switched. If |incoming_queue_lock_| is held, any other thread - // that wants to post a task will be blocked until this thread switches back - // in and releases |incoming_queue_lock_|. - if (schedule_work) - message_loop_->ScheduleWork(); +int IncomingTaskQueue::ReloadWorkQueue(TaskQueue* work_queue) { + // Make sure no tasks are lost. + DCHECK(work_queue->empty()); - return true; + // Acquire all we can from the inter-thread queue with one lock acquisition. + AutoLock lock(incoming_queue_lock_); + if (incoming_queue_.empty()) { + // If the loop attempts to reload but there are no tasks in the incoming + // queue, that means it will go to sleep waiting for more work. If the + // incoming queue becomes nonempty we need to schedule it again. + message_loop_scheduled_ = false; + } else { + incoming_queue_.swap(*work_queue); + } + // Reset the count of high resolution tasks since our queue is now empty. + int high_res_tasks = high_res_task_count_; + high_res_task_count_ = 0; + return high_res_tasks; } } // namespace internal diff --git a/chromium/base/message_loop/incoming_task_queue.h b/chromium/base/message_loop/incoming_task_queue.h index d6028cb914e..861f0fc1796 100644 --- a/chromium/base/message_loop/incoming_task_queue.h +++ b/chromium/base/message_loop/incoming_task_queue.h @@ -7,16 +7,18 @@ #include "base/base_export.h" #include "base/callback.h" +#include "base/debug/task_annotator.h" #include "base/macros.h" #include "base/memory/ref_counted.h" #include "base/pending_task.h" +#include "base/sequence_checker.h" #include "base/synchronization/lock.h" -#include "base/synchronization/read_write_lock.h" #include "base/time/time.h" namespace base { class MessageLoop; +class PostTaskTest; namespace internal { @@ -26,6 +28,41 @@ namespace internal { class BASE_EXPORT IncomingTaskQueue : public RefCountedThreadSafe<IncomingTaskQueue> { public: + // Provides a read and remove only view into a task queue. + class ReadAndRemoveOnlyQueue { + public: + ReadAndRemoveOnlyQueue() = default; + virtual ~ReadAndRemoveOnlyQueue() = default; + + // Returns the next task. HasTasks() is assumed to be true. + virtual const PendingTask& Peek() = 0; + + // Removes and returns the next task. HasTasks() is assumed to be true. + virtual PendingTask Pop() = 0; + + // Whether this queue has tasks. + virtual bool HasTasks() = 0; + + // Removes all tasks. + virtual void Clear() = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(ReadAndRemoveOnlyQueue); + }; + + // Provides a read-write task queue. + class Queue : public ReadAndRemoveOnlyQueue { + public: + Queue() = default; + ~Queue() override = default; + + // Adds the task to the end of the queue. + virtual void Push(PendingTask pending_task) = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(Queue); + }; + explicit IncomingTaskQueue(MessageLoop* message_loop); // Appends a task to the incoming queue. Posting of all tasks is routed though @@ -35,19 +72,14 @@ class BASE_EXPORT IncomingTaskQueue // Returns true if the task was successfully added to the queue, otherwise // returns false. In all cases, the ownership of |task| is transferred to the // called method. - bool AddToIncomingQueue(const tracked_objects::Location& from_here, + bool AddToIncomingQueue(const Location& from_here, OnceClosure task, TimeDelta delay, - bool nestable); + Nestable nestable); // Returns true if the message loop is "idle". Provided for testing. bool IsIdleForTesting(); - // Loads tasks from the |incoming_queue_| into |*work_queue|. Must be called - // from the thread that is running the loop. Returns the number of tasks that - // require high resolution timers. - int ReloadWorkQueue(TaskQueue* work_queue); - // Disconnects |this| from the parent message loop. void WillDestroyCurrentMessageLoop(); @@ -55,8 +87,107 @@ class BASE_EXPORT IncomingTaskQueue // scheduling work. void StartScheduling(); + // Runs |pending_task|. + void RunTask(PendingTask* pending_task); + + ReadAndRemoveOnlyQueue& triage_tasks() { return triage_tasks_; } + + Queue& delayed_tasks() { return delayed_tasks_; } + + Queue& deferred_tasks() { return deferred_tasks_; } + + bool HasPendingHighResolutionTasks() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return pending_high_res_tasks_ > 0; + } + private: + friend class base::PostTaskTest; friend class RefCountedThreadSafe<IncomingTaskQueue>; + + // These queues below support the previous MessageLoop behavior of + // maintaining three queue queues to process tasks: + // + // TriageQueue + // The first queue to receive all tasks for the processing sequence. Tasks are + // generally either dispatched immediately or sent to the queues below. + // + // DelayedQueue + // The queue for holding tasks that should be run later and sorted by expected + // run time. + // + // DeferredQueue + // The queue for holding tasks that couldn't be run while the MessageLoop was + // nested. These are generally processed during the idle stage. + // + // Many of these do not share implementations even though they look like they + // could because of small quirks (reloading semantics) or differing underlying + // data strucutre (TaskQueue vs DelayedTaskQueue). + + // The starting point for all tasks on the sequence processing the tasks. + class TriageQueue : public ReadAndRemoveOnlyQueue { + public: + TriageQueue(IncomingTaskQueue* outer); + ~TriageQueue() override; + + // ReadAndRemoveOnlyQueue: + // In general, the methods below will attempt to reload from the incoming + // queue if the queue itself is empty except for Clear(). See Clear() for + // why it doesn't reload. + const PendingTask& Peek() override; + PendingTask Pop() override; + // Whether this queue has tasks after reloading from the incoming queue. + bool HasTasks() override; + void Clear() override; + + private: + void ReloadFromIncomingQueueIfEmpty(); + + IncomingTaskQueue* const outer_; + TaskQueue queue_; + + DISALLOW_COPY_AND_ASSIGN(TriageQueue); + }; + + class DelayedQueue : public Queue { + public: + DelayedQueue(IncomingTaskQueue* outer); + ~DelayedQueue() override; + + // Queue: + const PendingTask& Peek() override; + PendingTask Pop() override; + // Whether this queue has tasks after sweeping the cancelled ones in front. + bool HasTasks() override; + void Clear() override; + void Push(PendingTask pending_task) override; + + private: + IncomingTaskQueue* const outer_; + DelayedTaskQueue queue_; + + DISALLOW_COPY_AND_ASSIGN(DelayedQueue); + }; + + class DeferredQueue : public Queue { + public: + DeferredQueue(IncomingTaskQueue* outer); + ~DeferredQueue() override; + + // Queue: + const PendingTask& Peek() override; + PendingTask Pop() override; + bool HasTasks() override; + void Clear() override; + void Push(PendingTask pending_task) override; + + private: + IncomingTaskQueue* const outer_; + TaskQueue queue_; + + DISALLOW_COPY_AND_ASSIGN(DeferredQueue); + }; + virtual ~IncomingTaskQueue(); // Adds a task to |incoming_queue_|. The caller retains ownership of @@ -65,42 +196,67 @@ class BASE_EXPORT IncomingTaskQueue // does not retain |pending_task->task| beyond this function call. bool PostPendingTask(PendingTask* pending_task); - // Wakes up the message loop and schedules work. - void ScheduleWork(); + // Does the real work of posting a pending task. Returns true if the caller + // should call ScheduleWork() on the message loop. + bool PostPendingTaskLockRequired(PendingTask* pending_task); - // Number of tasks that require high resolution timing. This value is kept - // so that ReloadWorkQueue() completes in constant time. - int high_res_task_count_; + // Loads tasks from the |incoming_queue_| into |*work_queue|. Must be called + // from the sequence processing the tasks. Returns the number of tasks that + // require high resolution timers in |work_queue|. + int ReloadWorkQueue(TaskQueue* work_queue); - // The lock that protects access to the members of this class, except - // |message_loop_|. + // Checks calls made only on the MessageLoop thread. + SEQUENCE_CHECKER(sequence_checker_); + + debug::TaskAnnotator task_annotator_; + + // True if we always need to call ScheduleWork when receiving a new task, even + // if the incoming queue was not empty. + const bool always_schedule_work_; + + // Queue for initial triaging of tasks on the |sequence_checker_| sequence. + TriageQueue triage_tasks_; + + // Queue for delayed tasks on the |sequence_checker_| sequence. + DelayedQueue delayed_tasks_; + + // Queue for non-nestable deferred tasks on the |sequence_checker_| sequence. + DeferredQueue deferred_tasks_; + + // Number of high resolution tasks in the sequence affine queues above. + int pending_high_res_tasks_ = 0; + + // Lock that protects |message_loop_| to prevent it from being deleted while + // a request is made to schedule work. + base::Lock message_loop_lock_; + + // Points to the message loop that owns |this|. + MessageLoop* message_loop_; + + // Synchronizes access to all members below this line. base::Lock incoming_queue_lock_; - // Lock that protects |message_loop_| to prevent it from being deleted while a - // task is being posted. - base::subtle::ReadWriteLock message_loop_lock_; + // Number of tasks that require high resolution timing. This value is kept + // so that ReloadWorkQueue() completes in constant time. + int high_res_task_count_ = 0; // An incoming queue of tasks that are acquired under a mutex for processing // on this instance's thread. These tasks have not yet been been pushed to // |message_loop_|. TaskQueue incoming_queue_; - // Points to the message loop that owns |this|. - MessageLoop* message_loop_; + // True if new tasks should be accepted. + bool accept_new_tasks_ = true; // The next sequence number to use for delayed tasks. - int next_sequence_num_; + int next_sequence_num_ = 0; // True if our message loop has already been scheduled and does not need to be // scheduled again until an empty reload occurs. - bool message_loop_scheduled_; - - // True if we always need to call ScheduleWork when receiving a new task, even - // if the incoming queue was not empty. - const bool always_schedule_work_; + bool message_loop_scheduled_ = false; // False until StartScheduling() is called. - bool is_ready_for_scheduling_; + bool is_ready_for_scheduling_ = false; DISALLOW_COPY_AND_ASSIGN(IncomingTaskQueue); }; diff --git a/chromium/base/message_loop/message_loop.cc b/chromium/base/message_loop/message_loop.cc index 67b9e225617..f60fd180641 100644 --- a/chromium/base/message_loop/message_loop.cc +++ b/chromium/base/message_loop/message_loop.cc @@ -118,16 +118,15 @@ MessageLoop::~MessageLoop() { // tasks. Normally, we should only pass through this loop once or twice. If // we end up hitting the loop limit, then it is probably due to one task that // is being stubborn. Inspect the queues to see who is left. - bool did_work; + bool tasks_remain; for (int i = 0; i < 100; ++i) { DeletePendingTasks(); - ReloadWorkQueue(); // If we end up with empty queues, then break out of the loop. - did_work = DeletePendingTasks(); - if (!did_work) + tasks_remain = incoming_task_queue_->triage_tasks().HasTasks(); + if (!tasks_remain) break; } - DCHECK(!did_work); + DCHECK(!tasks_remain); // Let interested parties have one last shot at accessing this. for (auto& observer : destruction_observers_) @@ -239,11 +238,11 @@ void MessageLoop::SetNestableTasksAllowed(bool allowed) { // loop that does not go through RunLoop::Run(). pump_->ScheduleWork(); } - nestable_tasks_allowed_ = allowed; + task_execution_allowed_ = allowed; } bool MessageLoop::NestableTasksAllowed() const { - return nestable_tasks_allowed_ || run_loop_client_->ProcessingTasksAllowed(); + return task_execution_allowed_; } // TODO(gab): Migrate TaskObservers to RunLoop as part of separating concerns @@ -278,18 +277,11 @@ std::unique_ptr<MessageLoop> MessageLoop::CreateUnbound( MessageLoop::MessageLoop(Type type, MessagePumpFactoryCallback pump_factory) : type_(type), -#if defined(OS_WIN) - pending_high_res_tasks_(0), - in_high_res_mode_(false), -#endif - nestable_tasks_allowed_(true), pump_factory_(std::move(pump_factory)), - current_pending_task_(nullptr), incoming_task_queue_(new internal::IncomingTaskQueue(this)), unbound_task_runner_( new internal::MessageLoopTaskRunner(incoming_task_queue_)), - task_runner_(unbound_task_runner_), - thread_id_(kInvalidThreadId) { + task_runner_(unbound_task_runner_) { // If type is TYPE_CUSTOM non-null pump_factory must be given. DCHECK(type_ != TYPE_CUSTOM || !pump_factory_.is_null()); } @@ -341,9 +333,17 @@ void MessageLoop::ClearTaskRunnerForTesting() { thread_task_runner_handle_.reset(); } -void MessageLoop::Run() { +void MessageLoop::Run(bool application_tasks_allowed) { DCHECK_EQ(this, current()); - pump_->Run(this); + if (application_tasks_allowed && !task_execution_allowed_) { + // Allow nested task execution as explicitly requested. + DCHECK(run_loop_client_->IsNested()); + task_execution_allowed_ = true; + pump_->Run(this); + task_execution_allowed_ = false; + } else { + pump_->Run(this); + } } void MessageLoop::Quit() { @@ -353,8 +353,7 @@ void MessageLoop::Quit() { void MessageLoop::EnsureWorkScheduled() { DCHECK_EQ(this, current()); - ReloadWorkQueue(); - if (!work_queue_.empty()) + if (incoming_task_queue_->triage_tasks().HasTasks()) pump_->ScheduleWork(); } @@ -370,50 +369,40 @@ bool MessageLoop::ProcessNextDelayedNonNestableTask() { if (run_loop_client_->IsNested()) return false; - while (!deferred_non_nestable_work_queue_.empty()) { - PendingTask pending_task = - std::move(deferred_non_nestable_work_queue_.front()); - deferred_non_nestable_work_queue_.pop(); - + while (incoming_task_queue_->deferred_tasks().HasTasks()) { + PendingTask pending_task = incoming_task_queue_->deferred_tasks().Pop(); if (!pending_task.task.IsCancelled()) { RunTask(&pending_task); return true; } - -#if defined(OS_WIN) - DecrementHighResTaskCountIfNeeded(pending_task); -#endif } return false; } void MessageLoop::RunTask(PendingTask* pending_task) { - DCHECK(NestableTasksAllowed()); + DCHECK(task_execution_allowed_); current_pending_task_ = pending_task; -#if defined(OS_WIN) - DecrementHighResTaskCountIfNeeded(*pending_task); -#endif - // Execute the task and assume the worst: It is probably not reentrant. - nestable_tasks_allowed_ = false; + task_execution_allowed_ = false; TRACE_TASK_EXECUTION("MessageLoop::RunTask", *pending_task); for (auto& observer : task_observers_) observer.WillProcessTask(*pending_task); - task_annotator_.RunTask("MessageLoop::PostTask", pending_task); + incoming_task_queue_->RunTask(pending_task); for (auto& observer : task_observers_) observer.DidProcessTask(*pending_task); - nestable_tasks_allowed_ = true; + task_execution_allowed_ = true; current_pending_task_ = nullptr; } bool MessageLoop::DeferOrRunPendingTask(PendingTask pending_task) { - if (pending_task.nestable || !run_loop_client_->IsNested()) { + if (pending_task.nestable == Nestable::kNestable || + !run_loop_client_->IsNested()) { RunTask(&pending_task); // Show that we ran a task (Note: a new one might arrive as a // consequence!). @@ -422,71 +411,17 @@ bool MessageLoop::DeferOrRunPendingTask(PendingTask pending_task) { // We couldn't run the task now because we're in a nested run loop // and the task isn't nestable. - deferred_non_nestable_work_queue_.push(std::move(pending_task)); - return false; -} - -void MessageLoop::AddToDelayedWorkQueue(PendingTask pending_task) { - // Move to the delayed work queue. - delayed_work_queue_.push(std::move(pending_task)); -} - -bool MessageLoop::SweepDelayedWorkQueueAndReturnTrueIfStillHasWork() { - while (!delayed_work_queue_.empty()) { - const PendingTask& pending_task = delayed_work_queue_.top(); - if (!pending_task.task.IsCancelled()) - return true; - -#if defined(OS_WIN) - DecrementHighResTaskCountIfNeeded(pending_task); -#endif - delayed_work_queue_.pop(); - } + incoming_task_queue_->deferred_tasks().Push(std::move(pending_task)); return false; } -bool MessageLoop::DeletePendingTasks() { - bool did_work = !work_queue_.empty(); - while (!work_queue_.empty()) { - PendingTask pending_task = std::move(work_queue_.front()); - work_queue_.pop(); - if (!pending_task.delayed_run_time.is_null()) { - // We want to delete delayed tasks in the same order in which they would - // normally be deleted in case of any funny dependencies between delayed - // tasks. - AddToDelayedWorkQueue(std::move(pending_task)); - } - } - did_work |= !deferred_non_nestable_work_queue_.empty(); - while (!deferred_non_nestable_work_queue_.empty()) { - deferred_non_nestable_work_queue_.pop(); - } - did_work |= !delayed_work_queue_.empty(); - - // Historically, we always delete the task regardless of valgrind status. It's - // not completely clear why we want to leak them in the loops above. This - // code is replicating legacy behavior, and should not be considered - // absolutely "correct" behavior. See TODO above about deleting all tasks - // when it's safe. - while (!delayed_work_queue_.empty()) { - delayed_work_queue_.pop(); - } - return did_work; -} - -void MessageLoop::ReloadWorkQueue() { - // We can improve performance of our loading tasks from the incoming queue to - // |*work_queue| by waiting until the last minute (|*work_queue| is empty) to - // load. That reduces the number of locks-per-task significantly when our - // queues get large. - if (work_queue_.empty()) { -#if defined(OS_WIN) - pending_high_res_tasks_ += - incoming_task_queue_->ReloadWorkQueue(&work_queue_); -#else - incoming_task_queue_->ReloadWorkQueue(&work_queue_); -#endif - } +void MessageLoop::DeletePendingTasks() { + incoming_task_queue_->triage_tasks().Clear(); + incoming_task_queue_->deferred_tasks().Clear(); + // TODO(robliao): Determine if we can move delayed task destruction before + // deferred tasks to maintain the MessagePump DoWork, DoDelayedWork, and + // DoIdleWork processing order. + incoming_task_queue_->delayed_tasks().Clear(); } void MessageLoop::ScheduleWork() { @@ -494,37 +429,27 @@ void MessageLoop::ScheduleWork() { } bool MessageLoop::DoWork() { - if (!NestableTasksAllowed()) { - // Task can't be executed right now. + if (!task_execution_allowed_) return false; - } - - for (;;) { - ReloadWorkQueue(); - if (work_queue_.empty()) - break; - // Execute oldest task. - do { - PendingTask pending_task = std::move(work_queue_.front()); - work_queue_.pop(); + // Execute oldest task. + while (incoming_task_queue_->triage_tasks().HasTasks()) { + PendingTask pending_task = incoming_task_queue_->triage_tasks().Pop(); + if (pending_task.task.IsCancelled()) + continue; - if (pending_task.task.IsCancelled()) { -#if defined(OS_WIN) - DecrementHighResTaskCountIfNeeded(pending_task); -#endif - } else if (!pending_task.delayed_run_time.is_null()) { - int sequence_num = pending_task.sequence_num; - TimeTicks delayed_run_time = pending_task.delayed_run_time; - AddToDelayedWorkQueue(std::move(pending_task)); - // If we changed the topmost task, then it is time to reschedule. - if (delayed_work_queue_.top().sequence_num == sequence_num) - pump_->ScheduleDelayedWork(delayed_run_time); - } else { - if (DeferOrRunPendingTask(std::move(pending_task))) - return true; + if (!pending_task.delayed_run_time.is_null()) { + int sequence_num = pending_task.sequence_num; + TimeTicks delayed_run_time = pending_task.delayed_run_time; + incoming_task_queue_->delayed_tasks().Push(std::move(pending_task)); + // If we changed the topmost task, then it is time to reschedule. + if (incoming_task_queue_->delayed_tasks().Peek().sequence_num == + sequence_num) { + pump_->ScheduleDelayedWork(delayed_run_time); } - } while (!work_queue_.empty()); + } else if (DeferOrRunPendingTask(std::move(pending_task))) { + return true; + } } // Nothing happened. @@ -532,8 +457,8 @@ bool MessageLoop::DoWork() { } bool MessageLoop::DoDelayedWork(TimeTicks* next_delayed_work_time) { - if (!NestableTasksAllowed() || - !SweepDelayedWorkQueueAndReturnTrueIfStillHasWork()) { + if (!task_execution_allowed_ || + !incoming_task_queue_->delayed_tasks().HasTasks()) { recent_time_ = *next_delayed_work_time = TimeTicks(); return false; } @@ -545,7 +470,8 @@ bool MessageLoop::DoDelayedWork(TimeTicks* next_delayed_work_time) { // fall behind (and have a lot of ready-to-run delayed tasks), the more // efficient we'll be at handling the tasks. - TimeTicks next_run_time = delayed_work_queue_.top().delayed_run_time; + TimeTicks next_run_time = + incoming_task_queue_->delayed_tasks().Peek().delayed_run_time; if (next_run_time > recent_time_) { recent_time_ = TimeTicks::Now(); // Get a better view of Now(); if (next_run_time > recent_time_) { @@ -554,12 +480,12 @@ bool MessageLoop::DoDelayedWork(TimeTicks* next_delayed_work_time) { } } - PendingTask pending_task = - std::move(const_cast<PendingTask&>(delayed_work_queue_.top())); - delayed_work_queue_.pop(); + PendingTask pending_task = incoming_task_queue_->delayed_tasks().Pop(); - if (SweepDelayedWorkQueueAndReturnTrueIfStillHasWork()) - *next_delayed_work_time = delayed_work_queue_.top().delayed_run_time; + if (incoming_task_queue_->delayed_tasks().HasTasks()) { + *next_delayed_work_time = + incoming_task_queue_->delayed_tasks().Peek().delayed_run_time; + } return DeferOrRunPendingTask(std::move(pending_task)); } @@ -577,7 +503,7 @@ bool MessageLoop::DoIdleWork() { // _if_ triggered by the timer happens with good resolution. If we don't // do this the default resolution is 15ms which might not be acceptable // for some tasks. - bool high_res = pending_high_res_tasks_ > 0; + bool high_res = incoming_task_queue_->HasPendingHighResolutionTasks(); if (high_res != in_high_res_mode_) { in_high_res_mode_ = high_res; Time::ActivateHighResolutionTimer(in_high_res_mode_); @@ -586,16 +512,6 @@ bool MessageLoop::DoIdleWork() { return false; } -#if defined(OS_WIN) -void MessageLoop::DecrementHighResTaskCountIfNeeded( - const PendingTask& pending_task) { - if (!pending_task.is_high_res) - return; - --pending_high_res_tasks_; - DCHECK_GE(pending_high_res_tasks_, 0); -} -#endif - #if !defined(OS_NACL) //------------------------------------------------------------------------------ // MessageLoopForUI @@ -683,13 +599,13 @@ bool MessageLoopForIO::WatchFileDescriptor(int fd, #if defined(OS_FUCHSIA) // Additional watch API for native platform resources. -bool MessageLoopForIO::WatchMxHandle(mx_handle_t handle, +bool MessageLoopForIO::WatchZxHandle(zx_handle_t handle, bool persistent, - mx_signals_t signals, - MxHandleWatchController* controller, - MxHandleWatcher* delegate) { + zx_signals_t signals, + ZxHandleWatchController* controller, + ZxHandleWatcher* delegate) { return ToPumpIO(pump_.get()) - ->WatchMxHandle(handle, persistent, signals, controller, delegate); + ->WatchZxHandle(handle, persistent, signals, controller, delegate); } #endif diff --git a/chromium/base/message_loop/message_loop.h b/chromium/base/message_loop/message_loop.h index a118917ea1c..46171f803a0 100644 --- a/chromium/base/message_loop/message_loop.h +++ b/chromium/base/message_loop/message_loop.h @@ -11,7 +11,6 @@ #include "base/base_export.h" #include "base/callback_forward.h" -#include "base/debug/task_annotator.h" #include "base/gtest_prod_util.h" #include "base/macros.h" #include "base/memory/ref_counted.h" @@ -234,7 +233,7 @@ class BASE_EXPORT MessageLoop : public MessagePump::Delegate, // Enables nestable tasks on |loop| while in scope. // DEPRECATED: This should not be used when the nested loop is driven by - // RunLoop (use RunLoop::Type::KNestableTasksAllowed instead). It can however + // RunLoop (use RunLoop::Type::kNestableTasksAllowed instead). It can however // still be useful in a few scenarios where re-entrancy is caused by a native // message loop. // TODO(gab): Remove usage of this class alongside RunLoop and rename it to @@ -251,8 +250,8 @@ class BASE_EXPORT MessageLoop : public MessagePump::Delegate, } private: - MessageLoop* loop_; - bool old_state_; + MessageLoop* const loop_; + const bool old_state_; }; // A TaskObserver is an object that receives task notifications from the @@ -281,10 +280,6 @@ class BASE_EXPORT MessageLoop : public MessagePump::Delegate, // Returns true if the message loop is "idle". Provided for testing. bool IsIdleForTesting(); - // Returns the TaskAnnotator which is used to add debug information to posted - // tasks. - debug::TaskAnnotator* task_annotator() { return &task_annotator_; } - // Runs the specified PendingTask. void RunTask(PendingTask* pending_task); @@ -338,7 +333,7 @@ class BASE_EXPORT MessageLoop : public MessagePump::Delegate, void SetThreadTaskRunnerHandle(); // RunLoop::Delegate: - void Run() override; + void Run(bool application_tasks_allowed) override; void Quit() override; void EnsureWorkScheduled() override; @@ -349,21 +344,9 @@ class BASE_EXPORT MessageLoop : public MessagePump::Delegate, // cannot be run right now. Returns true if the task was run. bool DeferOrRunPendingTask(PendingTask pending_task); - // Adds the pending task to delayed_work_queue_. - void AddToDelayedWorkQueue(PendingTask pending_task); - - // Sweeps any cancelled tasks from the front of the delayed work queue and - // returns true if there is remaining work. - bool SweepDelayedWorkQueueAndReturnTrueIfStillHasWork(); - // Delete tasks that haven't run yet without running them. Used in the - // destructor to make sure all the task's destructors get called. Returns - // true if some work was done. - bool DeletePendingTasks(); - - // Loads tasks from the incoming queue to |work_queue_| if the latter is - // empty. - void ReloadWorkQueue(); + // destructor to make sure all the task's destructors get called. + void DeletePendingTasks(); // Wakes up the message pump. Can be called on any thread. The caller is // responsible for synchronizing ScheduleWork() calls. @@ -376,42 +359,26 @@ class BASE_EXPORT MessageLoop : public MessagePump::Delegate, const Type type_; - // A list of tasks that need to be processed by this instance. Note that - // this queue is only accessed (push/pop) by our current thread. - TaskQueue work_queue_; - #if defined(OS_WIN) - // Helper to decrement the high resolution task count if |pending_task| is a - // high resolution task. - void DecrementHighResTaskCountIfNeeded(const PendingTask& pending_Task); - - // How many high resolution tasks are in the pending task queue. This value - // increases by N every time we call ReloadWorkQueue() and decreases by 1 - // every time we call RunTask() if the task needs a high resolution timer. - int pending_high_res_tasks_; // Tracks if we have requested high resolution timers. Its only use is to // turn off the high resolution timer upon loop destruction. - bool in_high_res_mode_; + bool in_high_res_mode_ = false; #endif - // Contains delayed tasks, sorted by their 'delayed_run_time' property. - DelayedTaskQueue delayed_work_queue_; - // A recent snapshot of Time::Now(), used to check delayed_work_queue_. TimeTicks recent_time_; - // A queue of non-nestable tasks that we had to defer because when it came - // time to execute them we were in a nested run loop. They will execute - // once we're out of nested run loops. - TaskQueue deferred_non_nestable_work_queue_; - ObserverList<DestructionObserver> destruction_observers_; - // A recursion block that prevents accidentally running additional tasks when - // insider a (accidentally induced?) nested message pump. Deprecated in favor - // of run_loop_client_->ProcessingTasksAllowed(), equivalent until then (both - // need to be checked in conditionals). - bool nestable_tasks_allowed_; + // A boolean which prevents unintentional reentrant task execution (e.g. from + // induced nested message loops). As such, nested message loops will only + // process system messages (not application tasks) by default. A nested loop + // layer must have been explicitly granted permission to be able to execute + // application tasks. This is granted either by + // RunLoop::Type::kNestableTasksAllowed when the loop is driven by the + // application or by a ScopedNestableTaskAllower preceding a system call that + // is known to generate a system-driven nested loop. + bool task_execution_allowed_ = true; // pump_factory_.Run() is called to create a message pump for this loop // if type_ is TYPE_CUSTOM and pump_ is null. @@ -419,14 +386,12 @@ class BASE_EXPORT MessageLoop : public MessagePump::Delegate, ObserverList<TaskObserver> task_observers_; - debug::TaskAnnotator task_annotator_; - // Used to allow creating a breadcrumb of program counters in PostTask. // This variable is only initialized while a task is being executed and is // meant only to store context for creating a backtrace breadcrumb. Do not // attach other semantics to it without thinking through the use caes // thoroughly. - const PendingTask* current_pending_task_; + const PendingTask* current_pending_task_ = nullptr; scoped_refptr<internal::IncomingTaskQueue> incoming_task_queue_; @@ -439,7 +404,7 @@ class BASE_EXPORT MessageLoop : public MessagePump::Delegate, // Id of the thread this message loop is bound to. Initialized once when the // MessageLoop is bound to its thread and constant forever after. - PlatformThreadId thread_id_; + PlatformThreadId thread_id_ = kInvalidThreadId; // Whether task observers are allowed. bool allow_task_observers_ = true; @@ -564,8 +529,8 @@ class BASE_EXPORT MessageLoopForIO : public MessageLoop { WATCH_WRITE = MessagePumpFuchsia::WATCH_WRITE, WATCH_READ_WRITE = MessagePumpFuchsia::WATCH_READ_WRITE}; - typedef MessagePumpFuchsia::MxHandleWatchController MxHandleWatchController; - typedef MessagePumpFuchsia::MxHandleWatcher MxHandleWatcher; + typedef MessagePumpFuchsia::ZxHandleWatchController ZxHandleWatchController; + typedef MessagePumpFuchsia::ZxHandleWatcher ZxHandleWatcher; #elif defined(OS_IOS) typedef MessagePumpIOSForIO::Watcher Watcher; typedef MessagePumpIOSForIO::FileDescriptorWatcher @@ -604,11 +569,11 @@ class BASE_EXPORT MessageLoopForIO : public MessageLoop { #if defined(OS_FUCHSIA) // Additional watch API for native platform resources. - bool WatchMxHandle(mx_handle_t handle, + bool WatchZxHandle(zx_handle_t handle, bool persistent, - mx_signals_t signals, - MxHandleWatchController* controller, - MxHandleWatcher* delegate); + zx_signals_t signals, + ZxHandleWatchController* controller, + ZxHandleWatcher* delegate); #endif }; diff --git a/chromium/base/message_loop/message_loop_io_posix_unittest.cc b/chromium/base/message_loop/message_loop_io_posix_unittest.cc index 6e6a92a6bef..5038bea7f8a 100644 --- a/chromium/base/message_loop/message_loop_io_posix_unittest.cc +++ b/chromium/base/message_loop/message_loop_io_posix_unittest.cc @@ -225,6 +225,27 @@ TEST_F(MessageLoopForIoPosixTest, WatchWritable) { ASSERT_TRUE(handler.is_writable_); } +// Verify that RunUntilIdle() receives IO notifications. +TEST_F(MessageLoopForIoPosixTest, RunUntilIdle) { + MessageLoopForIO message_loop; + MessageLoopForIO::FileDescriptorWatcher watcher(FROM_HERE); + TestHandler handler; + + // Watch the pipe for readability. + ASSERT_TRUE(MessageLoopForIO::current()->WatchFileDescriptor( + read_fd_.get(), /*persistent=*/false, MessageLoopForIO::WATCH_READ, + &watcher, &handler)); + + // The pipe should not be readable when first created. + RunLoop().RunUntilIdle(); + ASSERT_FALSE(handler.is_readable_); + + TriggerReadEvent(); + + while (!handler.is_readable_) + RunLoop().RunUntilIdle(); +} + void StopWatching(MessageLoopForIO::FileDescriptorWatcher* controller, RunLoop* run_loop) { controller->StopWatchingFileDescriptor(); diff --git a/chromium/base/message_loop/message_loop_perftest.cc b/chromium/base/message_loop/message_loop_perftest.cc new file mode 100644 index 00000000000..867e8fe850d --- /dev/null +++ b/chromium/base/message_loop/message_loop_perftest.cc @@ -0,0 +1,254 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <memory> +#include <vector> + +#include "base/atomicops.h" +#include "base/bind.h" +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/strings/stringprintf.h" +#include "base/synchronization/atomic_flag.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/platform_thread.h" +#include "base/threading/sequenced_task_runner_handle.h" +#include "base/time/time.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/perf/perf_test.h" + +namespace base { + +namespace { + +// A thread that waits for the caller to signal an event before proceeding to +// call Action::Run(). +class PostingThread { + public: + class Action { + public: + virtual ~Action() = default; + + // Called after the thread is started and |start_event_| is signalled. + virtual void Run() = 0; + + protected: + Action() = default; + + private: + DISALLOW_COPY_AND_ASSIGN(Action); + }; + + // Creates a PostingThread where the thread waits on |start_event| before + // calling action->Run(). If a thread is returned, the thread is guaranteed to + // be allocated and running and the caller must call Join() before destroying + // the PostingThread. + static std::unique_ptr<PostingThread> Create(WaitableEvent* start_event, + std::unique_ptr<Action> action) { + auto posting_thread = + WrapUnique(new PostingThread(start_event, std::move(action))); + + if (!posting_thread->Start()) + return nullptr; + + return posting_thread; + } + + ~PostingThread() { DCHECK_EQ(!thread_handle_.is_null(), join_called_); } + + void Join() { + PlatformThread::Join(thread_handle_); + join_called_ = true; + } + + private: + class Delegate final : public PlatformThread::Delegate { + public: + Delegate(PostingThread* outer, std::unique_ptr<Action> action) + : outer_(outer), action_(std::move(action)) { + DCHECK(outer_); + DCHECK(action_); + } + + ~Delegate() override = default; + + private: + void ThreadMain() override { + outer_->thread_started_.Signal(); + outer_->start_event_->Wait(); + action_->Run(); + } + + PostingThread* const outer_; + const std::unique_ptr<Action> action_; + + DISALLOW_COPY_AND_ASSIGN(Delegate); + }; + + PostingThread(WaitableEvent* start_event, std::unique_ptr<Action> delegate) + : start_event_(start_event), + thread_started_(WaitableEvent::ResetPolicy::MANUAL, + WaitableEvent::InitialState::NOT_SIGNALED), + delegate_(this, std::move(delegate)) { + DCHECK(start_event_); + } + + bool Start() { + bool thread_created = + PlatformThread::Create(0, &delegate_, &thread_handle_); + if (thread_created) + thread_started_.Wait(); + + return thread_created; + } + + bool join_called_ = false; + WaitableEvent* const start_event_; + WaitableEvent thread_started_; + Delegate delegate_; + + PlatformThreadHandle thread_handle_; + + DISALLOW_COPY_AND_ASSIGN(PostingThread); +}; + +class MessageLoopPerfTest : public ::testing::TestWithParam<int> { + public: + MessageLoopPerfTest() + : message_loop_task_runner_(SequencedTaskRunnerHandle::Get()), + run_posting_threads_(WaitableEvent::ResetPolicy::MANUAL, + WaitableEvent::InitialState::NOT_SIGNALED) {} + + static std::string ParamInfoToString( + ::testing::TestParamInfo<int> param_info) { + return PostingThreadCountToString(param_info.param); + } + + static std::string PostingThreadCountToString(int posting_threads) { + // Special case 1 thread for thread vs threads. + if (posting_threads == 1) + return "1_Posting_Thread"; + + return StringPrintf("%d_Posting_Threads", posting_threads); + } + + protected: + class ContinuouslyPostTasks final : public PostingThread::Action { + public: + ContinuouslyPostTasks(MessageLoopPerfTest* outer) : outer_(outer) { + DCHECK(outer_); + } + ~ContinuouslyPostTasks() override = default; + + private: + void Run() override { + RepeatingClosure task_to_run = + BindRepeating([](size_t* num_tasks_run) { ++*num_tasks_run; }, + &outer_->num_tasks_run_); + while (!outer_->stop_posting_threads_.IsSet()) { + outer_->message_loop_task_runner_->PostTask(FROM_HERE, task_to_run); + subtle::NoBarrier_AtomicIncrement(&outer_->num_tasks_posted_, 1); + } + } + + MessageLoopPerfTest* const outer_; + + DISALLOW_COPY_AND_ASSIGN(ContinuouslyPostTasks); + }; + + void SetUp() override { + // This check is here because we can't ASSERT_TRUE in the constructor. + ASSERT_TRUE(message_loop_task_runner_); + } + + // Runs ActionType::Run() on |num_posting_threads| and requests test + // termination around |duration|. + template <typename ActionType> + void RunTest(const int num_posting_threads, TimeDelta duration) { + std::vector<std::unique_ptr<PostingThread>> threads; + for (int i = 0; i < num_posting_threads; ++i) { + threads.emplace_back(PostingThread::Create( + &run_posting_threads_, std::make_unique<ActionType>(this))); + // Don't assert here to simplify the code that requires a Join() call for + // every created PostingThread. + EXPECT_TRUE(threads[i]); + } + + RunLoop run_loop; + message_loop_task_runner_->PostDelayedTask( + FROM_HERE, + BindOnce( + [](RunLoop* run_loop, AtomicFlag* stop_posting_threads) { + stop_posting_threads->Set(); + run_loop->Quit(); + }, + &run_loop, &stop_posting_threads_), + duration); + + TimeTicks post_task_start = TimeTicks::Now(); + run_posting_threads_.Signal(); + + TimeTicks run_loop_start = TimeTicks::Now(); + run_loop.Run(); + tasks_run_duration_ = TimeTicks::Now() - run_loop_start; + + for (auto& thread : threads) + thread->Join(); + + tasks_posted_duration_ = TimeTicks::Now() - post_task_start; + } + + size_t num_tasks_posted() const { + return subtle::NoBarrier_Load(&num_tasks_posted_); + } + + TimeDelta tasks_posted_duration() const { return tasks_posted_duration_; } + + size_t num_tasks_run() const { return num_tasks_run_; } + + TimeDelta tasks_run_duration() const { return tasks_run_duration_; } + + private: + MessageLoop message_loop_; + + // Accessed on multiple threads, thread-safe or constant: + const scoped_refptr<SequencedTaskRunner> message_loop_task_runner_; + WaitableEvent run_posting_threads_; + AtomicFlag stop_posting_threads_; + subtle::AtomicWord num_tasks_posted_ = 0; + + // Accessed only on the test case thread: + TimeDelta tasks_posted_duration_; + TimeDelta tasks_run_duration_; + size_t num_tasks_run_ = 0; + + DISALLOW_COPY_AND_ASSIGN(MessageLoopPerfTest); +}; + +} // namespace + +TEST_P(MessageLoopPerfTest, PostTaskRate) { + // Measures the average rate of posting tasks from different threads and the + // average rate that the message loop is running those tasks. + RunTest<ContinuouslyPostTasks>(GetParam(), TimeDelta::FromSeconds(3)); + perf_test::PrintResult("task_posting", "", + PostingThreadCountToString(GetParam()), + tasks_posted_duration().InMicroseconds() / + static_cast<double>(num_tasks_posted()), + "us/task", true); + perf_test::PrintResult("task_running", "", + PostingThreadCountToString(GetParam()), + tasks_run_duration().InMicroseconds() / + static_cast<double>(num_tasks_run()), + "us/task", true); +} + +INSTANTIATE_TEST_CASE_P(, + MessageLoopPerfTest, + ::testing::Values(1, 5, 10), + MessageLoopPerfTest::ParamInfoToString); +} // namespace base diff --git a/chromium/base/message_loop/message_loop_task_runner.cc b/chromium/base/message_loop/message_loop_task_runner.cc index c7ac583e6f8..5d41986d995 100644 --- a/chromium/base/message_loop/message_loop_task_runner.cc +++ b/chromium/base/message_loop/message_loop_task_runner.cc @@ -24,22 +24,21 @@ void MessageLoopTaskRunner::BindToCurrentThread() { valid_thread_id_ = PlatformThread::CurrentId(); } -bool MessageLoopTaskRunner::PostDelayedTask( - const tracked_objects::Location& from_here, - OnceClosure task, - base::TimeDelta delay) { +bool MessageLoopTaskRunner::PostDelayedTask(const Location& from_here, + OnceClosure task, + base::TimeDelta delay) { DCHECK(!task.is_null()) << from_here.ToString(); return incoming_queue_->AddToIncomingQueue(from_here, std::move(task), delay, - true); + Nestable::kNestable); } bool MessageLoopTaskRunner::PostNonNestableDelayedTask( - const tracked_objects::Location& from_here, + const Location& from_here, OnceClosure task, base::TimeDelta delay) { DCHECK(!task.is_null()) << from_here.ToString(); return incoming_queue_->AddToIncomingQueue(from_here, std::move(task), delay, - false); + Nestable::kNonNestable); } bool MessageLoopTaskRunner::RunsTasksInCurrentSequence() const { diff --git a/chromium/base/message_loop/message_loop_task_runner.h b/chromium/base/message_loop/message_loop_task_runner.h index f3ec51f8638..c7d48c28c15 100644 --- a/chromium/base/message_loop/message_loop_task_runner.h +++ b/chromium/base/message_loop/message_loop_task_runner.h @@ -31,12 +31,12 @@ class BASE_EXPORT MessageLoopTaskRunner : public SingleThreadTaskRunner { void BindToCurrentThread(); // SingleThreadTaskRunner implementation - bool PostDelayedTask(const tracked_objects::Location& from_here, + bool PostDelayedTask(const Location& from_here, OnceClosure task, - base::TimeDelta delay) override; - bool PostNonNestableDelayedTask(const tracked_objects::Location& from_here, + TimeDelta delay) override; + bool PostNonNestableDelayedTask(const Location& from_here, OnceClosure task, - base::TimeDelta delay) override; + TimeDelta delay) override; bool RunsTasksInCurrentSequence() const override; private: diff --git a/chromium/base/message_loop/message_loop_unittest.cc b/chromium/base/message_loop/message_loop_unittest.cc index c01f7e2b800..25718c62050 100644 --- a/chromium/base/message_loop/message_loop_unittest.cc +++ b/chromium/base/message_loop/message_loop_unittest.cc @@ -1564,6 +1564,97 @@ TEST_P(MessageLoopTypedTest, RecursivePosts) { RunLoop().Run(); } +TEST_P(MessageLoopTypedTest, NestableTasksAllowedAtTopLevel) { + MessageLoop loop(GetParam()); + EXPECT_TRUE(MessageLoop::current()->NestableTasksAllowed()); +} + +// Nestable tasks shouldn't be allowed to run reentrantly by default (regression +// test for https://crbug.com/754112). +TEST_P(MessageLoopTypedTest, NestableTasksDisallowedByDefault) { + MessageLoop loop(GetParam()); + RunLoop run_loop; + loop.task_runner()->PostTask( + FROM_HERE, + BindOnce( + [](RunLoop* run_loop) { + EXPECT_FALSE(MessageLoop::current()->NestableTasksAllowed()); + run_loop->Quit(); + }, + Unretained(&run_loop))); + run_loop.Run(); +} + +TEST_P(MessageLoopTypedTest, NestableTasksProcessedWhenRunLoopAllows) { + MessageLoop loop(GetParam()); + RunLoop run_loop; + loop.task_runner()->PostTask( + FROM_HERE, + BindOnce( + [](RunLoop* run_loop) { + // This test would hang if this RunLoop wasn't of type + // kNestableTasksAllowed (i.e. this is testing that this is + // processed and doesn't hang). + RunLoop nested_run_loop(RunLoop::Type::kNestableTasksAllowed); + ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + BindOnce( + [](RunLoop* nested_run_loop) { + // Each additional layer of application task nesting + // requires its own allowance. The kNestableTasksAllowed + // RunLoop allowed this task to be processed but further + // nestable tasks are by default disallowed from this + // layer. + EXPECT_FALSE( + MessageLoop::current()->NestableTasksAllowed()); + nested_run_loop->Quit(); + }, + Unretained(&nested_run_loop))); + nested_run_loop.Run(); + + run_loop->Quit(); + }, + Unretained(&run_loop))); + run_loop.Run(); +} + +TEST_P(MessageLoopTypedTest, NestableTasksAllowedExplicitlyInScope) { + MessageLoop loop(GetParam()); + RunLoop run_loop; + loop.task_runner()->PostTask( + FROM_HERE, + BindOnce( + [](RunLoop* run_loop) { + { + MessageLoop::ScopedNestableTaskAllower allow_nestable_tasks( + MessageLoop::current()); + EXPECT_TRUE(MessageLoop::current()->NestableTasksAllowed()); + } + EXPECT_FALSE(MessageLoop::current()->NestableTasksAllowed()); + run_loop->Quit(); + }, + Unretained(&run_loop))); + run_loop.Run(); +} + +TEST_P(MessageLoopTypedTest, NestableTasksAllowedManually) { + MessageLoop loop(GetParam()); + RunLoop run_loop; + loop.task_runner()->PostTask( + FROM_HERE, + BindOnce( + [](RunLoop* run_loop) { + EXPECT_FALSE(MessageLoop::current()->NestableTasksAllowed()); + MessageLoop::current()->SetNestableTasksAllowed(true); + EXPECT_TRUE(MessageLoop::current()->NestableTasksAllowed()); + MessageLoop::current()->SetNestableTasksAllowed(false); + EXPECT_FALSE(MessageLoop::current()->NestableTasksAllowed()); + run_loop->Quit(); + }, + Unretained(&run_loop))); + run_loop.Run(); +} + INSTANTIATE_TEST_CASE_P(, MessageLoopTypedTest, ::testing::Values(MessageLoop::TYPE_DEFAULT, diff --git a/chromium/base/message_loop/message_pump_default.cc b/chromium/base/message_loop/message_pump_default.cc index 651ebdc8ead..50dbc6f7183 100644 --- a/chromium/base/message_loop/message_pump_default.cc +++ b/chromium/base/message_loop/message_pump_default.cc @@ -10,6 +10,10 @@ #include "build/build_config.h" #if defined(OS_MACOSX) +#include <mach/thread_policy.h> + +#include "base/mac/mach_logging.h" +#include "base/mac/scoped_mach_port.h" #include "base/mac/scoped_nsautorelease_pool.h" #endif @@ -81,4 +85,19 @@ void MessagePumpDefault::ScheduleDelayedWork( delayed_work_time_ = delayed_work_time; } +#if defined(OS_MACOSX) +void MessagePumpDefault::SetTimerSlack(TimerSlack timer_slack) { + thread_latency_qos_policy_data_t policy{}; + policy.thread_latency_qos_tier = timer_slack == TIMER_SLACK_MAXIMUM + ? LATENCY_QOS_TIER_5 + : LATENCY_QOS_TIER_UNSPECIFIED; + mac::ScopedMachSendRight thread_port(mach_thread_self()); + kern_return_t kr = + thread_policy_set(thread_port.get(), THREAD_LATENCY_QOS_POLICY, + reinterpret_cast<thread_policy_t>(&policy), + THREAD_LATENCY_QOS_POLICY_COUNT); + MACH_DVLOG_IF(1, kr != KERN_SUCCESS, kr) << "thread_policy_set"; +} +#endif + } // namespace base diff --git a/chromium/base/message_loop/message_pump_default.h b/chromium/base/message_loop/message_pump_default.h index 4cd7cd17d56..dd11adcb6c0 100644 --- a/chromium/base/message_loop/message_pump_default.h +++ b/chromium/base/message_loop/message_pump_default.h @@ -10,6 +10,7 @@ #include "base/message_loop/message_pump.h" #include "base/synchronization/waitable_event.h" #include "base/time/time.h" +#include "build/build_config.h" namespace base { @@ -23,6 +24,9 @@ class BASE_EXPORT MessagePumpDefault : public MessagePump { void Quit() override; void ScheduleWork() override; void ScheduleDelayedWork(const TimeTicks& delayed_work_time) override; +#if defined(OS_MACOSX) + void SetTimerSlack(TimerSlack timer_slack) override; +#endif private: // This flag is set to false when Run should return. diff --git a/chromium/base/message_loop/message_pump_fuchsia.cc b/chromium/base/message_loop/message_pump_fuchsia.cc index 598bff28dcf..22fb3a1eb0a 100644 --- a/chromium/base/message_loop/message_pump_fuchsia.cc +++ b/chromium/base/message_loop/message_pump_fuchsia.cc @@ -4,24 +4,24 @@ #include "base/message_loop/message_pump_fuchsia.h" -#include <magenta/status.h> -#include <magenta/syscalls.h> +#include <zircon/status.h> +#include <zircon/syscalls.h> #include "base/auto_reset.h" #include "base/logging.h" namespace base { -MessagePumpFuchsia::MxHandleWatchController::MxHandleWatchController( - const tracked_objects::Location& from_here) +MessagePumpFuchsia::ZxHandleWatchController::ZxHandleWatchController( + const Location& from_here) : created_from_location_(from_here) {} -MessagePumpFuchsia::MxHandleWatchController::~MxHandleWatchController() { - if (!StopWatchingMxHandle()) +MessagePumpFuchsia::ZxHandleWatchController::~ZxHandleWatchController() { + if (!StopWatchingZxHandle()) NOTREACHED(); } -bool MessagePumpFuchsia::MxHandleWatchController::StopWatchingMxHandle() { +bool MessagePumpFuchsia::ZxHandleWatchController::StopWatchingZxHandle() { if (was_stopped_) { DCHECK(!*was_stopped_); *was_stopped_ = true; @@ -42,36 +42,36 @@ bool MessagePumpFuchsia::MxHandleWatchController::StopWatchingMxHandle() { if (!weak_pump_) return true; - int result = mx_port_cancel(weak_pump_->port_.get(), handle_, wait_key()); - DLOG_IF(ERROR, result != MX_OK) - << "mx_port_cancel(handle=" << handle_ - << ") failed: " << mx_status_get_string(result); + int result = zx_port_cancel(weak_pump_->port_.get(), handle_, wait_key()); + DLOG_IF(ERROR, result != ZX_OK) + << "zx_port_cancel(handle=" << handle_ + << ") failed: " << zx_status_get_string(result); - return result == MX_OK; + return result == ZX_OK; } -void MessagePumpFuchsia::FdWatchController::OnMxHandleSignalled( - mx_handle_t handle, - mx_signals_t signals) { +void MessagePumpFuchsia::FdWatchController::OnZxHandleSignalled( + zx_handle_t handle, + zx_signals_t signals) { uint32_t events; - __mxio_wait_end(io_, signals, &events); + __fdio_wait_end(io_, signals, &events); // Each |watcher_| callback we invoke may stop or delete |this|. The pump has // set |was_stopped_| to point to a safe location on the calling stack, so we // can use that to detect being stopped mid-callback and avoid doing further // work that would touch |this|. bool* was_stopped = was_stopped_; - if (events & MXIO_EVT_WRITABLE) + if (events & FDIO_EVT_WRITABLE) watcher_->OnFileCanWriteWithoutBlocking(fd_); - if (!*was_stopped && (events & MXIO_EVT_READABLE)) + if (!*was_stopped && (events & FDIO_EVT_READABLE)) watcher_->OnFileCanReadWithoutBlocking(fd_); // Don't add additional work here without checking |*was_stopped_| again. } MessagePumpFuchsia::FdWatchController::FdWatchController( - const tracked_objects::Location& from_here) - : MxHandleWatchController(from_here) {} + const Location& from_here) + : ZxHandleWatchController(from_here) {} MessagePumpFuchsia::FdWatchController::~FdWatchController() { if (!StopWatchingFileDescriptor()) @@ -79,16 +79,16 @@ MessagePumpFuchsia::FdWatchController::~FdWatchController() { } bool MessagePumpFuchsia::FdWatchController::StopWatchingFileDescriptor() { - bool success = StopWatchingMxHandle(); + bool success = StopWatchingZxHandle(); if (io_) { - __mxio_release(io_); + __fdio_release(io_); io_ = nullptr; } return success; } MessagePumpFuchsia::MessagePumpFuchsia() : weak_factory_(this) { - CHECK_EQ(MX_OK, mx_port_create(0, port_.receive())); + CHECK_EQ(ZX_OK, zx_port_create(0, port_.receive())); } bool MessagePumpFuchsia::WatchFileDescriptor(int fd, @@ -107,7 +107,7 @@ bool MessagePumpFuchsia::WatchFileDescriptor(int fd, controller->watcher_ = delegate; DCHECK(!controller->io_); - controller->io_ = __mxio_fd_to_io(fd); + controller->io_ = __fdio_fd_to_io(fd); if (!controller->io_) { DLOG(ERROR) << "Failed to get IO for FD"; return false; @@ -115,23 +115,23 @@ bool MessagePumpFuchsia::WatchFileDescriptor(int fd, switch (mode) { case WATCH_READ: - controller->desired_events_ = MXIO_EVT_READABLE; + controller->desired_events_ = FDIO_EVT_READABLE; break; case WATCH_WRITE: - controller->desired_events_ = MXIO_EVT_WRITABLE; + controller->desired_events_ = FDIO_EVT_WRITABLE; break; case WATCH_READ_WRITE: - controller->desired_events_ = MXIO_EVT_READABLE | MXIO_EVT_WRITABLE; + controller->desired_events_ = FDIO_EVT_READABLE | FDIO_EVT_WRITABLE; break; default: NOTREACHED() << "unexpected mode: " << mode; return false; } - // Pass dummy |handle| and |signals| values to WatchMxHandle(). The real + // Pass dummy |handle| and |signals| values to WatchZxHandle(). The real // values will be populated by FdWatchController::WaitBegin(), before actually // starting the wait operation. - return WatchMxHandle(MX_HANDLE_INVALID, persistent, 1, controller, + return WatchZxHandle(ZX_HANDLE_INVALID, persistent, 1, controller, controller); } @@ -139,28 +139,28 @@ bool MessagePumpFuchsia::FdWatchController::WaitBegin() { // Refresh the |handle_| and |desired_signals_| from the mxio for the fd. // Some types of mxio map read/write events to different signals depending on // their current state, so we must do this every time we begin to wait. - __mxio_wait_begin(io_, desired_events_, &handle_, &desired_signals_); - if (handle_ == MX_HANDLE_INVALID) { - DLOG(ERROR) << "mxio_wait_begin failed"; + __fdio_wait_begin(io_, desired_events_, &handle_, &desired_signals_); + if (handle_ == ZX_HANDLE_INVALID) { + DLOG(ERROR) << "fdio_wait_begin failed"; return false; } - return MessagePumpFuchsia::MxHandleWatchController::WaitBegin(); + return MessagePumpFuchsia::ZxHandleWatchController::WaitBegin(); } -bool MessagePumpFuchsia::WatchMxHandle(mx_handle_t handle, +bool MessagePumpFuchsia::WatchZxHandle(zx_handle_t handle, bool persistent, - mx_signals_t signals, - MxHandleWatchController* controller, - MxHandleWatcher* delegate) { + zx_signals_t signals, + ZxHandleWatchController* controller, + ZxHandleWatcher* delegate) { DCHECK_NE(0u, signals); DCHECK(controller); DCHECK(delegate); - DCHECK(handle == MX_HANDLE_INVALID || - controller->handle_ == MX_HANDLE_INVALID || + DCHECK(handle == ZX_HANDLE_INVALID || + controller->handle_ == ZX_HANDLE_INVALID || handle == controller->handle_); - if (!controller->StopWatchingMxHandle()) + if (!controller->StopWatchingZxHandle()) NOTREACHED(); controller->handle_ = handle; @@ -173,15 +173,15 @@ bool MessagePumpFuchsia::WatchMxHandle(mx_handle_t handle, return controller->WaitBegin(); } -bool MessagePumpFuchsia::MxHandleWatchController::WaitBegin() { +bool MessagePumpFuchsia::ZxHandleWatchController::WaitBegin() { DCHECK(!has_begun_); - mx_status_t status = - mx_object_wait_async(handle_, weak_pump_->port_.get(), wait_key(), - desired_signals_, MX_WAIT_ASYNC_ONCE); - if (status != MX_OK) { - DLOG(ERROR) << "mx_object_wait_async failed: " - << mx_status_get_string(status) + zx_status_t status = + zx_object_wait_async(handle_, weak_pump_->port_.get(), wait_key(), + desired_signals_, ZX_WAIT_ASYNC_ONCE); + if (status != ZX_OK) { + DLOG(ERROR) << "zx_object_wait_async failed: " + << zx_status_get_string(status) << " (port=" << weak_pump_->port_.get() << ")"; return false; } @@ -191,8 +191,8 @@ bool MessagePumpFuchsia::MxHandleWatchController::WaitBegin() { return true; } -uint32_t MessagePumpFuchsia::MxHandleWatchController::WaitEnd( - mx_signals_t signals) { +uint32_t MessagePumpFuchsia::ZxHandleWatchController::WaitEnd( + zx_signals_t signals) { DCHECK(has_begun_); has_begun_ = false; @@ -206,6 +206,54 @@ uint32_t MessagePumpFuchsia::MxHandleWatchController::WaitEnd( return signals; } +bool MessagePumpFuchsia::HandleEvents(zx_time_t deadline) { + zx_port_packet_t packet; + const zx_status_t wait_status = + zx_port_wait(port_.get(), deadline, &packet, 0); + + if (wait_status == ZX_ERR_TIMED_OUT) + return false; + + if (wait_status != ZX_OK) { + NOTREACHED() << "unexpected wait status: " + << zx_status_get_string(wait_status); + return false; + } + + if (packet.type == ZX_PKT_TYPE_SIGNAL_ONE) { + // A watched fd caused the wakeup via zx_object_wait_async(). + DCHECK_EQ(ZX_OK, packet.status); + ZxHandleWatchController* controller = + reinterpret_cast<ZxHandleWatchController*>( + static_cast<uintptr_t>(packet.key)); + + DCHECK_NE(0u, packet.signal.trigger & packet.signal.observed); + + zx_signals_t signals = controller->WaitEnd(packet.signal.observed); + + // In the case of a persistent Watch, the Watch may be stopped and + // potentially deleted by the caller within the callback, in which case + // |controller| should not be accessed again, and we mustn't continue the + // watch. We check for this with a bool on the stack, which the Watch + // receives a pointer to. + bool controller_was_stopped = false; + controller->was_stopped_ = &controller_was_stopped; + + controller->watcher_->OnZxHandleSignalled(controller->handle_, signals); + + if (!controller_was_stopped) { + controller->was_stopped_ = nullptr; + if (controller->persistent_) + controller->WaitBegin(); + } + } else { + // Wakeup caused by ScheduleWork(). + DCHECK_EQ(ZX_PKT_TYPE_USER, packet.type); + } + + return true; +} + void MessagePumpFuchsia::Run(Delegate* delegate) { AutoReset<bool> auto_reset_keep_running(&keep_running_, true); @@ -218,6 +266,10 @@ void MessagePumpFuchsia::Run(Delegate* delegate) { if (!keep_running_) break; + did_work |= HandleEvents(/*deadline=*/0); + if (!keep_running_) + break; + if (did_work) continue; @@ -228,51 +280,10 @@ void MessagePumpFuchsia::Run(Delegate* delegate) { if (did_work) continue; - mx_time_t deadline = delayed_work_time_.is_null() - ? MX_TIME_INFINITE - : delayed_work_time_.ToMXTime(); - mx_port_packet_t packet; - - const mx_status_t wait_status = - mx_port_wait(port_.get(), deadline, &packet, 0); - if (wait_status != MX_OK) { - if (wait_status != MX_ERR_TIMED_OUT) { - NOTREACHED() << "unexpected wait status: " - << mx_status_get_string(wait_status); - } - continue; - } - - if (packet.type == MX_PKT_TYPE_SIGNAL_ONE) { - // A watched fd caused the wakeup via mx_object_wait_async(). - DCHECK_EQ(MX_OK, packet.status); - MxHandleWatchController* controller = - reinterpret_cast<MxHandleWatchController*>( - static_cast<uintptr_t>(packet.key)); - - DCHECK_NE(0u, packet.signal.trigger & packet.signal.observed); - - mx_signals_t signals = controller->WaitEnd(packet.signal.observed); - - // In the case of a persistent Watch, the Watch may be stopped and - // potentially deleted by the caller within the callback, in which case - // |controller| should not be accessed again, and we mustn't continue the - // watch. We check for this with a bool on the stack, which the Watch - // receives a pointer to. - bool controller_was_stopped = false; - controller->was_stopped_ = &controller_was_stopped; - - controller->watcher_->OnMxHandleSignalled(controller->handle_, signals); - - if (!controller_was_stopped) { - controller->was_stopped_ = nullptr; - if (controller->persistent_) - controller->WaitBegin(); - } - } else { - // Wakeup caused by ScheduleWork(). - DCHECK_EQ(MX_PKT_TYPE_USER, packet.type); - } + zx_time_t deadline = delayed_work_time_.is_null() + ? ZX_TIME_INFINITE + : delayed_work_time_.ToZxTime(); + HandleEvents(deadline); } } @@ -283,11 +294,11 @@ void MessagePumpFuchsia::Quit() { void MessagePumpFuchsia::ScheduleWork() { // Since this can be called on any thread, we need to ensure that our Run loop // wakes up. - mx_port_packet_t packet = {}; - packet.type = MX_PKT_TYPE_USER; - mx_status_t status = mx_port_queue(port_.get(), &packet, 0); - DLOG_IF(ERROR, status != MX_OK) - << "mx_port_queue failed: " << status << " (port=" << port_.get() << ")"; + zx_port_packet_t packet = {}; + packet.type = ZX_PKT_TYPE_USER; + zx_status_t status = zx_port_queue(port_.get(), &packet, 0); + DLOG_IF(ERROR, status != ZX_OK) + << "zx_port_queue failed: " << status << " (port=" << port_.get() << ")"; } void MessagePumpFuchsia::ScheduleDelayedWork( diff --git a/chromium/base/message_loop/message_pump_fuchsia.h b/chromium/base/message_loop/message_pump_fuchsia.h index 7a221e15607..80d41538110 100644 --- a/chromium/base/message_loop/message_pump_fuchsia.h +++ b/chromium/base/message_loop/message_pump_fuchsia.h @@ -6,28 +6,28 @@ #define BASE_MESSAGE_LOOP_MESSAGE_PUMP_FUCHSIA_H_ #include "base/base_export.h" -#include "base/fuchsia/scoped_mx_handle.h" +#include "base/fuchsia/scoped_zx_handle.h" #include "base/location.h" #include "base/macros.h" #include "base/memory/weak_ptr.h" #include "base/message_loop/message_pump.h" -#include <magenta/syscalls/port.h> -#include <mxio/io.h> -#include <mxio/private.h> +#include <fdio/io.h> +#include <fdio/private.h> +#include <zircon/syscalls/port.h> namespace base { class BASE_EXPORT MessagePumpFuchsia : public MessagePump { public: // Implemented by callers to receive notifications of handle & fd events. - class MxHandleWatcher { + class ZxHandleWatcher { public: - virtual void OnMxHandleSignalled(mx_handle_t handle, - mx_signals_t signals) = 0; + virtual void OnZxHandleSignalled(zx_handle_t handle, + zx_signals_t signals) = 0; protected: - virtual ~MxHandleWatcher() {} + virtual ~ZxHandleWatcher() {} }; class FdWatcher { @@ -38,24 +38,21 @@ class BASE_EXPORT MessagePumpFuchsia : public MessagePump { virtual ~FdWatcher() {} }; - // Manages an active watch on an mx_handle_t. - class MxHandleWatchController { + // Manages an active watch on an zx_handle_t. + class ZxHandleWatchController { public: - explicit MxHandleWatchController( - const tracked_objects::Location& from_here); - // Deleting the Controller implicitly calls StopWatchingMxHandle. - virtual ~MxHandleWatchController(); + explicit ZxHandleWatchController(const Location& from_here); + // Deleting the Controller implicitly calls StopWatchingZxHandle. + virtual ~ZxHandleWatchController(); // Stop watching the handle, always safe to call. No-op if there's nothing // to do. - bool StopWatchingMxHandle(); + bool StopWatchingZxHandle(); - const tracked_objects::Location& created_from_location() { - return created_from_location_; - } + const Location& created_from_location() { return created_from_location_; } protected: - // This bool is used by the pump when invoking the MxHandleWatcher callback, + // This bool is used by the pump when invoking the ZxHandleWatcher callback, // and by the FdHandleWatchController when invoking read & write callbacks, // to cope with the possibility of the caller deleting the *Watcher within // the callback. The pump sets |was_stopped_| to a location on the stack, @@ -72,19 +69,19 @@ class BASE_EXPORT MessagePumpFuchsia : public MessagePump { // Called by MessagePumpFuchsia when the handle is signalled. Accepts the // set of signals that fired, and returns the intersection with those the // caller is interested in. - mx_signals_t WaitEnd(mx_signals_t observed); + zx_signals_t WaitEnd(zx_signals_t observed); // Returns the key to use to uniquely identify this object's wait operation. uint64_t wait_key() const { return static_cast<uint64_t>(reinterpret_cast<uintptr_t>(this)); } - const tracked_objects::Location created_from_location_; + const Location created_from_location_; // Set directly from the inputs to WatchFileDescriptor. - MxHandleWatcher* watcher_ = nullptr; - mx_handle_t handle_ = MX_HANDLE_INVALID; - mx_signals_t desired_signals_ = 0; + ZxHandleWatcher* watcher_ = nullptr; + zx_handle_t handle_ = ZX_HANDLE_INVALID; + zx_signals_t desired_signals_ = 0; // Used to safely access resources owned by the associated message pump. WeakPtr<MessagePumpFuchsia> weak_pump_; @@ -97,14 +94,14 @@ class BASE_EXPORT MessagePumpFuchsia : public MessagePump { // this controller. bool has_begun_ = false; - DISALLOW_COPY_AND_ASSIGN(MxHandleWatchController); + DISALLOW_COPY_AND_ASSIGN(ZxHandleWatchController); }; // Object returned by WatchFileDescriptor to manage further watching. - class FdWatchController : public MxHandleWatchController, - public MxHandleWatcher { + class FdWatchController : public ZxHandleWatchController, + public ZxHandleWatcher { public: - explicit FdWatchController(const tracked_objects::Location& from_here); + explicit FdWatchController(const Location& from_here); ~FdWatchController() override; bool StopWatchingFileDescriptor(); @@ -115,8 +112,8 @@ class BASE_EXPORT MessagePumpFuchsia : public MessagePump { // Determines the desires signals, and begins waiting on the handle. bool WaitBegin() override; - // MxHandleWatcher interface. - void OnMxHandleSignalled(mx_handle_t handle, mx_signals_t signals) override; + // ZxHandleWatcher interface. + void OnZxHandleSignalled(zx_handle_t handle, zx_signals_t signals) override; // Set directly from the inputs to WatchFileDescriptor. FdWatcher* watcher_ = nullptr; @@ -124,7 +121,7 @@ class BASE_EXPORT MessagePumpFuchsia : public MessagePump { uint32_t desired_events_ = 0; // Set by WatchFileDescriptor to hold a reference to the descriptor's mxio. - mxio_t* io_ = nullptr; + fdio_t* io_ = nullptr; DISALLOW_COPY_AND_ASSIGN(FdWatchController); }; @@ -137,11 +134,11 @@ class BASE_EXPORT MessagePumpFuchsia : public MessagePump { MessagePumpFuchsia(); - bool WatchMxHandle(mx_handle_t handle, + bool WatchZxHandle(zx_handle_t handle, bool persistent, - mx_signals_t signals, - MxHandleWatchController* controller, - MxHandleWatcher* delegate); + zx_signals_t signals, + ZxHandleWatchController* controller, + ZxHandleWatcher* delegate); bool WatchFileDescriptor(int fd, bool persistent, int mode, @@ -155,10 +152,14 @@ class BASE_EXPORT MessagePumpFuchsia : public MessagePump { void ScheduleDelayedWork(const TimeTicks& delayed_work_time) override; private: + // Handles IO events from the |port_|. Returns true if any events were + // received. + bool HandleEvents(zx_time_t deadline); + // This flag is set to false when Run should return. bool keep_running_ = true; - ScopedMxHandle port_; + ScopedZxHandle port_; // The time at which we should call DoDelayedWork. TimeTicks delayed_work_time_; diff --git a/chromium/base/message_loop/message_pump_glib_unittest.cc b/chromium/base/message_loop/message_pump_glib_unittest.cc index 1bfa70e9be3..3ac58c85bc3 100644 --- a/chromium/base/message_loop/message_pump_glib_unittest.cc +++ b/chromium/base/message_loop/message_pump_glib_unittest.cc @@ -152,8 +152,7 @@ void ExpectProcessedEvents(EventInjector* injector, int count) { } // Posts a task on the current message loop. -void PostMessageLoopTask(const tracked_objects::Location& from_here, - OnceClosure task) { +void PostMessageLoopTask(const Location& from_here, OnceClosure task) { ThreadTaskRunnerHandle::Get()->PostTask(from_here, std::move(task)); } diff --git a/chromium/base/message_loop/message_pump_io_ios.cc b/chromium/base/message_loop/message_pump_io_ios.cc index 85cb2555c95..74a3f158303 100644 --- a/chromium/base/message_loop/message_pump_io_ios.cc +++ b/chromium/base/message_loop/message_pump_io_ios.cc @@ -7,7 +7,7 @@ namespace base { MessagePumpIOSForIO::FileDescriptorWatcher::FileDescriptorWatcher( - const tracked_objects::Location& from_here) + const Location& from_here) : is_persistent_(false), fdref_(NULL), callback_types_(0), diff --git a/chromium/base/message_loop/message_pump_io_ios.h b/chromium/base/message_loop/message_pump_io_ios.h index f48e9573655..e842a6c9ecf 100644 --- a/chromium/base/message_loop/message_pump_io_ios.h +++ b/chromium/base/message_loop/message_pump_io_ios.h @@ -37,7 +37,7 @@ class BASE_EXPORT MessagePumpIOSForIO : public MessagePumpNSRunLoop { // Object returned by WatchFileDescriptor to manage further watching. class FileDescriptorWatcher { public: - explicit FileDescriptorWatcher(const tracked_objects::Location& from_here); + explicit FileDescriptorWatcher(const Location& from_here); ~FileDescriptorWatcher(); // Implicitly calls StopWatchingFileDescriptor. // NOTE: These methods aren't called StartWatching()/StopWatching() to @@ -47,9 +47,7 @@ class BASE_EXPORT MessagePumpIOSForIO : public MessagePumpNSRunLoop { // to do. bool StopWatchingFileDescriptor(); - const tracked_objects::Location& created_from_location() { - return created_from_location_; - } + const Location& created_from_location() { return created_from_location_; } private: friend class MessagePumpIOSForIO; @@ -77,7 +75,7 @@ class BASE_EXPORT MessagePumpIOSForIO : public MessagePumpNSRunLoop { base::WeakPtr<MessagePumpIOSForIO> pump_; Watcher* watcher_; - tracked_objects::Location created_from_location_; + Location created_from_location_; DISALLOW_COPY_AND_ASSIGN(FileDescriptorWatcher); }; diff --git a/chromium/base/message_loop/message_pump_libevent.cc b/chromium/base/message_loop/message_pump_libevent.cc index 14bc75c92b9..63d85c86221 100644 --- a/chromium/base/message_loop/message_pump_libevent.cc +++ b/chromium/base/message_loop/message_pump_libevent.cc @@ -44,7 +44,7 @@ namespace base { MessagePumpLibevent::FileDescriptorWatcher::FileDescriptorWatcher( - const tracked_objects::Location& from_here) + const Location& from_here) : event_(NULL), pump_(NULL), watcher_(NULL), diff --git a/chromium/base/message_loop/message_pump_libevent.h b/chromium/base/message_loop/message_pump_libevent.h index 1124560d66c..e14b58460b5 100644 --- a/chromium/base/message_loop/message_pump_libevent.h +++ b/chromium/base/message_loop/message_pump_libevent.h @@ -38,7 +38,7 @@ class BASE_EXPORT MessagePumpLibevent : public MessagePump { // Object returned by WatchFileDescriptor to manage further watching. class FileDescriptorWatcher { public: - explicit FileDescriptorWatcher(const tracked_objects::Location& from_here); + explicit FileDescriptorWatcher(const Location& from_here); ~FileDescriptorWatcher(); // Implicitly calls StopWatchingFileDescriptor. // NOTE: These methods aren't called StartWatching()/StopWatching() to @@ -48,9 +48,7 @@ class BASE_EXPORT MessagePumpLibevent : public MessagePump { // to do. bool StopWatchingFileDescriptor(); - const tracked_objects::Location& created_from_location() { - return created_from_location_; - } + const Location& created_from_location() { return created_from_location_; } private: friend class MessagePumpLibevent; @@ -78,7 +76,7 @@ class BASE_EXPORT MessagePumpLibevent : public MessagePump { // destructor. bool* was_destroyed_; - const tracked_objects::Location created_from_location_; + const Location created_from_location_; DISALLOW_COPY_AND_ASSIGN(FileDescriptorWatcher); }; diff --git a/chromium/base/message_loop/message_pump_mac.h b/chromium/base/message_loop/message_pump_mac.h index 763c6b4868d..9a5243441ee 100644 --- a/chromium/base/message_loop/message_pump_mac.h +++ b/chromium/base/message_loop/message_pump_mac.h @@ -91,9 +91,10 @@ class BASE_EXPORT MessagePumpCFRunLoopBase : public MessagePump { friend class MessagePumpScopedAutoreleasePool; friend class TestMessagePumpCFRunLoopBase; - // Tasks will be pumped in the run loop modes described by |mode_mask|, which - // maps bits to the index of an internal array of run loop mode identifiers. - explicit MessagePumpCFRunLoopBase(int mode_mask); + // Tasks will be pumped in the run loop modes described by + // |initial_mode_mask|, which maps bits to the index of an internal array of + // run loop mode identifiers. + explicit MessagePumpCFRunLoopBase(int initial_mode_mask); ~MessagePumpCFRunLoopBase() override; // Subclasses should implement the work they need to do in MessagePump::Run @@ -117,12 +118,18 @@ class BASE_EXPORT MessagePumpCFRunLoopBase : public MessagePump { // objects autoreleased by work to fall into the current autorelease pool. virtual AutoreleasePoolType* CreateAutoreleasePool(); - // Invokes function(run_loop_, arg, mode) for all the modes in |mode_mask_|. - template <typename Argument> - void InvokeForEnabledModes(void function(CFRunLoopRef, Argument, CFStringRef), - Argument argument); + // Enable and disable entries in |enabled_modes_| to match |mode_mask|. + void SetModeMask(int mode_mask); + + // Get the current mode mask from |enabled_modes_|. + int GetModeMask() const; private: + class ScopedModeEnabler; + + // The maximum number of run loop modes that can be monitored. + static constexpr int kNumModes = 4; + // Marking timers as invalid at the right time helps significantly reduce // power use (see the comment in RunDelayedWorkTimer()), however there is no // public API for doing so. CFRuntime.h states that CFRuntimeBase, upon which @@ -198,8 +205,8 @@ class BASE_EXPORT MessagePumpCFRunLoopBase : public MessagePump { // The thread's run loop. CFRunLoopRef run_loop_; - // Bitmask controlling the run loop modes in which posted tasks may run. - const int mode_mask_; + // The enabled modes. Posted tasks may run in any non-null entry. + std::unique_ptr<ScopedModeEnabler> enabled_modes_[kNumModes]; // The timer, sources, and observers are described above alongside their // callbacks. @@ -308,6 +315,19 @@ class MessagePumpUIApplication : public MessagePumpCFRunLoopBase { #else +// While in scope, permits posted tasks to be run in private AppKit run loop +// modes that would otherwise make the UI unresponsive. E.g., menu fade out. +class BASE_EXPORT ScopedPumpMessagesInPrivateModes { + public: + ScopedPumpMessagesInPrivateModes(); + ~ScopedPumpMessagesInPrivateModes(); + + int GetModeMaskForTest(); + + private: + DISALLOW_COPY_AND_ASSIGN(ScopedPumpMessagesInPrivateModes); +}; + class MessagePumpNSApplication : public MessagePumpCFRunLoopBase { public: MessagePumpNSApplication(); @@ -317,6 +337,8 @@ class MessagePumpNSApplication : public MessagePumpCFRunLoopBase { void Quit() override; private: + friend class ScopedPumpMessagesInPrivateModes; + // False after Quit is called. bool keep_running_; diff --git a/chromium/base/message_loop/message_pump_mac.mm b/chromium/base/message_loop/message_pump_mac.mm index 71cb00b80a1..0451e33ab21 100644 --- a/chromium/base/message_loop/message_pump_mac.mm +++ b/chromium/base/message_loop/message_pump_mac.mm @@ -12,6 +12,7 @@ #include "base/mac/call_with_eh_frame.h" #include "base/mac/scoped_cftyperef.h" #include "base/macros.h" +#include "base/memory/ptr_util.h" #include "base/message_loop/timer_slack.h" #include "base/run_loop.h" #include "base/time/time.h" @@ -45,17 +46,17 @@ const CFStringRef kAllModes[] = { }; // Mask that determines which modes in |kAllModes| to use. -enum { kCommonModeMask = 0x1, kAllModesMask = ~0 }; +enum { kCommonModeMask = 0x1, kAllModesMask = 0xf }; -// Modes to use for MessagePumpNSApplication. Currently just common and -// exclusive modes. TODO(tapted): Use kAllModesMask once http://crbug.com/640466 -// blockers are fixed. -enum { kNSApplicationModeMask = 0x3 }; +// Modes to use for MessagePumpNSApplication that are considered "safe". +// Currently just common and exclusive modes. Ideally, messages would be pumped +// in all modes, but that interacts badly with app modal dialogs (e.g. NSAlert). +enum { kNSApplicationModalSafeModeMask = 0x3 }; void NoOp(void* info) { } -const CFTimeInterval kCFTimeIntervalMax = +constexpr CFTimeInterval kCFTimeIntervalMax = std::numeric_limits<CFTimeInterval>::max(); #if !defined(OS_IOS) @@ -63,6 +64,9 @@ const CFTimeInterval kCFTimeIntervalMax = // initialized. Only accessed from the main thread. bool g_not_using_cr_app = false; +// The MessagePump controlling [NSApp run]. +MessagePumpNSApplication* g_app_pump; + // Various CoreFoundation definitions. typedef struct __CFRuntimeBase { uintptr_t _cfisa; @@ -128,6 +132,40 @@ class MessagePumpScopedAutoreleasePool { DISALLOW_COPY_AND_ASSIGN(MessagePumpScopedAutoreleasePool); }; +class MessagePumpCFRunLoopBase::ScopedModeEnabler { + public: + ScopedModeEnabler(MessagePumpCFRunLoopBase* owner, int mode_index) + : owner_(owner), mode_index_(mode_index) { + CFRunLoopRef loop = owner_->run_loop_; + CFRunLoopAddTimer(loop, owner_->delayed_work_timer_, mode()); + CFRunLoopAddSource(loop, owner_->work_source_, mode()); + CFRunLoopAddSource(loop, owner_->idle_work_source_, mode()); + CFRunLoopAddSource(loop, owner_->nesting_deferred_work_source_, mode()); + CFRunLoopAddObserver(loop, owner_->pre_wait_observer_, mode()); + CFRunLoopAddObserver(loop, owner_->pre_source_observer_, mode()); + CFRunLoopAddObserver(loop, owner_->enter_exit_observer_, mode()); + } + + ~ScopedModeEnabler() { + CFRunLoopRef loop = owner_->run_loop_; + CFRunLoopRemoveObserver(loop, owner_->enter_exit_observer_, mode()); + CFRunLoopRemoveObserver(loop, owner_->pre_source_observer_, mode()); + CFRunLoopRemoveObserver(loop, owner_->pre_wait_observer_, mode()); + CFRunLoopRemoveSource(loop, owner_->nesting_deferred_work_source_, mode()); + CFRunLoopRemoveSource(loop, owner_->idle_work_source_, mode()); + CFRunLoopRemoveSource(loop, owner_->work_source_, mode()); + CFRunLoopRemoveTimer(loop, owner_->delayed_work_timer_, mode()); + } + + const CFStringRef& mode() const { return kAllModes[mode_index_]; } + + private: + MessagePumpCFRunLoopBase* const owner_; // Weak. Owns this. + const int mode_index_; + + DISALLOW_COPY_AND_ASSIGN(ScopedModeEnabler); +}; + // Must be called on the run loop thread. void MessagePumpCFRunLoopBase::Run(Delegate* delegate) { // nesting_level_ will be incremented in EnterExitRunLoop, so set @@ -180,9 +218,8 @@ void MessagePumpCFRunLoopBase::SetTimerSlack(TimerSlack timer_slack) { } // Must be called on the run loop thread. -MessagePumpCFRunLoopBase::MessagePumpCFRunLoopBase(int mode_mask) - : mode_mask_(mode_mask), - delegate_(NULL), +MessagePumpCFRunLoopBase::MessagePumpCFRunLoopBase(int initial_mode_mask) + : delegate_(NULL), delayed_work_fire_time_(kCFTimeIntervalMax), timer_slack_(base::TIMER_SLACK_NONE), nesting_level_(0), @@ -205,7 +242,6 @@ MessagePumpCFRunLoopBase::MessagePumpCFRunLoopBase(int mode_mask) 0, // priority RunDelayedWorkTimer, &timer_context); - InvokeForEnabledModes(&CFRunLoopAddTimer, delayed_work_timer_); CFRunLoopSourceContext source_context = CFRunLoopSourceContext(); source_context.info = this; @@ -213,19 +249,14 @@ MessagePumpCFRunLoopBase::MessagePumpCFRunLoopBase(int mode_mask) work_source_ = CFRunLoopSourceCreate(NULL, // allocator 1, // priority &source_context); - InvokeForEnabledModes(&CFRunLoopAddSource, work_source_); - source_context.perform = RunIdleWorkSource; idle_work_source_ = CFRunLoopSourceCreate(NULL, // allocator 2, // priority &source_context); - InvokeForEnabledModes(&CFRunLoopAddSource, idle_work_source_); - source_context.perform = RunNestingDeferredWorkSource; nesting_deferred_work_source_ = CFRunLoopSourceCreate(NULL, // allocator 0, // priority &source_context); - InvokeForEnabledModes(&CFRunLoopAddSource, nesting_deferred_work_source_); CFRunLoopObserverContext observer_context = CFRunLoopObserverContext(); observer_context.info = this; @@ -235,16 +266,12 @@ MessagePumpCFRunLoopBase::MessagePumpCFRunLoopBase(int mode_mask) 0, // priority PreWaitObserver, &observer_context); - InvokeForEnabledModes(&CFRunLoopAddObserver, pre_wait_observer_); - pre_source_observer_ = CFRunLoopObserverCreate(NULL, // allocator kCFRunLoopBeforeSources, true, // repeat 0, // priority PreSourceObserver, &observer_context); - InvokeForEnabledModes(&CFRunLoopAddObserver, pre_source_observer_); - enter_exit_observer_ = CFRunLoopObserverCreate(NULL, // allocator kCFRunLoopEntry | kCFRunLoopExit, @@ -252,24 +279,20 @@ MessagePumpCFRunLoopBase::MessagePumpCFRunLoopBase(int mode_mask) 0, // priority EnterExitObserver, &observer_context); - InvokeForEnabledModes(&CFRunLoopAddObserver, enter_exit_observer_); + SetModeMask(initial_mode_mask); } // Ideally called on the run loop thread. If other run loops were running // lower on the run loop thread's stack when this object was created, the // same number of run loops must be running when this object is destroyed. MessagePumpCFRunLoopBase::~MessagePumpCFRunLoopBase() { - for (const CFRunLoopObserverRef& observer : - {enter_exit_observer_, pre_source_observer_, pre_wait_observer_}) { - InvokeForEnabledModes(&CFRunLoopRemoveObserver, observer); - CFRelease(observer); - } - for (const CFRunLoopSourceRef& source : - {nesting_deferred_work_source_, idle_work_source_, work_source_}) { - InvokeForEnabledModes(&CFRunLoopRemoveSource, source); - CFRelease(source); - } - InvokeForEnabledModes(&CFRunLoopRemoveTimer, delayed_work_timer_); + SetModeMask(0); + CFRelease(enter_exit_observer_); + CFRelease(pre_source_observer_); + CFRelease(pre_wait_observer_); + CFRelease(nesting_deferred_work_source_); + CFRelease(idle_work_source_); + CFRelease(work_source_); CFRelease(delayed_work_timer_); CFRelease(run_loop_); } @@ -297,17 +320,25 @@ AutoreleasePoolType* MessagePumpCFRunLoopBase::CreateAutoreleasePool() { return [[NSAutoreleasePool alloc] init]; } -template <typename Argument> -void MessagePumpCFRunLoopBase::InvokeForEnabledModes(void method(CFRunLoopRef, - Argument, - CFStringRef), - Argument argument) { +void MessagePumpCFRunLoopBase::SetModeMask(int mode_mask) { + static_assert(arraysize(enabled_modes_) == arraysize(kAllModes), + "mode size mismatch"); for (size_t i = 0; i < arraysize(kAllModes); ++i) { - if (mode_mask_ & (0x1 << i)) - method(run_loop_, argument, kAllModes[i]); + bool enable = mode_mask & (0x1 << i); + if (enable == !enabled_modes_[i]) { + enabled_modes_[i] = + enable ? base::MakeUnique<ScopedModeEnabler>(this, i) : nullptr; + } } } +int MessagePumpCFRunLoopBase::GetModeMask() const { + int mask = 0; + for (size_t i = 0; i < arraysize(enabled_modes_); ++i) + mask |= enabled_modes_[i] ? (0x1 << i) : 0; + return mask; +} + #if !defined(OS_IOS) // This function uses private API to modify a test timer's valid state and // uses public API to confirm that the private API changed the correct bit. @@ -679,11 +710,11 @@ MessagePumpNSRunLoop::MessagePumpNSRunLoop() quit_source_ = CFRunLoopSourceCreate(NULL, // allocator 0, // priority &source_context); - InvokeForEnabledModes(&CFRunLoopAddSource, quit_source_); + CFRunLoopAddSource(run_loop(), quit_source_, kCFRunLoopCommonModes); } MessagePumpNSRunLoop::~MessagePumpNSRunLoop() { - InvokeForEnabledModes(&CFRunLoopRemoveSource, quit_source_); + CFRunLoopRemoveSource(run_loop(), quit_source_, kCFRunLoopCommonModes); CFRelease(quit_source_); } @@ -726,12 +757,36 @@ void MessagePumpUIApplication::Attach(Delegate* delegate) { #else +ScopedPumpMessagesInPrivateModes::ScopedPumpMessagesInPrivateModes() { + DCHECK(g_app_pump); + DCHECK_EQ(kNSApplicationModalSafeModeMask, g_app_pump->GetModeMask()); + // Pumping events in private runloop modes is known to interact badly with + // app modal windows like NSAlert. + if (![NSApp modalWindow]) + g_app_pump->SetModeMask(kAllModesMask); +} + +ScopedPumpMessagesInPrivateModes::~ScopedPumpMessagesInPrivateModes() { + DCHECK(g_app_pump); + g_app_pump->SetModeMask(kNSApplicationModalSafeModeMask); +} + +int ScopedPumpMessagesInPrivateModes::GetModeMaskForTest() { + return g_app_pump ? g_app_pump->GetModeMask() : -1; +} + MessagePumpNSApplication::MessagePumpNSApplication() - : MessagePumpCFRunLoopBase(kNSApplicationModeMask), + : MessagePumpCFRunLoopBase(kNSApplicationModalSafeModeMask), keep_running_(true), - running_own_loop_(false) {} + running_own_loop_(false) { + DCHECK_EQ(nullptr, g_app_pump); + g_app_pump = this; +} -MessagePumpNSApplication::~MessagePumpNSApplication() {} +MessagePumpNSApplication::~MessagePumpNSApplication() { + DCHECK_EQ(this, g_app_pump); + g_app_pump = nullptr; +} void MessagePumpNSApplication::DoRun(Delegate* delegate) { bool last_running_own_loop_ = running_own_loop_; diff --git a/chromium/base/message_loop/message_pump_mac_unittest.cc b/chromium/base/message_loop/message_pump_mac_unittest.mm index 17c70c8a6cb..480b311fa38 100644 --- a/chromium/base/message_loop/message_pump_mac_unittest.cc +++ b/chromium/base/message_loop/message_pump_mac_unittest.mm @@ -5,9 +5,24 @@ #include "base/message_loop/message_pump_mac.h" #include "base/mac/scoped_cftyperef.h" +#import "base/mac/scoped_nsobject.h" #include "base/macros.h" +#include "base/message_loop/message_loop.h" +#include "base/threading/thread_task_runner_handle.h" #include "testing/gtest/include/gtest/gtest.h" +@interface TestModalAlertCloser : NSObject +- (void)runTestThenCloseAlert:(NSAlert*)alert; +@end + +namespace { + +// Internal constants from message_pump_mac.mm. +constexpr int kAllModesMask = 0xf; +constexpr int kNSApplicationModalSafeModeMask = 0x3; + +} // namespace + namespace base { class TestMessagePumpCFRunLoopBase { @@ -105,4 +120,105 @@ TEST(MessagePumpMacTest, TestInvalidatedTimerReuse) { CFRunLoopRemoveTimer(CFRunLoopGetCurrent(), test_timer, kMessageLoopExclusiveRunLoopMode); } + +namespace { + +// PostedTasks are only executed while the message pump has a delegate. That is, +// when a base::RunLoop is running, so in order to test whether posted tasks +// are run by CFRunLoopRunInMode and *not* by the regular RunLoop, we need to +// be inside a task that is also calling CFRunLoopRunInMode. This task runs the +// given |mode| after posting a task to increment a counter, then checks whether +// the counter incremented after emptying that run loop mode. +void IncrementInModeAndExpect(CFRunLoopMode mode, int result) { + // Since this task is "ours" rather than a system task, allow nesting. + MessageLoop::ScopedNestableTaskAllower allow(MessageLoop::current()); + int counter = 0; + auto increment = BindRepeating([](int* i) { ++*i; }, &counter); + ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, increment); + while (CFRunLoopRunInMode(mode, 0, true) == kCFRunLoopRunHandledSource) + ; + ASSERT_EQ(result, counter); +} + +} // namespace + +// Tests the correct behavior of ScopedPumpMessagesInPrivateModes. +TEST(MessagePumpMacTest, ScopedPumpMessagesInPrivateModes) { + MessageLoopForUI message_loop; + + CFRunLoopMode kRegular = kCFRunLoopDefaultMode; + CFRunLoopMode kPrivate = CFSTR("NSUnhighlightMenuRunLoopMode"); + + // Work is seen when running in the default mode. + ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, BindOnce(&IncrementInModeAndExpect, kRegular, 1)); + EXPECT_NO_FATAL_FAILURE(RunLoop().RunUntilIdle()); + + // But not seen when running in a private mode. + ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, BindOnce(&IncrementInModeAndExpect, kPrivate, 0)); + EXPECT_NO_FATAL_FAILURE(RunLoop().RunUntilIdle()); + + { + ScopedPumpMessagesInPrivateModes allow_private; + // Now the work should be seen. + ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, BindOnce(&IncrementInModeAndExpect, kPrivate, 1)); + EXPECT_NO_FATAL_FAILURE(RunLoop().RunUntilIdle()); + + // The regular mode should also work the same. + ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, BindOnce(&IncrementInModeAndExpect, kRegular, 1)); + EXPECT_NO_FATAL_FAILURE(RunLoop().RunUntilIdle()); + } + + // And now the scoper is out of scope, private modes should no longer see it. + ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, BindOnce(&IncrementInModeAndExpect, kPrivate, 0)); + EXPECT_NO_FATAL_FAILURE(RunLoop().RunUntilIdle()); + + // Only regular modes see it. + ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, BindOnce(&IncrementInModeAndExpect, kRegular, 1)); + EXPECT_NO_FATAL_FAILURE(RunLoop().RunUntilIdle()); +} + +// Tests that private message loop modes are not pumped while a modal dialog is +// present. +TEST(MessagePumpMacTest, ScopedPumpMessagesAttemptWithModalDialog) { + MessageLoopForUI message_loop; + + { + base::ScopedPumpMessagesInPrivateModes allow_private; + // No modal window, so all modes should be pumped. + EXPECT_EQ(kAllModesMask, allow_private.GetModeMaskForTest()); + } + + base::scoped_nsobject<NSAlert> alert([[NSAlert alloc] init]); + [alert addButtonWithTitle:@"OK"]; + base::scoped_nsobject<TestModalAlertCloser> closer( + [[TestModalAlertCloser alloc] init]); + [closer performSelector:@selector(runTestThenCloseAlert:) + withObject:alert + afterDelay:0 + inModes:@[ NSModalPanelRunLoopMode ]]; + NSInteger result = [alert runModal]; + EXPECT_EQ(NSAlertFirstButtonReturn, result); +} + } // namespace base + +@implementation TestModalAlertCloser + +- (void)runTestThenCloseAlert:(NSAlert*)alert { + EXPECT_TRUE([NSApp modalWindow]); + { + base::ScopedPumpMessagesInPrivateModes allow_private; + // With a modal window, only safe modes should be pumped. + EXPECT_EQ(kNSApplicationModalSafeModeMask, + allow_private.GetModeMaskForTest()); + } + [[alert buttons][0] performClick:nil]; +} + +@end diff --git a/chromium/base/message_loop/message_pump_perftest.cc b/chromium/base/message_loop/message_pump_perftest.cc index 6fafa41bfd5..4b45341d58d 100644 --- a/chromium/base/message_loop/message_pump_perftest.cc +++ b/chromium/base/message_loop/message_pump_perftest.cc @@ -263,7 +263,7 @@ class PostTaskTest : public testing::Test { for (int i = 0; i < batch_size; ++i) { for (int j = 0; j < tasks_per_reload; ++j) { queue->AddToIncomingQueue(FROM_HERE, base::BindOnce(&DoNothing), - base::TimeDelta(), false); + base::TimeDelta(), Nestable::kNonNestable); num_posted++; } TaskQueue loop_local_queue; |