Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1628)

Unified Diff: base/task_scheduler/scheduler_worker_unittest.cc

Issue 2692863012: SchedulerWorker Refcounting for Destruction in Production (Closed)
Patch Set: Remove Last Vestiges of std::unique_ptr SchedulerWorker Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « base/task_scheduler/scheduler_worker_stack_unittest.cc ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: base/task_scheduler/scheduler_worker_unittest.cc
diff --git a/base/task_scheduler/scheduler_worker_unittest.cc b/base/task_scheduler/scheduler_worker_unittest.cc
index b65d50c07c3fb5db64ba8df08031bc6934ef10a5..b8dea8e7454e5a41c432e49180e893e0e074d788 100644
--- a/base/task_scheduler/scheduler_worker_unittest.cc
+++ b/base/task_scheduler/scheduler_worker_unittest.cc
@@ -13,13 +13,16 @@
#include "base/bind_helpers.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
+#include "base/memory/ref_counted.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/test/test_timeouts.h"
#include "base/threading/platform_thread.h"
+#include "base/threading/simple_thread.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "testing/gmock/include/gmock/gmock.h"
@@ -121,7 +124,7 @@ class TaskSchedulerWorkerTest : public testing::TestWithParam<size_t> {
return re_enqueued_sequences_;
}
- std::unique_ptr<SchedulerWorker> worker_;
+ scoped_refptr<SchedulerWorker> worker_;
private:
class TestSchedulerWorkerDelegate : public SchedulerWorkerDefaultDelegate {
@@ -355,34 +358,92 @@ namespace {
class ControllableDetachDelegate : public SchedulerWorkerDefaultDelegate {
public:
- ControllableDetachDelegate(TaskTracker* task_tracker)
- : task_tracker_(task_tracker),
- work_processed_(WaitableEvent::ResetPolicy::MANUAL,
- WaitableEvent::InitialState::NOT_SIGNALED),
- detach_requested_(WaitableEvent::ResetPolicy::MANUAL,
+ class Controls : public RefCountedThreadSafe<Controls> {
+ public:
+ Controls()
+ : work_running_(WaitableEvent::ResetPolicy::MANUAL,
+ WaitableEvent::InitialState::SIGNALED),
+ work_processed_(WaitableEvent::ResetPolicy::MANUAL,
WaitableEvent::InitialState::NOT_SIGNALED),
- detached_(WaitableEvent::ResetPolicy::MANUAL,
- WaitableEvent::InitialState::NOT_SIGNALED) {
- EXPECT_TRUE(task_tracker_);
- }
+ detach_requested_(WaitableEvent::ResetPolicy::MANUAL,
+ WaitableEvent::InitialState::NOT_SIGNALED),
+ detached_(WaitableEvent::ResetPolicy::MANUAL,
+ WaitableEvent::InitialState::NOT_SIGNALED),
+ can_detach_block_(WaitableEvent::ResetPolicy::MANUAL,
+ WaitableEvent::InitialState::SIGNALED),
+ destroyed_(WaitableEvent::ResetPolicy::MANUAL,
+ WaitableEvent::InitialState::NOT_SIGNALED) {}
- ~ControllableDetachDelegate() override = default;
+ void HaveWorkBlock() { work_running_.Reset(); }
- // SchedulerWorker::Delegate:
- MOCK_METHOD1(OnMainEntry, void(SchedulerWorker* worker));
+ void UnblockWork() { work_running_.Signal(); }
+
+ void MakeCanDetachBlock() { can_detach_block_.Reset(); }
+
+ void UnblockCanDetach() { can_detach_block_.Signal(); }
+
+ void WaitForWorkToRun() { work_processed_.Wait(); }
+
+ void WaitForDetachRequest() { detach_requested_.Wait(); }
+
+ void WaitForDetach() { detached_.Wait(); }
+
+ void WaitForDelegateDestroy() { destroyed_.Wait(); }
+
+ void ResetState() {
+ work_running_.Signal();
+ work_processed_.Reset();
+ detach_requested_.Reset();
+ can_detach_block_.Signal();
+ work_requested_ = false;
+ }
+
+ void set_can_detach(bool can_detach) { can_detach_ = can_detach; }
+
+ private:
+ friend class ControllableDetachDelegate;
+ friend class RefCountedThreadSafe<Controls>;
+ ~Controls() = default;
+
+ WaitableEvent work_running_;
+ WaitableEvent work_processed_;
+ WaitableEvent detach_requested_;
+ WaitableEvent detached_;
+ WaitableEvent can_detach_block_;
+ WaitableEvent destroyed_;
+
+ bool can_detach_ = false;
+ bool work_requested_ = false;
+
+ DISALLOW_COPY_AND_ASSIGN(Controls);
+ };
+
+ ControllableDetachDelegate(TaskTracker* task_tracker)
+ : task_tracker_(task_tracker), controls_(new Controls()) {}
+
+ ~ControllableDetachDelegate() override { controls_->destroyed_.Signal(); }
scoped_refptr<Sequence> GetWork(SchedulerWorker* worker)
override {
// Sends one item of work to signal |work_processed_|. On subsequent calls,
// sends nullptr to indicate there's no more work to be done.
- if (work_requested_)
+ if (controls_->work_requested_)
return nullptr;
- work_requested_ = true;
+ controls_->work_requested_ = true;
scoped_refptr<Sequence> sequence(new Sequence);
std::unique_ptr<Task> task(new Task(
- FROM_HERE, Bind(&WaitableEvent::Signal, Unretained(&work_processed_)),
- TaskTraits(), TimeDelta()));
+ FROM_HERE,
+ Bind(
+ [](WaitableEvent* work_processed, WaitableEvent* work_running) {
+ work_processed->Signal();
+ work_running->Wait();
+ },
+ Unretained(&controls_->work_processed_),
+ Unretained(&controls_->work_running_)),
+ TaskTraits().WithBaseSyncPrimitives().WithShutdownBehavior(
+ TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN),
+ TimeDelta()));
EXPECT_TRUE(task_tracker_->WillPostTask(task.get()));
sequence->PushTask(std::move(task));
return sequence;
@@ -391,43 +452,42 @@ class ControllableDetachDelegate : public SchedulerWorkerDefaultDelegate {
void DidRunTask() override {}
bool CanDetach(SchedulerWorker* worker) override {
- detach_requested_.Signal();
- return can_detach_;
+ // Saving |can_detach_| now so that callers waiting on |detach_requested_|
+ // have the thread go to sleep and then allow detachment.
+ bool can_detach = controls_->can_detach_;
+ controls_->detach_requested_.Signal();
+ controls_->can_detach_block_.Wait();
+ return can_detach;
}
void OnDetach() override {
- EXPECT_TRUE(can_detach_);
- EXPECT_TRUE(detach_requested_.IsSignaled());
- detached_.Signal();
+ EXPECT_TRUE(controls_->can_detach_);
+ EXPECT_TRUE(controls_->detach_requested_.IsSignaled());
+ controls_->detached_.Signal();
}
- void WaitForWorkToRun() {
- work_processed_.Wait();
- }
+ // ControllableDetachDelegate:
+ scoped_refptr<Controls> controls() { return controls_; }
- void WaitForDetachRequest() {
- detach_requested_.Wait();
- }
+ private:
+ scoped_refptr<Sequence> work_sequence_;
+ TaskTracker* const task_tracker_;
+ scoped_refptr<Controls> controls_;
- void WaitForDetach() { detached_.Wait(); }
+ DISALLOW_COPY_AND_ASSIGN(ControllableDetachDelegate);
+};
- void ResetState() {
- work_requested_ = false;
- work_processed_.Reset();
- detach_requested_.Reset();
- }
+class MockedControllableDetachDelegate : public ControllableDetachDelegate {
+ public:
+ MockedControllableDetachDelegate(TaskTracker* task_tracker)
+ : ControllableDetachDelegate(task_tracker){};
+ ~MockedControllableDetachDelegate() = default;
- void set_can_detach(bool can_detach) { can_detach_ = can_detach; }
+ // SchedulerWorker::Delegate:
+ MOCK_METHOD1(OnMainEntry, void(SchedulerWorker* worker));
private:
- TaskTracker* const task_tracker_;
- bool work_requested_ = false;
- bool can_detach_ = false;
- WaitableEvent work_processed_;
- WaitableEvent detach_requested_;
- WaitableEvent detached_;
-
- DISALLOW_COPY_AND_ASSIGN(ControllableDetachDelegate);
+ DISALLOW_COPY_AND_ASSIGN(MockedControllableDetachDelegate);
};
} // namespace
@@ -435,50 +495,232 @@ class ControllableDetachDelegate : public SchedulerWorkerDefaultDelegate {
TEST(TaskSchedulerWorkerTest, WorkerDetaches) {
TaskTracker task_tracker;
// Will be owned by SchedulerWorker.
- ControllableDetachDelegate* delegate =
- new StrictMock<ControllableDetachDelegate>(&task_tracker);
- delegate->set_can_detach(true);
+ MockedControllableDetachDelegate* delegate =
+ new StrictMock<MockedControllableDetachDelegate>(&task_tracker);
+ scoped_refptr<ControllableDetachDelegate::Controls> controls =
+ delegate->controls();
+ controls->set_can_detach(true);
EXPECT_CALL(*delegate, OnMainEntry(_));
- std::unique_ptr<SchedulerWorker> worker =
- SchedulerWorker::Create(
- ThreadPriority::NORMAL, WrapUnique(delegate), &task_tracker,
- SchedulerWorker::InitialState::ALIVE);
+ scoped_refptr<SchedulerWorker> worker = SchedulerWorker::Create(
+ ThreadPriority::NORMAL, WrapUnique(delegate), &task_tracker,
+ SchedulerWorker::InitialState::ALIVE);
worker->WakeUp();
- delegate->WaitForWorkToRun();
+ controls->WaitForWorkToRun();
Mock::VerifyAndClear(delegate);
- delegate->WaitForDetachRequest();
- delegate->WaitForDetach();
+ controls->WaitForDetachRequest();
+ controls->WaitForDetach();
ASSERT_FALSE(worker->ThreadAliveForTesting());
}
+TEST(TaskSchedulerWorkerTest, WorkerCleanupBeforeDetach) {
+ TaskTracker task_tracker;
+ // Will be owned by SchedulerWorker.
+ // No mock here as that's reasonably covered by other tests and the delegate
+ // may destroy on a different thread. Mocks aren't designed with that in mind.
+ std::unique_ptr<ControllableDetachDelegate> delegate =
+ MakeUnique<ControllableDetachDelegate>(&task_tracker);
+ scoped_refptr<ControllableDetachDelegate::Controls> controls =
+ delegate->controls();
+
+ controls->set_can_detach(true);
+ controls->MakeCanDetachBlock();
+
+ scoped_refptr<SchedulerWorker> worker = SchedulerWorker::Create(
+ ThreadPriority::NORMAL, std::move(delegate), &task_tracker,
+ SchedulerWorker::InitialState::ALIVE);
+ worker->WakeUp();
+
+ controls->WaitForDetachRequest();
+ worker->Cleanup();
+ worker = nullptr;
+ controls->UnblockCanDetach();
+ controls->WaitForDelegateDestroy();
+}
+
+TEST(TaskSchedulerWorkerTest, WorkerCleanupAfterDetach) {
+ TaskTracker task_tracker;
+ // Will be owned by SchedulerWorker.
+ // No mock here as that's reasonably covered by other tests and the delegate
+ // may destroy on a different thread. Mocks aren't designed with that in mind.
+ std::unique_ptr<ControllableDetachDelegate> delegate =
+ MakeUnique<ControllableDetachDelegate>(&task_tracker);
+ scoped_refptr<ControllableDetachDelegate::Controls> controls =
+ delegate->controls();
+
+ controls->set_can_detach(true);
+
+ scoped_refptr<SchedulerWorker> worker = SchedulerWorker::Create(
+ ThreadPriority::NORMAL, std::move(delegate), &task_tracker,
+ SchedulerWorker::InitialState::ALIVE);
+ worker->WakeUp();
+
+ controls->WaitForDetach();
+ worker->Cleanup();
+ worker = nullptr;
+ controls->WaitForDelegateDestroy();
+}
+
+TEST(TaskSchedulerWorkerTest, WorkerCleanupDuringWork) {
+ TaskTracker task_tracker;
+ // Will be owned by SchedulerWorker.
+ // No mock here as that's reasonably covered by other tests and the delegate
+ // may destroy on a different thread. Mocks aren't designed with that in mind.
+ std::unique_ptr<ControllableDetachDelegate> delegate =
+ MakeUnique<ControllableDetachDelegate>(&task_tracker);
+ scoped_refptr<ControllableDetachDelegate::Controls> controls =
+ delegate->controls();
+
+ controls->HaveWorkBlock();
+
+ scoped_refptr<SchedulerWorker> worker = SchedulerWorker::Create(
+ ThreadPriority::NORMAL, std::move(delegate), &task_tracker,
+ SchedulerWorker::InitialState::ALIVE);
+ worker->WakeUp();
+
+ controls->WaitForWorkToRun();
+ worker->Cleanup();
+ worker = nullptr;
+ controls->UnblockWork();
+ controls->WaitForDelegateDestroy();
+}
+
+TEST(TaskSchedulerWorkerTest, WorkerCleanupDuringWait) {
+ TaskTracker task_tracker;
+ // Will be owned by SchedulerWorker.
+ // No mock here as that's reasonably covered by other tests and the delegate
+ // may destroy on a different thread. Mocks aren't designed with that in mind.
+ std::unique_ptr<ControllableDetachDelegate> delegate =
+ MakeUnique<ControllableDetachDelegate>(&task_tracker);
+ scoped_refptr<ControllableDetachDelegate::Controls> controls =
+ delegate->controls();
+
+ scoped_refptr<SchedulerWorker> worker = SchedulerWorker::Create(
+ ThreadPriority::NORMAL, std::move(delegate), &task_tracker,
+ SchedulerWorker::InitialState::ALIVE);
+ worker->WakeUp();
+
+ controls->WaitForDetachRequest();
+ worker->Cleanup();
+ worker = nullptr;
+ controls->WaitForDelegateDestroy();
+}
+
+TEST(TaskSchedulerWorkerTest, WorkerCleanupDuringShutdown) {
+ TaskTracker task_tracker;
+ // Will be owned by SchedulerWorker.
+ // No mock here as that's reasonably covered by other tests and the delegate
+ // may destroy on a different thread. Mocks aren't designed with that in mind.
+ std::unique_ptr<ControllableDetachDelegate> delegate =
+ MakeUnique<ControllableDetachDelegate>(&task_tracker);
+ scoped_refptr<ControllableDetachDelegate::Controls> controls =
+ delegate->controls();
+
+ controls->HaveWorkBlock();
+
+ scoped_refptr<SchedulerWorker> worker = SchedulerWorker::Create(
+ ThreadPriority::NORMAL, std::move(delegate), &task_tracker,
+ SchedulerWorker::InitialState::ALIVE);
+ worker->WakeUp();
+
+ controls->WaitForWorkToRun();
+ task_tracker.Shutdown();
+ worker->Cleanup();
+ worker = nullptr;
+ controls->UnblockWork();
+ controls->WaitForDelegateDestroy();
+}
+
+namespace {
+
+class CallJoinFromDifferentThread : public SimpleThread {
+ public:
+ CallJoinFromDifferentThread(SchedulerWorker* worker_to_join)
+ : SimpleThread("SchedulerWorkerJoinThread"),
+ worker_to_join_(worker_to_join),
+ run_started_event_(WaitableEvent::ResetPolicy::MANUAL,
+ WaitableEvent::InitialState::NOT_SIGNALED) {}
+
+ ~CallJoinFromDifferentThread() override = default;
+
+ void Run() override {
+ run_started_event_.Signal();
+ worker_to_join_->JoinForTesting();
+ }
+
+ void WaitForRunToStart() { run_started_event_.Wait(); }
+
+ private:
+ SchedulerWorker* const worker_to_join_;
+ WaitableEvent run_started_event_;
+ DISALLOW_COPY_AND_ASSIGN(CallJoinFromDifferentThread);
+};
+
+} // namespace
+
+TEST(TaskSchedulerWorkerTest, WorkerCleanupDuringJoin) {
+ TaskTracker task_tracker;
+ // Will be owned by SchedulerWorker.
+ // No mock here as that's reasonably covered by other tests and the
+ // delegate may destroy on a different thread. Mocks aren't designed with that
+ // in mind.
+ std::unique_ptr<ControllableDetachDelegate> delegate =
+ MakeUnique<ControllableDetachDelegate>(&task_tracker);
+ scoped_refptr<ControllableDetachDelegate::Controls> controls =
+ delegate->controls();
+
+ controls->HaveWorkBlock();
+
+ scoped_refptr<SchedulerWorker> worker = SchedulerWorker::Create(
+ ThreadPriority::NORMAL, std::move(delegate), &task_tracker,
+ SchedulerWorker::InitialState::ALIVE);
+ worker->WakeUp();
+
+ controls->WaitForWorkToRun();
+ CallJoinFromDifferentThread join_from_different_thread(worker.get());
+ join_from_different_thread.Start();
+ join_from_different_thread.WaitForRunToStart();
+ // Sleep here to give the other thread a chance to call JoinForTesting().
+ // Receiving a signal that Run() was called doesn't mean JoinForTesting() was
+ // necessarily called, and we can't signal after JoinForTesting() as
+ // JoinForTesting() blocks until we call UnblockWork().
+ PlatformThread::Sleep(TestTimeouts::tiny_timeout());
+ worker->Cleanup();
+ worker = nullptr;
+ controls->UnblockWork();
+ controls->WaitForDelegateDestroy();
+ join_from_different_thread.Join();
+}
+
TEST(TaskSchedulerWorkerTest, WorkerDetachesAndWakes) {
TaskTracker task_tracker;
// Will be owned by SchedulerWorker.
- ControllableDetachDelegate* delegate =
- new StrictMock<ControllableDetachDelegate>(&task_tracker);
- delegate->set_can_detach(true);
+ MockedControllableDetachDelegate* delegate =
+ new StrictMock<MockedControllableDetachDelegate>(&task_tracker);
+ scoped_refptr<ControllableDetachDelegate::Controls> controls =
+ delegate->controls();
+
+ controls->set_can_detach(true);
EXPECT_CALL(*delegate, OnMainEntry(_));
- std::unique_ptr<SchedulerWorker> worker =
- SchedulerWorker::Create(
- ThreadPriority::NORMAL, WrapUnique(delegate), &task_tracker,
- SchedulerWorker::InitialState::ALIVE);
+ scoped_refptr<SchedulerWorker> worker = SchedulerWorker::Create(
+ ThreadPriority::NORMAL, WrapUnique(delegate), &task_tracker,
+ SchedulerWorker::InitialState::ALIVE);
worker->WakeUp();
- delegate->WaitForWorkToRun();
+ controls->WaitForWorkToRun();
Mock::VerifyAndClear(delegate);
- delegate->WaitForDetachRequest();
- delegate->WaitForDetach();
+ controls->WaitForDetachRequest();
+ controls->WaitForDetach();
ASSERT_FALSE(worker->ThreadAliveForTesting());
- delegate->ResetState();
- delegate->set_can_detach(false);
+ controls->ResetState();
+ controls->set_can_detach(false);
// Expect OnMainEntry() to be called when SchedulerWorker recreates its
// thread.
EXPECT_CALL(*delegate, OnMainEntry(worker.get()));
worker->WakeUp();
- delegate->WaitForWorkToRun();
+ controls->WaitForWorkToRun();
Mock::VerifyAndClear(delegate);
- delegate->WaitForDetachRequest();
- delegate->WaitForDetach();
+ controls->WaitForDetachRequest();
+ controls->WaitForDetach();
ASSERT_TRUE(worker->ThreadAliveForTesting());
worker->JoinForTesting();
}
@@ -486,18 +728,19 @@ TEST(TaskSchedulerWorkerTest, WorkerDetachesAndWakes) {
TEST(TaskSchedulerWorkerTest, CreateDetached) {
TaskTracker task_tracker;
// Will be owned by SchedulerWorker.
- ControllableDetachDelegate* delegate =
- new StrictMock<ControllableDetachDelegate>(&task_tracker);
- std::unique_ptr<SchedulerWorker> worker =
- SchedulerWorker::Create(
- ThreadPriority::NORMAL, WrapUnique(delegate), &task_tracker,
- SchedulerWorker::InitialState::DETACHED);
+ MockedControllableDetachDelegate* delegate =
+ new StrictMock<MockedControllableDetachDelegate>(&task_tracker);
+ scoped_refptr<ControllableDetachDelegate::Controls> controls =
+ delegate->controls();
+ scoped_refptr<SchedulerWorker> worker = SchedulerWorker::Create(
+ ThreadPriority::NORMAL, WrapUnique(delegate), &task_tracker,
+ SchedulerWorker::InitialState::DETACHED);
ASSERT_FALSE(worker->ThreadAliveForTesting());
EXPECT_CALL(*delegate, OnMainEntry(worker.get()));
worker->WakeUp();
- delegate->WaitForWorkToRun();
+ controls->WaitForWorkToRun();
Mock::VerifyAndClear(delegate);
- delegate->WaitForDetachRequest();
+ controls->WaitForDetachRequest();
ASSERT_TRUE(worker->ThreadAliveForTesting());
worker->JoinForTesting();
}
@@ -560,7 +803,7 @@ TEST(TaskSchedulerWorkerTest, BumpPriorityOfAliveThreadDuringShutdown) {
? ThreadPriority::BACKGROUND
: ThreadPriority::NORMAL);
- std::unique_ptr<SchedulerWorker> worker = SchedulerWorker::Create(
+ scoped_refptr<SchedulerWorker> worker = SchedulerWorker::Create(
ThreadPriority::BACKGROUND, std::move(delegate), &task_tracker,
SchedulerWorker::InitialState::ALIVE);
@@ -587,7 +830,7 @@ TEST(TaskSchedulerWorkerTest, BumpPriorityOfDetachedThreadDuringShutdown) {
delegate_raw->SetExpectedThreadPriority(ThreadPriority::NORMAL);
// Create a DETACHED thread.
- std::unique_ptr<SchedulerWorker> worker = SchedulerWorker::Create(
+ scoped_refptr<SchedulerWorker> worker = SchedulerWorker::Create(
ThreadPriority::BACKGROUND, std::move(delegate), &task_tracker,
SchedulerWorker::InitialState::DETACHED);
« no previous file with comments | « base/task_scheduler/scheduler_worker_stack_unittest.cc ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698