TaskScheduler: Bump thread priority during shutdown.
Bump the priority of BACKGROUND threads to NORMAL during shutdown.
This will prevent TaskTracker::Shutdown() from waiting for work to
be done by threads that never get a time slice.
BUG=553459
Review-Url: https://codereview.chromium.org/2161213002
Cr-Commit-Position: refs/heads/master@{#407147}
diff --git a/base/task_scheduler/scheduler_worker.cc b/base/task_scheduler/scheduler_worker.cc
index 5a2a7d6a..e989934 100644
--- a/base/task_scheduler/scheduler_worker.cc
+++ b/base/task_scheduler/scheduler_worker.cc
@@ -10,6 +10,7 @@
#include "base/logging.h"
#include "base/task_scheduler/task_tracker.h"
+#include "build/build_config.h"
namespace base {
namespace internal {
@@ -39,6 +40,11 @@
while (!outer_->task_tracker_->IsShutdownComplete() &&
!outer_->ShouldExitForTesting()) {
DCHECK(outer_);
+
+#if !defined(OS_LINUX)
+ UpdateThreadPriority(GetDesiredThreadPriority());
+#endif
+
// Get the sequence containing the next task to execute.
scoped_refptr<Sequence> sequence = outer_->delegate_->GetWork(outer_);
if (!sequence) {
@@ -91,17 +97,17 @@
private:
Thread(SchedulerWorker* outer)
- : outer_(outer),
- wake_up_event_(WaitableEvent::ResetPolicy::MANUAL,
- WaitableEvent::InitialState::NOT_SIGNALED) {
+ : outer_(outer),
+ wake_up_event_(WaitableEvent::ResetPolicy::MANUAL,
+ WaitableEvent::InitialState::NOT_SIGNALED),
+ current_thread_priority_(GetDesiredThreadPriority()) {
DCHECK(outer_);
}
void Initialize() {
constexpr size_t kDefaultStackSize = 0;
- PlatformThread::CreateWithPriority(kDefaultStackSize, this,
- &thread_handle_,
- outer_->thread_priority_);
+ PlatformThread::CreateWithPriority(kDefaultStackSize, this, &thread_handle_,
+ current_thread_priority_);
}
void WaitForWork() {
@@ -117,6 +123,31 @@
wake_up_event_.Reset();
}
+ // Returns the desired thread priority based on the worker priority and the
+ // current shutdown state.
+ ThreadPriority GetDesiredThreadPriority() {
+ DCHECK(outer_);
+
+ if (outer_->task_tracker_->HasShutdownStarted() &&
+ static_cast<int>(outer_->thread_priority_) <
+ static_cast<int>(ThreadPriority::NORMAL)) {
+ return ThreadPriority::NORMAL;
+ }
+ return outer_->thread_priority_;
+ }
+
+ // Increasing the thread priority requires the CAP_SYS_NICE capability on
+ // Linux.
+#if !defined(OS_LINUX)
+ void UpdateThreadPriority(ThreadPriority desired_thread_priority) {
+ if (desired_thread_priority == current_thread_priority_)
+ return;
+
+ PlatformThread::SetCurrentThreadPriority(desired_thread_priority);
+ current_thread_priority_ = desired_thread_priority;
+ }
+#endif // !defined(OS_LINUX)
+
PlatformThreadHandle thread_handle_;
SchedulerWorker* outer_;
@@ -124,6 +155,10 @@
// Event signaled to wake up this thread.
WaitableEvent wake_up_event_;
+ // Current priority of this thread. May be different from
+ // |outer_->thread_priority_| during shutdown.
+ ThreadPriority current_thread_priority_;
+
DISALLOW_COPY_AND_ASSIGN(Thread);
};
diff --git a/base/task_scheduler/scheduler_worker_unittest.cc b/base/task_scheduler/scheduler_worker_unittest.cc
index 437bbedfd..fb94edc 100644
--- a/base/task_scheduler/scheduler_worker_unittest.cc
+++ b/base/task_scheduler/scheduler_worker_unittest.cc
@@ -14,11 +14,14 @@
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/waitable_event.h"
#include "base/task_scheduler/scheduler_lock.h"
#include "base/task_scheduler/sequence.h"
#include "base/task_scheduler/task.h"
#include "base/task_scheduler/task_tracker.h"
+#include "base/threading/platform_thread.h"
#include "base/time/time.h"
+#include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace base {
@@ -421,6 +424,108 @@
worker->JoinForTesting();
}
+namespace {
+
+class ExpectThreadPriorityDelegate : public SchedulerWorker::Delegate {
+ public:
+ ExpectThreadPriorityDelegate()
+ : priority_verified_in_get_work_event_(
+ WaitableEvent::ResetPolicy::AUTOMATIC,
+ WaitableEvent::InitialState::NOT_SIGNALED),
+ expected_thread_priority_(ThreadPriority::BACKGROUND) {}
+
+ void SetExpectedThreadPriority(ThreadPriority expected_thread_priority) {
+ expected_thread_priority_ = expected_thread_priority;
+ }
+
+ void WaitForPriorityVerifiedInGetWork() {
+ priority_verified_in_get_work_event_.Wait();
+ }
+
+ // SchedulerWorker::Delegate:
+ void OnMainEntry(SchedulerWorker* worker) override { VerifyThreadPriority(); }
+ scoped_refptr<Sequence> GetWork(SchedulerWorker* worker) override {
+ VerifyThreadPriority();
+ priority_verified_in_get_work_event_.Signal();
+ return nullptr;
+ }
+ void ReEnqueueSequence(scoped_refptr<Sequence> sequence) override {}
+ TimeDelta GetSleepTimeout() override { return TimeDelta::Max(); }
+ bool CanDetach(SchedulerWorker* worker) override { return false; }
+
+ private:
+ void VerifyThreadPriority() {
+ AutoSchedulerLock auto_lock(expected_thread_priority_lock_);
+ EXPECT_EQ(expected_thread_priority_,
+ PlatformThread::GetCurrentThreadPriority());
+ }
+
+ // Signaled after GetWork() has verified the priority of the worker thread.
+ WaitableEvent priority_verified_in_get_work_event_;
+
+ // Synchronizes access to |expected_thread_priority_|.
+ SchedulerLock expected_thread_priority_lock_;
+
+ // Expected thread priority for the next call to OnMainEntry() or GetWork().
+ ThreadPriority expected_thread_priority_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExpectThreadPriorityDelegate);
+};
+
+} // namespace
+
+// Increasing the thread priority requires the CAP_SYS_NICE capability on Linux.
+#if !defined(OS_LINUX)
+TEST(TaskSchedulerWorkerTest, BumpPriorityOfAliveThreadDuringShutdown) {
+ TaskTracker task_tracker;
+
+ std::unique_ptr<ExpectThreadPriorityDelegate> delegate(
+ new ExpectThreadPriorityDelegate);
+ ExpectThreadPriorityDelegate* delegate_raw = delegate.get();
+ delegate_raw->SetExpectedThreadPriority(ThreadPriority::BACKGROUND);
+
+ std::unique_ptr<SchedulerWorker> worker = SchedulerWorker::Create(
+ ThreadPriority::BACKGROUND, std::move(delegate), &task_tracker,
+ SchedulerWorker::InitialState::ALIVE);
+
+ // Verify that the initial thread priority is BACKGROUND.
+ worker->WakeUp();
+ delegate_raw->WaitForPriorityVerifiedInGetWork();
+
+ // Verify that the thread priority is bumped to NORMAL during shutdown.
+ delegate_raw->SetExpectedThreadPriority(ThreadPriority::NORMAL);
+ task_tracker.SetHasShutdownStartedForTesting();
+ worker->WakeUp();
+ delegate_raw->WaitForPriorityVerifiedInGetWork();
+
+ worker->JoinForTesting();
+}
+#endif // defined(OS_LINUX)
+
+TEST(TaskSchedulerWorkerTest, BumpPriorityOfDetachedThreadDuringShutdown) {
+ TaskTracker task_tracker;
+
+ std::unique_ptr<ExpectThreadPriorityDelegate> delegate(
+ new ExpectThreadPriorityDelegate);
+ ExpectThreadPriorityDelegate* delegate_raw = delegate.get();
+ delegate_raw->SetExpectedThreadPriority(ThreadPriority::NORMAL);
+
+ // Create a DETACHED thread.
+ std::unique_ptr<SchedulerWorker> worker = SchedulerWorker::Create(
+ ThreadPriority::BACKGROUND, std::move(delegate), &task_tracker,
+ SchedulerWorker::InitialState::DETACHED);
+
+ // Pretend that shutdown has started.
+ task_tracker.SetHasShutdownStartedForTesting();
+
+ // Wake up the thread and verify that its priority is NORMAL when
+ // OnMainEntry() and GetWork() are called.
+ worker->WakeUp();
+ delegate_raw->WaitForPriorityVerifiedInGetWork();
+
+ worker->JoinForTesting();
+}
+
} // namespace
} // namespace internal
} // namespace base
diff --git a/base/task_scheduler/task_tracker.cc b/base/task_scheduler/task_tracker.cc
index 8817baa..1ebc81b 100644
--- a/base/task_scheduler/task_tracker.cc
+++ b/base/task_scheduler/task_tracker.cc
@@ -124,6 +124,7 @@
// This method can only be called once.
DCHECK(!shutdown_event_);
DCHECK(!num_block_shutdown_tasks_posted_during_shutdown_);
+ DCHECK(!state_->HasShutdownStarted());
shutdown_event_.reset(
new WaitableEvent(WaitableEvent::ResetPolicy::MANUAL,
@@ -214,14 +215,17 @@
AfterRunTask(shutdown_behavior);
}
+bool TaskTracker::HasShutdownStarted() const {
+ return state_->HasShutdownStarted();
+}
+
bool TaskTracker::IsShutdownComplete() const {
AutoSchedulerLock auto_lock(shutdown_lock_);
return shutdown_event_ && shutdown_event_->IsSignaled();
}
-bool TaskTracker::IsShuttingDownForTesting() const {
- AutoSchedulerLock auto_lock(shutdown_lock_);
- return shutdown_event_ && !shutdown_event_->IsSignaled();
+void TaskTracker::SetHasShutdownStartedForTesting() {
+ state_->StartShutdown();
}
bool TaskTracker::BeforePostTask(TaskShutdownBehavior shutdown_behavior) {
diff --git a/base/task_scheduler/task_tracker.h b/base/task_scheduler/task_tracker.h
index fc0901f..609edbc 100644
--- a/base/task_scheduler/task_tracker.h
+++ b/base/task_scheduler/task_tracker.h
@@ -44,12 +44,17 @@
// must have allowed |task| to be posted.
void RunTask(const Task* task);
- // Returns true if shutdown has completed.
+ // Returns true once shutdown has started (Shutdown() has been called but
+ // might not have returned).
+ bool HasShutdownStarted() const;
+
+ // Returns true if shutdown has completed (Shutdown() has returned).
bool IsShutdownComplete() const;
- // Returns true while shutdown is in progress (i.e. Shutdown() has been called
- // but hasn't returned).
- bool IsShuttingDownForTesting() const;
+ // Causes HasShutdownStarted() to return true. Unlike when Shutdown() returns,
+ // IsShutdownComplete() won't return true after this returns. Shutdown()
+ // cannot be called after this.
+ void SetHasShutdownStartedForTesting();
private:
class State;
diff --git a/base/task_scheduler/task_tracker_unittest.cc b/base/task_scheduler/task_tracker_unittest.cc
index 58f47b81..326f7aa0 100644
--- a/base/task_scheduler/task_tracker_unittest.cc
+++ b/base/task_scheduler/task_tracker_unittest.cc
@@ -132,10 +132,8 @@
ASSERT_FALSE(thread_calling_shutdown_);
thread_calling_shutdown_.reset(new ThreadCallingShutdown(&tracker_));
thread_calling_shutdown_->Start();
- while (!tracker_.IsShuttingDownForTesting() &&
- !tracker_.IsShutdownComplete()) {
+ while (!tracker_.HasShutdownStarted())
PlatformThread::YieldCurrentThread();
- }
}
void WaitForAsyncIsShutdownComplete() {
@@ -148,8 +146,8 @@
void VerifyAsyncShutdownInProgress() {
ASSERT_TRUE(thread_calling_shutdown_);
EXPECT_FALSE(thread_calling_shutdown_->has_returned());
+ EXPECT_TRUE(tracker_.HasShutdownStarted());
EXPECT_FALSE(tracker_.IsShutdownComplete());
- EXPECT_TRUE(tracker_.IsShuttingDownForTesting());
}
size_t NumTasksExecuted() {