File: cancelation_signal_unittest.cc

package info (click to toggle)
chromium 138.0.7204.183-1~deb12u1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm-proposed-updates
  • size: 6,080,960 kB
  • sloc: cpp: 34,937,079; ansic: 7,176,967; javascript: 4,110,704; python: 1,419,954; asm: 946,768; xml: 739,971; pascal: 187,324; sh: 89,623; perl: 88,663; objc: 79,944; sql: 50,304; cs: 41,786; fortran: 24,137; makefile: 21,811; php: 13,980; tcl: 13,166; yacc: 8,925; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (183 lines) | stat: -rw-r--r-- 5,949 bytes parent folder | download | duplicates (6)
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