1 // Copyright 2017 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 "base/test/scoped_task_environment.h"
6
7 #include "base/bind_helpers.h"
8 #include "base/logging.h"
9 #include "base/memory/ptr_util.h"
10 #include "base/message_loop/message_loop.h"
11 #include "base/run_loop.h"
12 #include "base/synchronization/condition_variable.h"
13 #include "base/synchronization/lock.h"
14 #include "base/task_scheduler/post_task.h"
15 #include "base/task_scheduler/task_scheduler.h"
16 #include "base/task_scheduler/task_scheduler_impl.h"
17 #include "base/test/test_mock_time_task_runner.h"
18 #include "base/threading/sequence_local_storage_map.h"
19 #include "base/threading/thread_restrictions.h"
20 #include "base/threading/thread_task_runner_handle.h"
21 #include "base/time/time.h"
22
23 #if defined(OS_POSIX)
24 #include "base/files/file_descriptor_watcher_posix.h"
25 #endif
26
27 namespace base {
28 namespace test {
29
30 namespace {
31
CreateMessageLoopForMainThreadType(ScopedTaskEnvironment::MainThreadType main_thread_type)32 std::unique_ptr<MessageLoop> CreateMessageLoopForMainThreadType(
33 ScopedTaskEnvironment::MainThreadType main_thread_type) {
34 switch (main_thread_type) {
35 case ScopedTaskEnvironment::MainThreadType::DEFAULT:
36 return std::make_unique<MessageLoop>(MessageLoop::TYPE_DEFAULT);
37 case ScopedTaskEnvironment::MainThreadType::MOCK_TIME:
38 return nullptr;
39 case ScopedTaskEnvironment::MainThreadType::UI:
40 return std::make_unique<MessageLoop>(MessageLoop::TYPE_UI);
41 case ScopedTaskEnvironment::MainThreadType::IO:
42 return std::make_unique<MessageLoop>(MessageLoop::TYPE_IO);
43 }
44 NOTREACHED();
45 return nullptr;
46 }
47
48 } // namespace
49
50 class ScopedTaskEnvironment::TestTaskTracker
51 : public internal::TaskSchedulerImpl::TaskTrackerImpl {
52 public:
53 TestTaskTracker();
54
55 // Allow running tasks.
56 void AllowRunTasks();
57
58 // Disallow running tasks. Returns true on success; success requires there to
59 // be no tasks currently running. Returns false if >0 tasks are currently
60 // running. Prior to returning false, it will attempt to block until at least
61 // one task has completed (in an attempt to avoid callers busy-looping
62 // DisallowRunTasks() calls with the same set of slowly ongoing tasks). This
63 // block attempt will also have a short timeout (in an attempt to prevent the
64 // fallout of blocking: if the only task remaining is blocked on the main
65 // thread, waiting for it to complete results in a deadlock...).
66 bool DisallowRunTasks();
67
68 private:
69 friend class ScopedTaskEnvironment;
70
71 // internal::TaskSchedulerImpl::TaskTrackerImpl:
72 void RunOrSkipTask(internal::Task task,
73 internal::Sequence* sequence,
74 bool can_run_task) override;
75
76 // Synchronizes accesses to members below.
77 Lock lock_;
78
79 // True if running tasks is allowed.
80 bool can_run_tasks_ = true;
81
82 // Signaled when |can_run_tasks_| becomes true.
83 ConditionVariable can_run_tasks_cv_;
84
85 // Signaled when a task is completed.
86 ConditionVariable task_completed_;
87
88 // Number of tasks that are currently running.
89 int num_tasks_running_ = 0;
90
91 DISALLOW_COPY_AND_ASSIGN(TestTaskTracker);
92 };
93
ScopedTaskEnvironment(MainThreadType main_thread_type,ExecutionMode execution_control_mode)94 ScopedTaskEnvironment::ScopedTaskEnvironment(
95 MainThreadType main_thread_type,
96 ExecutionMode execution_control_mode)
97 : execution_control_mode_(execution_control_mode),
98 message_loop_(CreateMessageLoopForMainThreadType(main_thread_type)),
99 mock_time_task_runner_(
100 main_thread_type == MainThreadType::MOCK_TIME
101 ? MakeRefCounted<TestMockTimeTaskRunner>(
102 TestMockTimeTaskRunner::Type::kBoundToThread)
103 : nullptr),
104 slsm_for_mock_time_(
105 main_thread_type == MainThreadType::MOCK_TIME
106 ? std::make_unique<internal::SequenceLocalStorageMap>()
107 : nullptr),
108 slsm_registration_for_mock_time_(
109 main_thread_type == MainThreadType::MOCK_TIME
110 ? std::make_unique<
111 internal::ScopedSetSequenceLocalStorageMapForCurrentThread>(
112 slsm_for_mock_time_.get())
113 : nullptr),
114 #if defined(OS_POSIX)
115 file_descriptor_watcher_(
116 main_thread_type == MainThreadType::IO
117 ? std::make_unique<FileDescriptorWatcher>(
118 static_cast<MessageLoopForIO*>(message_loop_.get()))
119 : nullptr),
120 #endif // defined(OS_POSIX)
121 task_tracker_(new TestTaskTracker()) {
122 CHECK(!TaskScheduler::GetInstance());
123
124 // Instantiate a TaskScheduler with 2 threads in each of its 4 pools. Threads
125 // stay alive even when they don't have work.
126 // Each pool uses two threads to prevent deadlocks in unit tests that have a
127 // sequence that uses WithBaseSyncPrimitives() to wait on the result of
128 // another sequence. This isn't perfect (doesn't solve wait chains) but solves
129 // the basic use case for now.
130 // TODO(fdoray/jeffreyhe): Make the TaskScheduler dynamically replace blocked
131 // threads and get rid of this limitation. http://crbug.com/738104
132 constexpr int kMaxThreads = 2;
133 const TimeDelta kSuggestedReclaimTime = TimeDelta::Max();
134 const SchedulerWorkerPoolParams worker_pool_params(kMaxThreads,
135 kSuggestedReclaimTime);
136 TaskScheduler::SetInstance(std::make_unique<internal::TaskSchedulerImpl>(
137 "ScopedTaskEnvironment", WrapUnique(task_tracker_)));
138 task_scheduler_ = TaskScheduler::GetInstance();
139 TaskScheduler::GetInstance()->Start({worker_pool_params, worker_pool_params,
140 worker_pool_params, worker_pool_params});
141
142 if (execution_control_mode_ == ExecutionMode::QUEUED)
143 CHECK(task_tracker_->DisallowRunTasks());
144 }
145
~ScopedTaskEnvironment()146 ScopedTaskEnvironment::~ScopedTaskEnvironment() {
147 // Ideally this would RunLoop().RunUntilIdle() here to catch any errors or
148 // infinite post loop in the remaining work but this isn't possible right now
149 // because base::~MessageLoop() didn't use to do this and adding it here would
150 // make the migration away from MessageLoop that much harder.
151 CHECK_EQ(TaskScheduler::GetInstance(), task_scheduler_);
152 // Without FlushForTesting(), DeleteSoon() and ReleaseSoon() tasks could be
153 // skipped, resulting in memory leaks.
154 task_tracker_->AllowRunTasks();
155 TaskScheduler::GetInstance()->FlushForTesting();
156 TaskScheduler::GetInstance()->Shutdown();
157 TaskScheduler::GetInstance()->JoinForTesting();
158 // Destroying TaskScheduler state can result in waiting on worker threads.
159 // Make sure this is allowed to avoid flaking tests that have disallowed waits
160 // on their main thread.
161 ScopedAllowBaseSyncPrimitivesForTesting allow_waits_to_destroy_task_tracker;
162 TaskScheduler::SetInstance(nullptr);
163 }
164
165 scoped_refptr<base::SingleThreadTaskRunner>
GetMainThreadTaskRunner()166 ScopedTaskEnvironment::GetMainThreadTaskRunner() {
167 if (message_loop_)
168 return message_loop_->task_runner();
169 DCHECK(mock_time_task_runner_);
170 return mock_time_task_runner_;
171 }
172
MainThreadHasPendingTask() const173 bool ScopedTaskEnvironment::MainThreadHasPendingTask() const {
174 if (message_loop_)
175 return !message_loop_->IsIdleForTesting();
176 DCHECK(mock_time_task_runner_);
177 return mock_time_task_runner_->HasPendingTask();
178 }
179
RunUntilIdle()180 void ScopedTaskEnvironment::RunUntilIdle() {
181 // TODO(gab): This can be heavily simplified to essentially:
182 // bool HasMainThreadTasks() {
183 // if (message_loop_)
184 // return !message_loop_->IsIdleForTesting();
185 // return mock_time_task_runner_->NextPendingTaskDelay().is_zero();
186 // }
187 // while (task_tracker_->HasIncompleteTasks() || HasMainThreadTasks()) {
188 // base::RunLoop().RunUntilIdle();
189 // // Avoid busy-looping.
190 // if (task_tracker_->HasIncompleteTasks())
191 // PlatformThread::Sleep(TimeDelta::FromMilliSeconds(1));
192 // }
193 // Challenge: HasMainThreadTasks() requires support for proper
194 // IncomingTaskQueue::IsIdleForTesting() (check all queues).
195 //
196 // Other than that it works because once |task_tracker_->HasIncompleteTasks()|
197 // is false we know for sure that the only thing that can make it true is a
198 // main thread task (ScopedTaskEnvironment owns all the threads). As such we
199 // can't racily see it as false on the main thread and be wrong as if it the
200 // main thread sees the atomic count at zero, it's the only one that can make
201 // it go up. And the only thing that can make it go up on the main thread are
202 // main thread tasks and therefore we're done if there aren't any left.
203 //
204 // This simplification further allows simplification of DisallowRunTasks().
205 //
206 // This can also be simplified even further once TaskTracker becomes directly
207 // aware of main thread tasks. https://crbug.com/660078.
208
209 for (;;) {
210 task_tracker_->AllowRunTasks();
211
212 // First run as many tasks as possible on the main thread in parallel with
213 // tasks in TaskScheduler. This increases likelihood of TSAN catching
214 // threading errors and eliminates possibility of hangs should a
215 // TaskScheduler task synchronously block on a main thread task
216 // (TaskScheduler::FlushForTesting() can't be used here for that reason).
217 RunLoop().RunUntilIdle();
218
219 // Then halt TaskScheduler. DisallowRunTasks() failing indicates that there
220 // were TaskScheduler tasks currently running. In that case, try again from
221 // top when DisallowRunTasks() yields control back to this thread as they
222 // may have posted main thread tasks.
223 if (!task_tracker_->DisallowRunTasks())
224 continue;
225
226 // Once TaskScheduler is halted. Run any remaining main thread tasks (which
227 // may have been posted by TaskScheduler tasks that completed between the
228 // above main thread RunUntilIdle() and TaskScheduler DisallowRunTasks()).
229 // Note: this assumes that no main thread task synchronously blocks on a
230 // TaskScheduler tasks (it certainly shouldn't); this call could otherwise
231 // hang.
232 RunLoop().RunUntilIdle();
233
234 // The above RunUntilIdle() guarantees there are no remaining main thread
235 // tasks (the TaskScheduler being halted during the last RunUntilIdle() is
236 // key as it prevents a task being posted to it racily with it determining
237 // it had no work remaining). Therefore, we're done if there is no more work
238 // on TaskScheduler either (there can be TaskScheduler work remaining if
239 // DisallowRunTasks() preempted work and/or the last RunUntilIdle() posted
240 // more TaskScheduler tasks).
241 // Note: this last |if| couldn't be turned into a |do {} while();|. A
242 // conditional loop makes it such that |continue;| results in checking the
243 // condition (not unconditionally loop again) which would be incorrect for
244 // the above logic as it'd then be possible for a TaskScheduler task to be
245 // running during the DisallowRunTasks() test, causing it to fail, but then
246 // post to the main thread and complete before the loop's condition is
247 // verified which could result in HasIncompleteUndelayedTasksForTesting()
248 // returning false and the loop erroneously exiting with a pending task on
249 // the main thread.
250 if (!task_tracker_->HasIncompleteUndelayedTasksForTesting())
251 break;
252 }
253
254 // The above loop always ends with running tasks being disallowed. Re-enable
255 // parallel execution before returning unless in ExecutionMode::QUEUED.
256 if (execution_control_mode_ != ExecutionMode::QUEUED)
257 task_tracker_->AllowRunTasks();
258 }
259
FastForwardBy(TimeDelta delta)260 void ScopedTaskEnvironment::FastForwardBy(TimeDelta delta) {
261 DCHECK(mock_time_task_runner_);
262 mock_time_task_runner_->FastForwardBy(delta);
263 }
264
FastForwardUntilNoTasksRemain()265 void ScopedTaskEnvironment::FastForwardUntilNoTasksRemain() {
266 DCHECK(mock_time_task_runner_);
267 mock_time_task_runner_->FastForwardUntilNoTasksRemain();
268 }
269
GetMockTickClock()270 const TickClock* ScopedTaskEnvironment::GetMockTickClock() {
271 DCHECK(mock_time_task_runner_);
272 return mock_time_task_runner_->GetMockTickClock();
273 }
274
DeprecatedGetMockTickClock()275 std::unique_ptr<TickClock> ScopedTaskEnvironment::DeprecatedGetMockTickClock() {
276 DCHECK(mock_time_task_runner_);
277 return mock_time_task_runner_->DeprecatedGetMockTickClock();
278 }
279
GetPendingMainThreadTaskCount() const280 size_t ScopedTaskEnvironment::GetPendingMainThreadTaskCount() const {
281 DCHECK(mock_time_task_runner_);
282 return mock_time_task_runner_->GetPendingTaskCount();
283 }
284
NextMainThreadPendingTaskDelay() const285 TimeDelta ScopedTaskEnvironment::NextMainThreadPendingTaskDelay() const {
286 DCHECK(mock_time_task_runner_);
287 return mock_time_task_runner_->NextPendingTaskDelay();
288 }
289
TestTaskTracker()290 ScopedTaskEnvironment::TestTaskTracker::TestTaskTracker()
291 : internal::TaskSchedulerImpl::TaskTrackerImpl("ScopedTaskEnvironment"),
292 can_run_tasks_cv_(&lock_),
293 task_completed_(&lock_) {}
294
AllowRunTasks()295 void ScopedTaskEnvironment::TestTaskTracker::AllowRunTasks() {
296 AutoLock auto_lock(lock_);
297 can_run_tasks_ = true;
298 can_run_tasks_cv_.Broadcast();
299 }
300
DisallowRunTasks()301 bool ScopedTaskEnvironment::TestTaskTracker::DisallowRunTasks() {
302 AutoLock auto_lock(lock_);
303
304 // Can't disallow run task if there are tasks running.
305 if (num_tasks_running_ > 0) {
306 // Attempt to wait a bit so that the caller doesn't busy-loop with the same
307 // set of pending work. A short wait is required to avoid deadlock
308 // scenarios. See DisallowRunTasks()'s declaration for more details.
309 task_completed_.TimedWait(TimeDelta::FromMilliseconds(1));
310 return false;
311 }
312
313 can_run_tasks_ = false;
314 return true;
315 }
316
RunOrSkipTask(internal::Task task,internal::Sequence * sequence,bool can_run_task)317 void ScopedTaskEnvironment::TestTaskTracker::RunOrSkipTask(
318 internal::Task task,
319 internal::Sequence* sequence,
320 bool can_run_task) {
321 {
322 AutoLock auto_lock(lock_);
323
324 while (!can_run_tasks_)
325 can_run_tasks_cv_.Wait();
326
327 ++num_tasks_running_;
328 }
329
330 internal::TaskSchedulerImpl::TaskTrackerImpl::RunOrSkipTask(
331 std::move(task), sequence, can_run_task);
332
333 {
334 AutoLock auto_lock(lock_);
335
336 CHECK_GT(num_tasks_running_, 0);
337 CHECK(can_run_tasks_);
338
339 --num_tasks_running_;
340
341 task_completed_.Broadcast();
342 }
343 }
344
345 } // namespace test
346 } // namespace base
347