• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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