Index: third_party/WebKit/Source/platform/scheduler/base/task_queue_manager.cc |
diff --git a/third_party/WebKit/Source/platform/scheduler/base/task_queue_manager.cc b/third_party/WebKit/Source/platform/scheduler/base/task_queue_manager.cc |
index 30c022e6ee0f5a92676c7376c4e4ee646ff4406f..74a311b176169842a5b18c0f8dc41f83d0819383 100644 |
--- a/third_party/WebKit/Source/platform/scheduler/base/task_queue_manager.cc |
+++ b/third_party/WebKit/Source/platform/scheduler/base/task_queue_manager.cc |
@@ -58,7 +58,7 @@ |
: real_time_domain_(new RealTimeDomain(tracing_category)), |
delegate_(delegate), |
task_was_run_on_quiescence_monitored_queue_(false), |
- record_task_delay_histograms_(true), |
+ other_thread_pending_wakeup_(false), |
work_batch_size_(1), |
task_count_(0), |
tracing_category_(tracing_category), |
@@ -75,10 +75,12 @@ |
"TaskQueueManager", this); |
selector_.SetTaskQueueSelectorObserver(this); |
- delayed_do_work_closure_ = |
- base::Bind(&TaskQueueManager::DoWork, weak_factory_.GetWeakPtr(), true); |
- immediate_do_work_closure_ = |
- base::Bind(&TaskQueueManager::DoWork, weak_factory_.GetWeakPtr(), false); |
+ from_main_thread_immediate_do_work_closure_ = |
+ base::Bind(&TaskQueueManager::DoWork, weak_factory_.GetWeakPtr(), |
+ base::TimeTicks(), true); |
+ from_other_thread_immediate_do_work_closure_ = |
+ base::Bind(&TaskQueueManager::DoWork, weak_factory_.GetWeakPtr(), |
+ base::TimeTicks(), false); |
// TODO(alexclarke): Change this to be a parameter that's passed in. |
RegisterTimeDomain(real_time_domain_.get()); |
@@ -97,11 +99,6 @@ |
delegate_->RemoveNestingObserver(this); |
} |
- |
-TaskQueueManager::AnyThread::AnyThread() |
- : do_work_running_count(0), |
- immediate_do_work_posted_count(0), |
- is_nested(false) {} |
void TaskQueueManager::RegisterTimeDomain(TimeDomain* time_domain) { |
time_domains_.insert(time_domain); |
@@ -148,72 +145,45 @@ |
queues_.erase(task_queue); |
selector_.RemoveQueue(task_queue.get()); |
- |
- { |
- base::AutoLock lock(any_thread_lock_); |
- any_thread().has_incoming_immediate_work.erase(task_queue.get()); |
- } |
-} |
- |
-void TaskQueueManager::UpdateWorkQueues( |
- const std::set<internal::TaskQueueImpl*>* queues_to_reload, |
- LazyNow* lazy_now) { |
- if (queues_to_reload) { |
- for (internal::TaskQueueImpl* queue : *queues_to_reload) { |
- queue->ReloadImmediateWorkQueueIfEmpty(); |
- } |
- } |
+} |
+ |
+void TaskQueueManager::UpdateWorkQueues(LazyNow lazy_now) { |
+ TRACE_EVENT0(disabled_by_default_tracing_category_, |
+ "TaskQueueManager::UpdateWorkQueues"); |
for (TimeDomain* time_domain : time_domains_) { |
- if (time_domain == real_time_domain_.get()) { |
- time_domain->WakeupReadyDelayedQueues(lazy_now); |
- continue; |
- } |
- LazyNow time_domain_lazy_now = time_domain->CreateLazyNow(); |
- time_domain->WakeupReadyDelayedQueues(&time_domain_lazy_now); |
+ LazyNow lazy_now_in_domain = time_domain == real_time_domain_.get() |
+ ? lazy_now |
+ : time_domain->CreateLazyNow(); |
+ time_domain->UpdateWorkQueues(lazy_now_in_domain); |
} |
} |
void TaskQueueManager::OnBeginNestedMessageLoop() { |
// We just entered a nested message loop, make sure there's a DoWork posted or |
// the system will grind to a halt. |
- { |
- base::AutoLock lock(any_thread_lock_); |
- any_thread().immediate_do_work_posted_count++; |
- any_thread().is_nested = true; |
- } |
- delegate_->PostTask(FROM_HERE, immediate_do_work_closure_); |
-} |
- |
-void TaskQueueManager::OnQueueHasImmediateWork(internal::TaskQueueImpl* queue, |
- bool ensure_do_work_posted) { |
- MoveableAutoLock lock(any_thread_lock_); |
- any_thread().has_incoming_immediate_work.insert(queue); |
- if (ensure_do_work_posted) |
- MaybeScheduleImmediateWorkLocked(FROM_HERE, std::move(lock)); |
+ delegate_->PostTask(FROM_HERE, from_main_thread_immediate_do_work_closure_); |
} |
void TaskQueueManager::MaybeScheduleImmediateWork( |
const tracked_objects::Location& from_here) { |
- MoveableAutoLock lock(any_thread_lock_); |
- MaybeScheduleImmediateWorkLocked(from_here, std::move(lock)); |
-} |
- |
-void TaskQueueManager::MaybeScheduleImmediateWorkLocked( |
- const tracked_objects::Location& from_here, |
- MoveableAutoLock&& lock) { |
- { |
- MoveableAutoLock auto_lock(std::move(lock)); |
- // Unless we're nested, try to avoid posting redundant DoWorks. |
- if (!any_thread().is_nested && |
- (any_thread().do_work_running_count == 1 || |
- any_thread().immediate_do_work_posted_count > 0)) { |
+ bool on_main_thread = delegate_->BelongsToCurrentThread(); |
+ // De-duplicate DoWork posts. |
+ if (on_main_thread) { |
+ if (!main_thread_pending_wakeups_.insert(base::TimeTicks()).second) { |
return; |
} |
- |
- any_thread().immediate_do_work_posted_count++; |
- } |
- delegate_->PostTask(from_here, immediate_do_work_closure_); |
+ delegate_->PostTask(from_here, from_main_thread_immediate_do_work_closure_); |
+ } else { |
+ { |
+ base::AutoLock lock(other_thread_lock_); |
+ if (other_thread_pending_wakeup_) |
+ return; |
+ other_thread_pending_wakeup_ = true; |
+ } |
+ delegate_->PostTask(from_here, |
+ from_other_thread_immediate_do_work_closure_); |
+ } |
} |
void TaskQueueManager::MaybeScheduleDelayedWork( |
@@ -222,75 +192,54 @@ |
base::TimeDelta delay) { |
DCHECK(main_thread_checker_.CalledOnValidThread()); |
DCHECK_GE(delay, base::TimeDelta()); |
- { |
- base::AutoLock lock(any_thread_lock_); |
- |
- // Unless we're nested, don't post a delayed DoWork if there's an immediate |
- // DoWork in flight or we're inside a DoWork. We can rely on DoWork posting |
- // a delayed continuation as needed. |
- if (!any_thread().is_nested && |
- (any_thread().immediate_do_work_posted_count > 0 || |
- any_thread().do_work_running_count == 1)) { |
- return; |
- } |
- } |
- |
+ |
+ // If there's a pending immediate DoWork then we rely on |
+ // TryAdvanceTimeDomains getting the TimeDomain to call |
+ // MaybeScheduleDelayedWork again when the immediate DoWork is complete. |
+ if (main_thread_pending_wakeups_.find(base::TimeTicks()) != |
+ main_thread_pending_wakeups_.end()) { |
+ return; |
+ } |
// De-duplicate DoWork posts. |
base::TimeTicks run_time = now + delay; |
- if (next_delayed_do_work_ <= run_time && !next_delayed_do_work_.is_null()) |
+ if (!main_thread_pending_wakeups_.empty() && |
+ *main_thread_pending_wakeups_.begin() <= run_time) { |
return; |
- |
- TRACE_EVENT1(tracing_category_, "MaybeScheduleDelayedWorkInternal", |
- "delay_ms", delay.InMillisecondsF()); |
- |
- cancelable_delayed_do_work_closure_.Reset(delayed_do_work_closure_); |
- next_delayed_do_work_ = run_time; |
+ } |
+ main_thread_pending_wakeups_.insert(run_time); |
delegate_->PostDelayedTask( |
- from_here, cancelable_delayed_do_work_closure_.callback(), delay); |
-} |
- |
-void TaskQueueManager::DoWork(bool delayed) { |
- DCHECK(main_thread_checker_.CalledOnValidThread()); |
- TRACE_EVENT1(tracing_category_, "TaskQueueManager::DoWork", "delayed", |
- delayed); |
+ from_here, base::Bind(&TaskQueueManager::DoWork, |
+ weak_factory_.GetWeakPtr(), run_time, true), |
+ delay); |
+} |
+ |
+void TaskQueueManager::DoWork(base::TimeTicks run_time, bool from_main_thread) { |
+ DCHECK(main_thread_checker_.CalledOnValidThread()); |
+ TRACE_EVENT1(tracing_category_, "TaskQueueManager::DoWork", |
+ "from_main_thread", from_main_thread); |
+ |
+ if (from_main_thread) { |
+ main_thread_pending_wakeups_.erase(run_time); |
+ } else { |
+ base::AutoLock lock(other_thread_lock_); |
+ other_thread_pending_wakeup_ = false; |
+ } |
+ |
+ // Posting a DoWork while a DoWork is running leads to spurious DoWorks. |
+ main_thread_pending_wakeups_.insert(base::TimeTicks()); |
+ |
+ if (!delegate_->IsNested()) |
+ queues_to_delete_.clear(); |
+ |
LazyNow lazy_now(real_time_domain()->CreateLazyNow()); |
- |
- bool is_nested = delegate_->IsNested(); |
- if (!is_nested) |
- queues_to_delete_.clear(); |
- |
- // This must be done before running any tasks because they could invoke a |
- // nested message loop and we risk having a stale |next_delayed_do_work_|. |
- if (delayed) |
- next_delayed_do_work_ = base::TimeTicks(); |
+ UpdateWorkQueues(lazy_now); |
for (int i = 0; i < work_batch_size_; i++) { |
- std::set<internal::TaskQueueImpl*> queues_to_reload; |
- |
- { |
- base::AutoLock lock(any_thread_lock_); |
- any_thread().is_nested = is_nested; |
- DCHECK_EQ(any_thread().is_nested, delegate_->IsNested()); |
- |
- if (i == 0) { |
- any_thread().do_work_running_count++; |
- |
- if (!delayed) { |
- any_thread().immediate_do_work_posted_count--; |
- DCHECK_GE(any_thread().immediate_do_work_posted_count, 0); |
- } |
- } |
- std::swap(queues_to_reload, any_thread().has_incoming_immediate_work); |
- } |
- |
- UpdateWorkQueues(&queues_to_reload, &lazy_now); |
- |
- internal::WorkQueue* work_queue = nullptr; |
+ internal::WorkQueue* work_queue; |
if (!SelectWorkQueueToService(&work_queue)) |
break; |
- // NB this may unregister the queue. |
- switch (ProcessTaskFromWorkQueue(work_queue, is_nested, &lazy_now)) { |
+ switch (ProcessTaskFromWorkQueue(work_queue, &lazy_now)) { |
case ProcessTaskResult::DEFERRED: |
// If a task was deferred, try again with another task. |
continue; |
@@ -300,117 +249,31 @@ |
return; // The TaskQueueManager got deleted, we must bail out. |
} |
+ work_queue = nullptr; // The queue may have been unregistered. |
+ |
+ UpdateWorkQueues(lazy_now); |
+ |
// Only run a single task per batch in nested run loops so that we can |
// properly exit the nested loop when someone calls RunLoop::Quit(). |
- if (is_nested) |
+ if (delegate_->IsNested()) |
break; |
} |
+ |
+ main_thread_pending_wakeups_.erase(base::TimeTicks()); |
// TODO(alexclarke): Consider refactoring the above loop to terminate only |
// when there's no more work left to be done, rather than posting a |
// continuation task. |
- |
- { |
- MoveableAutoLock lock(any_thread_lock_); |
- base::Optional<base::TimeDelta> next_delay = |
- ComputeDelayTillNextTaskLocked(&lazy_now); |
- |
- any_thread().do_work_running_count--; |
- DCHECK_GE(any_thread().do_work_running_count, 0); |
- |
- any_thread().is_nested = is_nested; |
- DCHECK_EQ(any_thread().is_nested, delegate_->IsNested()); |
- |
- PostDoWorkContinuationLocked(next_delay, &lazy_now, std::move(lock)); |
- } |
-} |
- |
-void TaskQueueManager::PostDoWorkContinuationLocked( |
- base::Optional<base::TimeDelta> next_delay, |
- LazyNow* lazy_now, |
- MoveableAutoLock&& lock) { |
- base::TimeDelta delay; |
- |
- { |
- MoveableAutoLock auto_lock(std::move(lock)); |
- |
- // If there are no tasks left then we don't need to post a continuation. |
- if (!next_delay) { |
- // If there's a pending delayed DoWork, cancel it because it's not needed. |
- if (!next_delayed_do_work_.is_null()) { |
- next_delayed_do_work_ = base::TimeTicks(); |
- cancelable_delayed_do_work_closure_.Cancel(); |
- } |
- return; |
- } |
- |
- // If an immediate DoWork is posted, we don't need to post a continuation. |
- if (any_thread().immediate_do_work_posted_count > 0) |
- return; |
- |
- delay = next_delay.value(); |
- |
- // This isn't supposed to happen, but in case it does convert to |
- // non-delayed. |
- if (delay < base::TimeDelta()) |
- delay = base::TimeDelta(); |
- |
- if (delay.is_zero()) { |
- // If a delayed DoWork is pending then we don't need to post a |
- // continuation because it should run immediately. |
- if (!next_delayed_do_work_.is_null() && |
- next_delayed_do_work_ <= lazy_now->Now()) { |
- return; |
- } |
- |
- any_thread().immediate_do_work_posted_count++; |
- } else { |
- base::TimeTicks run_time = lazy_now->Now() + delay; |
- if (next_delayed_do_work_ == run_time) |
- return; |
- |
- next_delayed_do_work_ = run_time; |
- } |
- } |
- |
- // We avoid holding |any_thread_lock_| while posting the task. |
- if (delay.is_zero()) { |
- delegate_->PostTask(FROM_HERE, immediate_do_work_closure_); |
- } else { |
- cancelable_delayed_do_work_closure_.Reset(delayed_do_work_closure_); |
- delegate_->PostDelayedTask( |
- FROM_HERE, cancelable_delayed_do_work_closure_.callback(), delay); |
- } |
-} |
- |
-base::Optional<base::TimeDelta> |
-TaskQueueManager::ComputeDelayTillNextTaskLocked(LazyNow* lazy_now) { |
- // If we have incoming immediate work for any enabled queue, we know there is |
- // immediate work to be done. |
- for (internal::TaskQueueImpl* queue : |
- any_thread().has_incoming_immediate_work) { |
- if (queue->IsQueueEnabled()) |
- return base::TimeDelta(); |
- } |
- |
- // If the selector has non-empty queues we trivially know there is immediate |
- // work to be done. |
- if (!selector_.EnabledWorkQueuesEmpty()) |
- return base::TimeDelta(); |
- |
- UpdateWorkQueues(nullptr, lazy_now); |
- |
- // Otherwise we need to find the shortest delay, if any. |
- base::Optional<base::TimeDelta> next_continuation; |
+ if (!selector_.EnabledWorkQueuesEmpty() || TryAdvanceTimeDomains()) |
+ MaybeScheduleImmediateWork(FROM_HERE); |
+} |
+ |
+bool TaskQueueManager::TryAdvanceTimeDomains() { |
+ bool can_advance = false; |
for (TimeDomain* time_domain : time_domains_) { |
- base::Optional<base::TimeDelta> continuation = |
- time_domain->DelayTillNextTask(lazy_now); |
- if (!continuation) |
- continue; |
- if (!next_continuation || next_continuation.value() > continuation.value()) |
- next_continuation = continuation; |
- } |
- return next_continuation; |
+ can_advance |= time_domain->MaybeAdvanceTime(); |
+ } |
+ return can_advance; |
} |
bool TaskQueueManager::SelectWorkQueueToService( |
@@ -429,7 +292,6 @@ |
TaskQueueManager::ProcessTaskResult TaskQueueManager::ProcessTaskFromWorkQueue( |
internal::WorkQueue* work_queue, |
- bool is_nested, |
LazyNow* lazy_now) { |
DCHECK(main_thread_checker_.CalledOnValidThread()); |
scoped_refptr<DeletionSentinel> protect(deletion_sentinel_); |
@@ -444,7 +306,7 @@ |
if (queue->GetQuiescenceMonitored()) |
task_was_run_on_quiescence_monitored_queue_ = true; |
- if (!pending_task.nestable && is_nested) { |
+ if (!pending_task.nestable && delegate_->IsNested()) { |
// Defer non-nestable work to the main task runner. NOTE these tasks can be |
// arbitrarily delayed so the additional delay should not be a problem. |
// TODO(skyostil): Figure out a way to not forget which task queue the |
@@ -457,8 +319,7 @@ |
return ProcessTaskResult::DEFERRED; |
} |
- if (record_task_delay_histograms_) |
- MaybeRecordTaskDelayHistograms(pending_task, queue); |
+ MaybeRecordTaskDelayHistograms(pending_task, queue); |
double task_start_time = 0; |
TRACE_TASK_EXECUTION("TaskQueueManager::ProcessTaskFromWorkQueue", |
@@ -609,22 +470,13 @@ |
for (auto* time_domain : time_domains_) |
time_domain->AsValueInto(state.get()); |
state->EndArray(); |
- |
- { |
- base::AutoLock lock(any_thread_lock_); |
- state->SetBoolean("is_nested", any_thread().is_nested); |
- state->SetInteger("do_work_running_count", |
- any_thread().do_work_running_count); |
- state->SetInteger("immediate_do_work_posted_count", |
- any_thread().immediate_do_work_posted_count); |
- } |
return std::move(state); |
} |
void TaskQueueManager::OnTaskQueueEnabled(internal::TaskQueueImpl* queue) { |
DCHECK(main_thread_checker_.CalledOnValidThread()); |
// Only schedule DoWork if there's something to do. |
- if (queue->HasPendingImmediateWork() && !queue->BlockedByFence()) |
+ if (queue->HasPendingImmediateWork()) |
MaybeScheduleImmediateWork(FROM_HERE); |
} |
@@ -642,11 +494,5 @@ |
return !selector_.EnabledWorkQueuesEmpty(); |
} |
-void TaskQueueManager::SetRecordTaskDelayHistograms( |
- bool record_task_delay_histograms) { |
- DCHECK(main_thread_checker_.CalledOnValidThread()); |
- record_task_delay_histograms_ = record_task_delay_histograms; |
-} |
- |
} // namespace scheduler |
} // namespace blink |