1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
|
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/sync/engine/cancelation_signal.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/synchronization/waitable_event.h"
#include "base/task/single_thread_task_runner.h"
#include "base/threading/platform_thread.h"
#include "base/threading/thread.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace syncer {
class BlockingTask : public CancelationSignal::Observer {
public:
explicit BlockingTask(CancelationSignal* cancel_signal);
~BlockingTask() override;
// Starts the `exec_thread_` and uses it to execute DoRun().
void RunAsync(base::WaitableEvent* task_start_signal,
base::WaitableEvent* task_done_signal);
// Blocks until canceled. Signals `task_done_signal` when finished (either
// via early cancel or cancel after start). Signals `task_start_signal` if
// and when the task starts successfully (which will not happen if the task
// was cancelled early).
void Run(base::WaitableEvent* task_start_signal,
base::WaitableEvent* task_done_signal);
// Implementation of CancelationSignal::Observer.
// Wakes up the thread blocked in Run().
void OnCancelationSignalReceived() override;
// Checks if we ever did successfully start waiting for `event_`. Be careful
// with this. The flag itself is thread-unsafe, and the event that flips it
// is racy.
bool WasStarted();
private:
base::WaitableEvent event_;
base::Thread exec_thread_;
const raw_ptr<CancelationSignal> cancel_signal_;
bool was_started_ = false;
};
BlockingTask::BlockingTask(CancelationSignal* cancel_signal)
: event_(base::WaitableEvent::ResetPolicy::MANUAL,
base::WaitableEvent::InitialState::NOT_SIGNALED),
exec_thread_("BlockingTaskBackgroundThread"),
cancel_signal_(cancel_signal) {}
BlockingTask::~BlockingTask() {
if (was_started_) {
cancel_signal_->UnregisterHandler(this);
}
}
void BlockingTask::RunAsync(base::WaitableEvent* task_start_signal,
base::WaitableEvent* task_done_signal) {
exec_thread_.Start();
exec_thread_.task_runner()->PostTask(
FROM_HERE, base::BindOnce(&BlockingTask::Run, base::Unretained(this),
base::Unretained(task_start_signal),
base::Unretained(task_done_signal)));
}
void BlockingTask::Run(base::WaitableEvent* task_start_signal,
base::WaitableEvent* task_done_signal) {
if (cancel_signal_->TryRegisterHandler(this)) {
DCHECK(!event_.IsSignaled());
was_started_ = true;
task_start_signal->Signal();
event_.Wait();
}
task_done_signal->Signal();
}
void BlockingTask::OnCancelationSignalReceived() {
event_.Signal();
}
bool BlockingTask::WasStarted() {
return was_started_;
}
class CancelationSignalTest : public ::testing::Test {
public:
CancelationSignalTest();
~CancelationSignalTest() override;
// Starts the blocking task on a background thread. Does not wait for the
// task to start.
void StartBlockingTaskAsync();
// Starts the blocking task on a background thread. Does not return until
// the task has been started.
void StartBlockingTaskAndWaitForItToStart();
// Cancels the blocking task.
void CancelBlocking();
// Verifies that the background task was canceled early.
//
// This method may block for a brief period of time while waiting for the
// background thread to make progress.
bool VerifyTaskNotStarted();
private:
CancelationSignal signal_;
base::WaitableEvent task_start_event_;
base::WaitableEvent task_done_event_;
BlockingTask blocking_task_;
};
CancelationSignalTest::CancelationSignalTest()
: task_start_event_(base::WaitableEvent::ResetPolicy::AUTOMATIC,
base::WaitableEvent::InitialState::NOT_SIGNALED),
task_done_event_(base::WaitableEvent::ResetPolicy::AUTOMATIC,
base::WaitableEvent::InitialState::NOT_SIGNALED),
blocking_task_(&signal_) {}
CancelationSignalTest::~CancelationSignalTest() = default;
void CancelationSignalTest::StartBlockingTaskAsync() {
blocking_task_.RunAsync(&task_start_event_, &task_done_event_);
}
void CancelationSignalTest::StartBlockingTaskAndWaitForItToStart() {
blocking_task_.RunAsync(&task_start_event_, &task_done_event_);
task_start_event_.Wait();
}
void CancelationSignalTest::CancelBlocking() {
signal_.Signal();
}
bool CancelationSignalTest::VerifyTaskNotStarted() {
// Wait until BlockingTask::Run() has finished.
task_done_event_.Wait();
// Verify the background thread never started blocking.
return !blocking_task_.WasStarted();
}
class FakeObserver : public CancelationSignal::Observer {
public:
void OnCancelationSignalReceived() override {}
};
TEST(CancelationSignalTest_SingleThread, CheckFlags) {
FakeObserver observer;
CancelationSignal signal;
EXPECT_FALSE(signal.IsSignalled());
signal.Signal();
EXPECT_TRUE(signal.IsSignalled());
EXPECT_FALSE(signal.TryRegisterHandler(&observer));
}
// Send the cancelation signal before the task is started. This will ensure
// that the task will never be "started" (ie. TryRegisterHandler() will fail,
// so it will never start blocking on its main WaitableEvent).
TEST_F(CancelationSignalTest, CancelEarly) {
CancelBlocking();
StartBlockingTaskAsync();
EXPECT_TRUE(VerifyTaskNotStarted());
}
// Send the cancelation signal after the task has started running. This tests
// the non-early exit code path, where the task is stopped while it is in
// progress.
TEST_F(CancelationSignalTest, Cancel) {
StartBlockingTaskAndWaitForItToStart();
// Wait for the task to finish and let verify it has been started.
CancelBlocking();
EXPECT_FALSE(VerifyTaskNotStarted());
}
} // namespace syncer
|