You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
347 lines
14 KiB
347 lines
14 KiB
// Copyright 2017 The Chromium Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include "base/test/scoped_task_environment.h"
|
|
|
|
#include "base/bind_helpers.h"
|
|
#include "base/logging.h"
|
|
#include "base/memory/ptr_util.h"
|
|
#include "base/message_loop/message_loop.h"
|
|
#include "base/run_loop.h"
|
|
#include "base/synchronization/condition_variable.h"
|
|
#include "base/synchronization/lock.h"
|
|
#include "base/task_scheduler/post_task.h"
|
|
#include "base/task_scheduler/task_scheduler.h"
|
|
#include "base/task_scheduler/task_scheduler_impl.h"
|
|
#include "base/test/test_mock_time_task_runner.h"
|
|
#include "base/threading/sequence_local_storage_map.h"
|
|
#include "base/threading/thread_restrictions.h"
|
|
#include "base/threading/thread_task_runner_handle.h"
|
|
#include "base/time/time.h"
|
|
|
|
#if defined(OS_POSIX)
|
|
#include "base/files/file_descriptor_watcher_posix.h"
|
|
#endif
|
|
|
|
namespace base {
|
|
namespace test {
|
|
|
|
namespace {
|
|
|
|
std::unique_ptr<MessageLoop> CreateMessageLoopForMainThreadType(
|
|
ScopedTaskEnvironment::MainThreadType main_thread_type) {
|
|
switch (main_thread_type) {
|
|
case ScopedTaskEnvironment::MainThreadType::DEFAULT:
|
|
return std::make_unique<MessageLoop>(MessageLoop::TYPE_DEFAULT);
|
|
case ScopedTaskEnvironment::MainThreadType::MOCK_TIME:
|
|
return nullptr;
|
|
case ScopedTaskEnvironment::MainThreadType::UI:
|
|
return std::make_unique<MessageLoop>(MessageLoop::TYPE_UI);
|
|
case ScopedTaskEnvironment::MainThreadType::IO:
|
|
return std::make_unique<MessageLoop>(MessageLoop::TYPE_IO);
|
|
}
|
|
NOTREACHED();
|
|
return nullptr;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
class ScopedTaskEnvironment::TestTaskTracker
|
|
: public internal::TaskSchedulerImpl::TaskTrackerImpl {
|
|
public:
|
|
TestTaskTracker();
|
|
|
|
// Allow running tasks.
|
|
void AllowRunTasks();
|
|
|
|
// Disallow running tasks. Returns true on success; success requires there to
|
|
// be no tasks currently running. Returns false if >0 tasks are currently
|
|
// running. Prior to returning false, it will attempt to block until at least
|
|
// one task has completed (in an attempt to avoid callers busy-looping
|
|
// DisallowRunTasks() calls with the same set of slowly ongoing tasks). This
|
|
// block attempt will also have a short timeout (in an attempt to prevent the
|
|
// fallout of blocking: if the only task remaining is blocked on the main
|
|
// thread, waiting for it to complete results in a deadlock...).
|
|
bool DisallowRunTasks();
|
|
|
|
private:
|
|
friend class ScopedTaskEnvironment;
|
|
|
|
// internal::TaskSchedulerImpl::TaskTrackerImpl:
|
|
void RunOrSkipTask(internal::Task task,
|
|
internal::Sequence* sequence,
|
|
bool can_run_task) override;
|
|
|
|
// Synchronizes accesses to members below.
|
|
Lock lock_;
|
|
|
|
// True if running tasks is allowed.
|
|
bool can_run_tasks_ = true;
|
|
|
|
// Signaled when |can_run_tasks_| becomes true.
|
|
ConditionVariable can_run_tasks_cv_;
|
|
|
|
// Signaled when a task is completed.
|
|
ConditionVariable task_completed_;
|
|
|
|
// Number of tasks that are currently running.
|
|
int num_tasks_running_ = 0;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(TestTaskTracker);
|
|
};
|
|
|
|
ScopedTaskEnvironment::ScopedTaskEnvironment(
|
|
MainThreadType main_thread_type,
|
|
ExecutionMode execution_control_mode)
|
|
: execution_control_mode_(execution_control_mode),
|
|
message_loop_(CreateMessageLoopForMainThreadType(main_thread_type)),
|
|
mock_time_task_runner_(
|
|
main_thread_type == MainThreadType::MOCK_TIME
|
|
? MakeRefCounted<TestMockTimeTaskRunner>(
|
|
TestMockTimeTaskRunner::Type::kBoundToThread)
|
|
: nullptr),
|
|
slsm_for_mock_time_(
|
|
main_thread_type == MainThreadType::MOCK_TIME
|
|
? std::make_unique<internal::SequenceLocalStorageMap>()
|
|
: nullptr),
|
|
slsm_registration_for_mock_time_(
|
|
main_thread_type == MainThreadType::MOCK_TIME
|
|
? std::make_unique<
|
|
internal::ScopedSetSequenceLocalStorageMapForCurrentThread>(
|
|
slsm_for_mock_time_.get())
|
|
: nullptr),
|
|
#if defined(OS_POSIX)
|
|
file_descriptor_watcher_(
|
|
main_thread_type == MainThreadType::IO
|
|
? std::make_unique<FileDescriptorWatcher>(
|
|
static_cast<MessageLoopForIO*>(message_loop_.get()))
|
|
: nullptr),
|
|
#endif // defined(OS_POSIX)
|
|
task_tracker_(new TestTaskTracker()) {
|
|
CHECK(!TaskScheduler::GetInstance());
|
|
|
|
// Instantiate a TaskScheduler with 2 threads in each of its 4 pools. Threads
|
|
// stay alive even when they don't have work.
|
|
// Each pool uses two threads to prevent deadlocks in unit tests that have a
|
|
// sequence that uses WithBaseSyncPrimitives() to wait on the result of
|
|
// another sequence. This isn't perfect (doesn't solve wait chains) but solves
|
|
// the basic use case for now.
|
|
// TODO(fdoray/jeffreyhe): Make the TaskScheduler dynamically replace blocked
|
|
// threads and get rid of this limitation. http://crbug.com/738104
|
|
constexpr int kMaxThreads = 2;
|
|
const TimeDelta kSuggestedReclaimTime = TimeDelta::Max();
|
|
const SchedulerWorkerPoolParams worker_pool_params(kMaxThreads,
|
|
kSuggestedReclaimTime);
|
|
TaskScheduler::SetInstance(std::make_unique<internal::TaskSchedulerImpl>(
|
|
"ScopedTaskEnvironment", WrapUnique(task_tracker_)));
|
|
task_scheduler_ = TaskScheduler::GetInstance();
|
|
TaskScheduler::GetInstance()->Start({worker_pool_params, worker_pool_params,
|
|
worker_pool_params, worker_pool_params});
|
|
|
|
if (execution_control_mode_ == ExecutionMode::QUEUED)
|
|
CHECK(task_tracker_->DisallowRunTasks());
|
|
}
|
|
|
|
ScopedTaskEnvironment::~ScopedTaskEnvironment() {
|
|
// Ideally this would RunLoop().RunUntilIdle() here to catch any errors or
|
|
// infinite post loop in the remaining work but this isn't possible right now
|
|
// because base::~MessageLoop() didn't use to do this and adding it here would
|
|
// make the migration away from MessageLoop that much harder.
|
|
CHECK_EQ(TaskScheduler::GetInstance(), task_scheduler_);
|
|
// Without FlushForTesting(), DeleteSoon() and ReleaseSoon() tasks could be
|
|
// skipped, resulting in memory leaks.
|
|
task_tracker_->AllowRunTasks();
|
|
TaskScheduler::GetInstance()->FlushForTesting();
|
|
TaskScheduler::GetInstance()->Shutdown();
|
|
TaskScheduler::GetInstance()->JoinForTesting();
|
|
// Destroying TaskScheduler state can result in waiting on worker threads.
|
|
// Make sure this is allowed to avoid flaking tests that have disallowed waits
|
|
// on their main thread.
|
|
ScopedAllowBaseSyncPrimitivesForTesting allow_waits_to_destroy_task_tracker;
|
|
TaskScheduler::SetInstance(nullptr);
|
|
}
|
|
|
|
scoped_refptr<base::SingleThreadTaskRunner>
|
|
ScopedTaskEnvironment::GetMainThreadTaskRunner() {
|
|
if (message_loop_)
|
|
return message_loop_->task_runner();
|
|
DCHECK(mock_time_task_runner_);
|
|
return mock_time_task_runner_;
|
|
}
|
|
|
|
bool ScopedTaskEnvironment::MainThreadHasPendingTask() const {
|
|
if (message_loop_)
|
|
return !message_loop_->IsIdleForTesting();
|
|
DCHECK(mock_time_task_runner_);
|
|
return mock_time_task_runner_->HasPendingTask();
|
|
}
|
|
|
|
void ScopedTaskEnvironment::RunUntilIdle() {
|
|
// TODO(gab): This can be heavily simplified to essentially:
|
|
// bool HasMainThreadTasks() {
|
|
// if (message_loop_)
|
|
// return !message_loop_->IsIdleForTesting();
|
|
// return mock_time_task_runner_->NextPendingTaskDelay().is_zero();
|
|
// }
|
|
// while (task_tracker_->HasIncompleteTasks() || HasMainThreadTasks()) {
|
|
// base::RunLoop().RunUntilIdle();
|
|
// // Avoid busy-looping.
|
|
// if (task_tracker_->HasIncompleteTasks())
|
|
// PlatformThread::Sleep(TimeDelta::FromMilliSeconds(1));
|
|
// }
|
|
// Challenge: HasMainThreadTasks() requires support for proper
|
|
// IncomingTaskQueue::IsIdleForTesting() (check all queues).
|
|
//
|
|
// Other than that it works because once |task_tracker_->HasIncompleteTasks()|
|
|
// is false we know for sure that the only thing that can make it true is a
|
|
// main thread task (ScopedTaskEnvironment owns all the threads). As such we
|
|
// can't racily see it as false on the main thread and be wrong as if it the
|
|
// main thread sees the atomic count at zero, it's the only one that can make
|
|
// it go up. And the only thing that can make it go up on the main thread are
|
|
// main thread tasks and therefore we're done if there aren't any left.
|
|
//
|
|
// This simplification further allows simplification of DisallowRunTasks().
|
|
//
|
|
// This can also be simplified even further once TaskTracker becomes directly
|
|
// aware of main thread tasks. https://crbug.com/660078.
|
|
|
|
for (;;) {
|
|
task_tracker_->AllowRunTasks();
|
|
|
|
// First run as many tasks as possible on the main thread in parallel with
|
|
// tasks in TaskScheduler. This increases likelihood of TSAN catching
|
|
// threading errors and eliminates possibility of hangs should a
|
|
// TaskScheduler task synchronously block on a main thread task
|
|
// (TaskScheduler::FlushForTesting() can't be used here for that reason).
|
|
RunLoop().RunUntilIdle();
|
|
|
|
// Then halt TaskScheduler. DisallowRunTasks() failing indicates that there
|
|
// were TaskScheduler tasks currently running. In that case, try again from
|
|
// top when DisallowRunTasks() yields control back to this thread as they
|
|
// may have posted main thread tasks.
|
|
if (!task_tracker_->DisallowRunTasks())
|
|
continue;
|
|
|
|
// Once TaskScheduler is halted. Run any remaining main thread tasks (which
|
|
// may have been posted by TaskScheduler tasks that completed between the
|
|
// above main thread RunUntilIdle() and TaskScheduler DisallowRunTasks()).
|
|
// Note: this assumes that no main thread task synchronously blocks on a
|
|
// TaskScheduler tasks (it certainly shouldn't); this call could otherwise
|
|
// hang.
|
|
RunLoop().RunUntilIdle();
|
|
|
|
// The above RunUntilIdle() guarantees there are no remaining main thread
|
|
// tasks (the TaskScheduler being halted during the last RunUntilIdle() is
|
|
// key as it prevents a task being posted to it racily with it determining
|
|
// it had no work remaining). Therefore, we're done if there is no more work
|
|
// on TaskScheduler either (there can be TaskScheduler work remaining if
|
|
// DisallowRunTasks() preempted work and/or the last RunUntilIdle() posted
|
|
// more TaskScheduler tasks).
|
|
// Note: this last |if| couldn't be turned into a |do {} while();|. A
|
|
// conditional loop makes it such that |continue;| results in checking the
|
|
// condition (not unconditionally loop again) which would be incorrect for
|
|
// the above logic as it'd then be possible for a TaskScheduler task to be
|
|
// running during the DisallowRunTasks() test, causing it to fail, but then
|
|
// post to the main thread and complete before the loop's condition is
|
|
// verified which could result in HasIncompleteUndelayedTasksForTesting()
|
|
// returning false and the loop erroneously exiting with a pending task on
|
|
// the main thread.
|
|
if (!task_tracker_->HasIncompleteUndelayedTasksForTesting())
|
|
break;
|
|
}
|
|
|
|
// The above loop always ends with running tasks being disallowed. Re-enable
|
|
// parallel execution before returning unless in ExecutionMode::QUEUED.
|
|
if (execution_control_mode_ != ExecutionMode::QUEUED)
|
|
task_tracker_->AllowRunTasks();
|
|
}
|
|
|
|
void ScopedTaskEnvironment::FastForwardBy(TimeDelta delta) {
|
|
DCHECK(mock_time_task_runner_);
|
|
mock_time_task_runner_->FastForwardBy(delta);
|
|
}
|
|
|
|
void ScopedTaskEnvironment::FastForwardUntilNoTasksRemain() {
|
|
DCHECK(mock_time_task_runner_);
|
|
mock_time_task_runner_->FastForwardUntilNoTasksRemain();
|
|
}
|
|
|
|
const TickClock* ScopedTaskEnvironment::GetMockTickClock() {
|
|
DCHECK(mock_time_task_runner_);
|
|
return mock_time_task_runner_->GetMockTickClock();
|
|
}
|
|
|
|
std::unique_ptr<TickClock> ScopedTaskEnvironment::DeprecatedGetMockTickClock() {
|
|
DCHECK(mock_time_task_runner_);
|
|
return mock_time_task_runner_->DeprecatedGetMockTickClock();
|
|
}
|
|
|
|
size_t ScopedTaskEnvironment::GetPendingMainThreadTaskCount() const {
|
|
DCHECK(mock_time_task_runner_);
|
|
return mock_time_task_runner_->GetPendingTaskCount();
|
|
}
|
|
|
|
TimeDelta ScopedTaskEnvironment::NextMainThreadPendingTaskDelay() const {
|
|
DCHECK(mock_time_task_runner_);
|
|
return mock_time_task_runner_->NextPendingTaskDelay();
|
|
}
|
|
|
|
ScopedTaskEnvironment::TestTaskTracker::TestTaskTracker()
|
|
: internal::TaskSchedulerImpl::TaskTrackerImpl("ScopedTaskEnvironment"),
|
|
can_run_tasks_cv_(&lock_),
|
|
task_completed_(&lock_) {}
|
|
|
|
void ScopedTaskEnvironment::TestTaskTracker::AllowRunTasks() {
|
|
AutoLock auto_lock(lock_);
|
|
can_run_tasks_ = true;
|
|
can_run_tasks_cv_.Broadcast();
|
|
}
|
|
|
|
bool ScopedTaskEnvironment::TestTaskTracker::DisallowRunTasks() {
|
|
AutoLock auto_lock(lock_);
|
|
|
|
// Can't disallow run task if there are tasks running.
|
|
if (num_tasks_running_ > 0) {
|
|
// Attempt to wait a bit so that the caller doesn't busy-loop with the same
|
|
// set of pending work. A short wait is required to avoid deadlock
|
|
// scenarios. See DisallowRunTasks()'s declaration for more details.
|
|
task_completed_.TimedWait(TimeDelta::FromMilliseconds(1));
|
|
return false;
|
|
}
|
|
|
|
can_run_tasks_ = false;
|
|
return true;
|
|
}
|
|
|
|
void ScopedTaskEnvironment::TestTaskTracker::RunOrSkipTask(
|
|
internal::Task task,
|
|
internal::Sequence* sequence,
|
|
bool can_run_task) {
|
|
{
|
|
AutoLock auto_lock(lock_);
|
|
|
|
while (!can_run_tasks_)
|
|
can_run_tasks_cv_.Wait();
|
|
|
|
++num_tasks_running_;
|
|
}
|
|
|
|
internal::TaskSchedulerImpl::TaskTrackerImpl::RunOrSkipTask(
|
|
std::move(task), sequence, can_run_task);
|
|
|
|
{
|
|
AutoLock auto_lock(lock_);
|
|
|
|
CHECK_GT(num_tasks_running_, 0);
|
|
CHECK(can_run_tasks_);
|
|
|
|
--num_tasks_running_;
|
|
|
|
task_completed_.Broadcast();
|
|
}
|
|
}
|
|
|
|
} // namespace test
|
|
} // namespace base
|