[Reland #3] Use the SequenceManager in ScopedTaskEnvironment
A reland of https://crrev.com/c/1324391
This is necessary because we want content::TestBrowserThreadBundle to
own a BrowserUIThreadScheduler, but that also owns a ScopedTaskEnvironment
and you can't have two SequenceManagers on the same thread.
This patch allows ScopedTaskEnvironment to optionally work with an
externally owned SequenceManager solving the problem.
This implements https://docs.google.com/document/d/1y08C6JQ9Yta3EQXzwIqqIIKHq9500WV6CWFZzZfDx7I/edit?usp=drivesdk,
We now have the ability to mock time on the UI and IO threads.
RE HttpServiceTest change HttpServiceTest.MultipleRequests was failing
mysteriously on fuschia on the bots only (not locally). IO_MOCK_TIME seems
to have fixed this.
TBR=gab@chromium.org,fdoray@chromium.org,wez@chromium.org
Bug: 863341, 891670, 708584
Change-Id: I95444a0a50f8b577c5fae62c12c8423c7e8f21d6
Reviewed-on: https://chromium-review.googlesource.com/c/1361863
Commit-Queue: Alex Clarke <alexclarke@chromium.org>
Reviewed-by: Alex Clarke <alexclarke@chromium.org>
Reviewed-by: Sami Kyöstilä <skyostil@chromium.org>
Cr-Commit-Position: refs/heads/master@{#615496}
diff --git a/base/test/scoped_task_environment.cc b/base/test/scoped_task_environment.cc
index b3c81c7..333956f3 100644
--- a/base/test/scoped_task_environment.cc
+++ b/base/test/scoped_task_environment.cc
@@ -13,6 +13,8 @@
#include "base/synchronization/condition_variable.h"
#include "base/synchronization/lock.h"
#include "base/task/post_task.h"
+#include "base/task/sequence_manager/sequence_manager_impl.h"
+#include "base/task/sequence_manager/time_domain.h"
#include "base/task/task_scheduler/task_scheduler.h"
#include "base/task/task_scheduler/task_scheduler_impl.h"
#include "base/test/test_mock_time_task_runner.h"
@@ -36,20 +38,35 @@
LazyInstance<ThreadLocalPointer<ScopedTaskEnvironment::LifetimeObserver>>::Leaky
environment_lifetime_observer;
-std::unique_ptr<MessageLoop> CreateMessageLoopForMainThreadType(
+base::Optional<MessageLoop::Type> GetMessageLoopTypeForMainThreadType(
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;
+ return MessageLoop::TYPE_DEFAULT;
case ScopedTaskEnvironment::MainThreadType::UI:
- return std::make_unique<MessageLoop>(MessageLoop::TYPE_UI);
+ case ScopedTaskEnvironment::MainThreadType::UI_MOCK_TIME:
+ return MessageLoop::TYPE_UI;
case ScopedTaskEnvironment::MainThreadType::IO:
- return std::make_unique<MessageLoop>(MessageLoop::TYPE_IO);
+ case ScopedTaskEnvironment::MainThreadType::IO_MOCK_TIME:
+ return MessageLoop::TYPE_IO;
}
NOTREACHED();
- return nullptr;
+ return base::nullopt;
+}
+
+std::unique_ptr<sequence_manager::SequenceManager>
+CreateSequenceManagerForMainThreadType(
+ ScopedTaskEnvironment::MainThreadType main_thread_type) {
+ auto type = GetMessageLoopTypeForMainThreadType(main_thread_type);
+ if (!type) {
+ return nullptr;
+ } else {
+ auto settings = base::sequence_manager::SequenceManager::Settings{
+ .message_loop_type = *type};
+ return sequence_manager::CreateSequenceManagerOnCurrentThreadWithPump(
+ MessageLoop::CreateMessagePumpForType(*type), std::move(settings));
+ }
}
class TickClockBasedClock : public Clock {
@@ -71,6 +88,135 @@
} // namespace
+class ScopedTaskEnvironment::MockTimeDomain
+ : public sequence_manager::TimeDomain,
+ public TickClock {
+ public:
+ MockTimeDomain() = default;
+ ~MockTimeDomain() override = default;
+
+ using TimeDomain::NextScheduledRunTime;
+
+ static std::unique_ptr<ScopedTaskEnvironment::MockTimeDomain> Create(
+ ScopedTaskEnvironment::MainThreadType main_thread_type) {
+ if (main_thread_type == MainThreadType::MOCK_TIME ||
+ main_thread_type == MainThreadType::UI_MOCK_TIME ||
+ main_thread_type == MainThreadType::IO_MOCK_TIME) {
+ return std::make_unique<ScopedTaskEnvironment::MockTimeDomain>();
+ }
+ return nullptr;
+ }
+
+ // sequence_manager::TimeDomain:
+
+ sequence_manager::LazyNow CreateLazyNow() const override {
+ base::AutoLock lock(now_ticks_lock_);
+ return sequence_manager::LazyNow(now_ticks_);
+ }
+
+ TimeTicks Now() const override {
+ // This can be called from any thread.
+ base::AutoLock lock(now_ticks_lock_);
+ return now_ticks_;
+ }
+
+ Optional<TimeDelta> DelayTillNextTask(
+ sequence_manager::LazyNow* lazy_now) override {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ // Make sure TimeDomain::NextScheduledRunTime has taken canceled tasks into
+ // account, ReclaimMemory sweeps canceled delayed tasks.
+ sequence_manager()->ReclaimMemory();
+ Optional<TimeTicks> run_time = NextScheduledRunTime();
+ // Check if we've run out of tasks.
+ if (!run_time)
+ return base::nullopt;
+
+ // Check if we have a task that should be running now.
+ if (run_time <= now_ticks_)
+ return base::TimeDelta();
+
+ // The next task is a future delayed task. Since we're using mock time, we
+ // don't want an actual OS level delayed wake up scheduled, so pretend we
+ // have no more work. This will result in MaybeFastForwardToNextTask getting
+ // called which lets us advance |now_ticks_|.
+ return base::nullopt;
+ }
+
+ // This method is called when the underlying message pump has run out of
+ // non-delayed work.
+ bool MaybeFastForwardToNextTask(bool quit_when_idle_requested) override {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ // If we're being externally controlled by a RunLoop in client code, check
+ // if the RunLoop is due to quit when idle, if so we don't want to advance
+ // mock time.
+ if (stop_when_message_pump_is_idle_ && quit_when_idle_requested)
+ return false;
+
+ // We don't need to call ReclaimMemory here because
+ // DelayTillNextTask will have dealt with cancelled delayed tasks for us.
+ Optional<TimeTicks> run_time = NextScheduledRunTime();
+ if (!run_time) {
+ // We've run out of tasks, but ScopedTaskEnvironment::FastForwardBy
+ // requires the virtual time to be consumed.
+ if (now_ticks_ < allow_advance_until_ && !allow_advance_until_.is_max())
+ SetTime(allow_advance_until_);
+ return false;
+ }
+
+ // Don't advance past |allow_advance_until_|.
+ DCHECK_GT(*run_time, now_ticks_);
+ TimeTicks time_to_advance_to = std::min(allow_advance_until_, *run_time);
+ if (time_to_advance_to <= now_ticks_)
+ return false;
+
+ SetTime(time_to_advance_to);
+
+ // Make sure a DoWork is scheduled.
+ return true;
+ }
+
+ const char* GetName() const override { return "MockTimeDomain"; }
+
+ // TickClock implementation:
+ TimeTicks NowTicks() const override { return Now(); }
+
+ // Allows time to advance when reaching idle, until
+ // |now_ticks_ == advance_until|. No-op if |advance_until <= now_ticks_|.
+ // Doesn't schedule work by itself.
+ void SetAllowTimeToAutoAdvanceUntil(TimeTicks advance_until) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ allow_advance_until_ = advance_until;
+ }
+
+ void SetStopWhenMessagePumpIsIdle(bool stop_when_message_pump_is_idle) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ stop_when_message_pump_is_idle_ = stop_when_message_pump_is_idle;
+ }
+
+ private:
+ void SetTime(TimeTicks time) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK_LE(time, allow_advance_until_);
+
+ base::AutoLock lock(now_ticks_lock_);
+ now_ticks_ = time;
+ }
+
+ SEQUENCE_CHECKER(sequence_checker_);
+
+ // By default we want RunLoop.Run() to advance virtual time due to the API
+ // contract.
+ TimeTicks allow_advance_until_ = TimeTicks::Max();
+ bool stop_when_message_pump_is_idle_ = true;
+
+ // Protects |now_ticks_|
+ mutable Lock now_ticks_lock_;
+
+ // Only ever written to from the main sequence.
+ TimeTicks now_ticks_;
+};
+
class ScopedTaskEnvironment::TestTaskTracker
: public internal::TaskSchedulerImpl::TaskTrackerImpl {
public:
@@ -119,31 +265,40 @@
ScopedTaskEnvironment::ScopedTaskEnvironment(
MainThreadType main_thread_type,
ExecutionMode execution_control_mode)
+ : ScopedTaskEnvironment(
+ CreateSequenceManagerForMainThreadType(main_thread_type),
+ nullptr,
+ main_thread_type,
+ execution_control_mode) {}
+
+ScopedTaskEnvironment::ScopedTaskEnvironment(
+ sequence_manager::SequenceManager* sequence_manager,
+ MainThreadType main_thread_type,
+ ExecutionMode execution_control_mode)
+ : ScopedTaskEnvironment(nullptr,
+ sequence_manager,
+ main_thread_type,
+ execution_control_mode) {}
+
+ScopedTaskEnvironment::ScopedTaskEnvironment(
+ std::unique_ptr<sequence_manager::SequenceManager> owned_sequence_manager,
+ sequence_manager::SequenceManager* sequence_manager,
+ 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),
- mock_clock_(mock_time_task_runner_
- ? std::make_unique<TickClockBasedClock>(
- mock_time_task_runner_->GetMockTickClock())
- : nullptr),
+ mock_time_domain_(MockTimeDomain::Create(main_thread_type)),
+ owned_sequence_manager_(std::move(owned_sequence_manager)),
+ sequence_manager_(owned_sequence_manager_.get()
+ ? owned_sequence_manager_.get()
+ : sequence_manager),
+ task_queue_(CreateDefaultTaskQueue()),
+ mock_clock_(mock_time_domain_ ? std::make_unique<TickClockBasedClock>(
+ mock_time_domain_.get())
+ : nullptr),
#if defined(OS_POSIX) || defined(OS_FUCHSIA)
file_descriptor_watcher_(main_thread_type == MainThreadType::IO
? std::make_unique<FileDescriptorWatcher>(
- message_loop_->task_runner())
+ task_queue_->task_runner())
: nullptr),
#endif // defined(OS_POSIX) || defined(OS_FUCHSIA)
task_tracker_(new TestTaskTracker()) {
@@ -154,6 +309,8 @@
"someone has explicitly disabled it with "
"DisableCheckForLeakedGlobals().";
+ sequence_manager_->SetDefaultTaskRunner(task_queue_->task_runner());
+
// 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
@@ -220,6 +377,20 @@
LifetimeObserver* observer = environment_lifetime_observer.Get().Get();
if (observer)
observer->OnScopedTaskEnvironmentDestroyed();
+
+ task_queue_ = nullptr;
+ if (mock_time_domain_)
+ sequence_manager_->UnregisterTimeDomain(mock_time_domain_.get());
+}
+
+scoped_refptr<sequence_manager::TaskQueue>
+ScopedTaskEnvironment::CreateDefaultTaskQueue() {
+ if (mock_time_domain_)
+ sequence_manager_->RegisterTimeDomain(mock_time_domain_.get());
+
+ return sequence_manager_->CreateTaskQueue(
+ sequence_manager::TaskQueue::Spec("scoped_task_environment_default")
+ .SetTimeDomain(mock_time_domain_.get()));
}
void ScopedTaskEnvironment::SetLifetimeObserver(
@@ -230,20 +401,29 @@
scoped_refptr<base::SingleThreadTaskRunner>
ScopedTaskEnvironment::GetMainThreadTaskRunner() {
- if (message_loop_)
- return message_loop_->task_runner();
- DCHECK(mock_time_task_runner_);
- return mock_time_task_runner_;
+ return task_queue_->task_runner();
}
bool ScopedTaskEnvironment::MainThreadHasPendingTask() const {
- if (message_loop_)
- return !message_loop_->IsIdleForTesting();
- DCHECK(mock_time_task_runner_);
- return mock_time_task_runner_->HasPendingTask();
+ sequence_manager::internal::SequenceManagerImpl* sequence_manager_impl =
+ static_cast<sequence_manager::internal::SequenceManagerImpl*>(
+ sequence_manager_);
+ // ReclaimMemory sweeps canceled delayed tasks.
+ sequence_manager_impl->ReclaimMemory();
+ // Unfortunately this API means different things depending on whether mock
+ // time is used or not. If MockTime is used then tests want to know if there
+ // are any delayed or non-delayed tasks, otherwise only non-delayed tasks are
+ // considered.
+ if (mock_time_domain_)
+ return sequence_manager_impl->HasTasks();
+ return !sequence_manager_impl->IsIdleForTesting();
}
void ScopedTaskEnvironment::RunUntilIdle() {
+ // Prevent virtual time from advancing while within this call.
+ if (mock_time_domain_)
+ mock_time_domain_->SetAllowTimeToAutoAdvanceUntil(TimeTicks());
+
// TODO(gab): This can be heavily simplified to essentially:
// bool HasMainThreadTasks() {
// if (message_loop_)
@@ -321,31 +501,36 @@
// parallel execution before returning unless in ExecutionMode::QUEUED.
if (execution_control_mode_ != ExecutionMode::QUEUED)
task_tracker_->AllowRunTasks();
+
+ if (mock_time_domain_)
+ mock_time_domain_->SetAllowTimeToAutoAdvanceUntil(TimeTicks::Max());
}
void ScopedTaskEnvironment::FastForwardBy(TimeDelta delta) {
- DCHECK(mock_time_task_runner_);
- mock_time_task_runner_->FastForwardBy(delta);
+ MessageLoopCurrent::ScopedNestableTaskAllower allow;
+ DCHECK(mock_time_domain_);
+ mock_time_domain_->SetStopWhenMessagePumpIsIdle(false);
+ mock_time_domain_->SetAllowTimeToAutoAdvanceUntil(mock_time_domain_->Now() +
+ delta);
+ RunLoop().RunUntilIdle();
+ mock_time_domain_->SetStopWhenMessagePumpIsIdle(true);
+ mock_time_domain_->SetAllowTimeToAutoAdvanceUntil(TimeTicks::Max());
}
void ScopedTaskEnvironment::FastForwardUntilNoTasksRemain() {
- DCHECK(mock_time_task_runner_);
- mock_time_task_runner_->FastForwardUntilNoTasksRemain();
+ // TimeTicks::operator+(TimeDelta) uses saturated arithmetic so it's safe to
+ // pass in TimeDelta::Max().
+ FastForwardBy(TimeDelta::Max());
}
const TickClock* ScopedTaskEnvironment::GetMockTickClock() const {
- 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();
+ DCHECK(mock_time_domain_);
+ return mock_time_domain_.get();
}
base::TimeTicks ScopedTaskEnvironment::NowTicks() const {
- DCHECK(mock_time_task_runner_);
- return mock_time_task_runner_->NowTicks();
+ DCHECK(mock_time_domain_);
+ return mock_time_domain_->Now();
}
const Clock* ScopedTaskEnvironment::GetMockClock() const {
@@ -354,13 +539,19 @@
}
size_t ScopedTaskEnvironment::GetPendingMainThreadTaskCount() const {
- DCHECK(mock_time_task_runner_);
- return mock_time_task_runner_->GetPendingTaskCount();
+ // ReclaimMemory sweeps canceled delayed tasks.
+ sequence_manager_->ReclaimMemory();
+ return sequence_manager_->GetPendingTaskCountForTesting();
}
TimeDelta ScopedTaskEnvironment::NextMainThreadPendingTaskDelay() const {
- DCHECK(mock_time_task_runner_);
- return mock_time_task_runner_->NextPendingTaskDelay();
+ // ReclaimMemory sweeps canceled delayed tasks.
+ sequence_manager_->ReclaimMemory();
+ DCHECK(mock_time_domain_);
+ Optional<TimeTicks> run_time = mock_time_domain_->NextScheduledRunTime();
+ if (run_time)
+ return *run_time - mock_time_domain_->Now();
+ return TimeDelta::Max();
}
ScopedTaskEnvironment::TestTaskTracker::TestTaskTracker()
diff --git a/base/test/scoped_task_environment.h b/base/test/scoped_task_environment.h
index fe77549..b86acbc 100644
--- a/base/test/scoped_task_environment.h
+++ b/base/test/scoped_task_environment.h
@@ -9,21 +9,16 @@
#include "base/memory/ref_counted.h"
#include "base/single_thread_task_runner.h"
#include "base/task/lazy_task_runner.h"
+#include "base/task/sequence_manager/sequence_manager.h"
#include "base/time/time.h"
#include "build/build_config.h"
namespace base {
-namespace internal {
-class ScopedSetSequenceLocalStorageMapForCurrentThread;
-class SequenceLocalStorageMap;
-} // namespace internal
class Clock;
class FileDescriptorWatcher;
-class MessageLoop;
class TaskScheduler;
-class TestMockTimeTaskRunner;
class TickClock;
namespace test {
@@ -78,9 +73,18 @@
MOCK_TIME,
// The main thread pumps UI messages.
UI,
+ // The main thread pumps UI messages and uses a mock clock for delayed tasks
+ // (controllable via FastForward*() methods).
+ // TODO(gab@): Enable mock time on all threads and make MOCK_TIME
+ // configurable independent of MainThreadType.
+ UI_MOCK_TIME,
// The main thread pumps asynchronous IO messages and supports the
// FileDescriptorWatcher API on POSIX.
IO,
+ // The main thread pumps IO messages and uses a mock clock for delayed tasks
+ // (controllable via FastForward*() methods). In addition it supports the
+ // FileDescriptorWatcher API on POSIX.
+ IO_MOCK_TIME,
};
enum class ExecutionMode {
@@ -96,6 +100,13 @@
MainThreadType main_thread_type = MainThreadType::DEFAULT,
ExecutionMode execution_control_mode = ExecutionMode::ASYNC);
+ // Constructs a ScopedTaskEnvironment using a preexisting |sequence_manager|.
+ // |sequence_manager| must outlive this ScopedTaskEnvironment.
+ ScopedTaskEnvironment(
+ sequence_manager::SequenceManager* sequence_manager,
+ MainThreadType main_thread_type = MainThreadType::DEFAULT,
+ ExecutionMode execution_control_mode = ExecutionMode::ASYNC);
+
// Waits until no undelayed TaskScheduler tasks remain. Then, unregisters the
// TaskScheduler and the (Thread|Sequenced)TaskRunnerHandle.
~ScopedTaskEnvironment();
@@ -165,24 +176,25 @@
TimeDelta NextMainThreadPendingTaskDelay() const;
private:
+ class MockTimeDomain;
class TestTaskTracker;
+ ScopedTaskEnvironment(
+ std::unique_ptr<sequence_manager::SequenceManager> owned_sequence_manager,
+ sequence_manager::SequenceManager* sequence_manager,
+ MainThreadType main_thread_type,
+ ExecutionMode execution_control_mode);
+
+ scoped_refptr<sequence_manager::TaskQueue> CreateDefaultTaskQueue();
+
const ExecutionMode execution_control_mode_;
- // Exactly one of these will be non-null to provide the task environment on
- // the main thread. Users of this class should NOT rely on the presence of a
- // MessageLoop beyond (Thread|Sequenced)TaskRunnerHandle and RunLoop as
- // the backing implementation of each MainThreadType may change over time.
- const std::unique_ptr<MessageLoop> message_loop_;
- const scoped_refptr<TestMockTimeTaskRunner> mock_time_task_runner_;
+ const std::unique_ptr<MockTimeDomain> mock_time_domain_;
+ const std::unique_ptr<sequence_manager::SequenceManager>
+ owned_sequence_manager_;
+ sequence_manager::SequenceManager* const sequence_manager_;
- // Non-null in MOCK_TIME, where an explicit SequenceLocalStorageMap needs to
- // be provided. TODO(gab): This can be removed once mock time support is added
- // to MessageLoop directly.
- const std::unique_ptr<internal::SequenceLocalStorageMap> slsm_for_mock_time_;
- const std::unique_ptr<
- internal::ScopedSetSequenceLocalStorageMapForCurrentThread>
- slsm_registration_for_mock_time_;
+ scoped_refptr<sequence_manager::TaskQueue> task_queue_;
// Only set for instances with a MOCK_TIME MainThreadType.
const std::unique_ptr<Clock> mock_clock_;
diff --git a/base/test/scoped_task_environment_unittest.cc b/base/test/scoped_task_environment_unittest.cc
index 2e89ade..b126704 100644
--- a/base/test/scoped_task_environment_unittest.cc
+++ b/base/test/scoped_task_environment_unittest.cc
@@ -373,6 +373,10 @@
ScopedTaskEnvironmentTest,
::testing::Values(ScopedTaskEnvironment::MainThreadType::MOCK_TIME));
INSTANTIATE_TEST_CASE_P(
+ MainThreadUiMockTime,
+ ScopedTaskEnvironmentTest,
+ ::testing::Values(ScopedTaskEnvironment::MainThreadType::UI_MOCK_TIME));
+INSTANTIATE_TEST_CASE_P(
MainThreadUI,
ScopedTaskEnvironmentTest,
::testing::Values(ScopedTaskEnvironment::MainThreadType::UI));
@@ -587,6 +591,10 @@
MainThreadMockTime,
ScopedTaskEnvironmentMockedTime,
::testing::Values(ScopedTaskEnvironment::MainThreadType::MOCK_TIME));
+INSTANTIATE_TEST_CASE_P(
+ MainThreadUiMockTime,
+ ScopedTaskEnvironmentMockedTime,
+ ::testing::Values(ScopedTaskEnvironment::MainThreadType::UI_MOCK_TIME));
} // namespace test
} // namespace base
diff --git a/webrunner/net_http/http_service_unittest.cc b/webrunner/net_http/http_service_unittest.cc
index b77d00f9..5b6d20b0 100644
--- a/webrunner/net_http/http_service_unittest.cc
+++ b/webrunner/net_http/http_service_unittest.cc
@@ -35,7 +35,7 @@
public:
HttpServiceTest()
: task_environment_(
- base::test::ScopedTaskEnvironment::MainThreadType::IO),
+ base::test::ScopedTaskEnvironment::MainThreadType::IO_MOCK_TIME),
binding_(&http_service_server_) {
// Initialize the test server.
test_server_.AddDefaultHandlers(