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