Commit 1b3b02a4 authored by Gabriel Charette's avatar Gabriel Charette Committed by Commit Bot

[ScopedTaskEnvironment] Add MOCK_TIME support for ThreadPool tasks

Multi-threaded MOCK_TIME advances when either RunLoop::Run() or
ScopedTaskEnvironment::FastForward*() is active and the system reaches
an idle phase (both main thread and thread pool are out of immediate
tasks). It then advances by the minimum time delta necessary to make a
task able to run.

In a follow-up CL, I'll split MOCK_TIME off of MainThreadType (now that
it's no longer main thread specific) and migrate callers.

Bug: 946657
Change-Id: Id3da34ac066623291a8fca682fd13c9473e83e0b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1686776Reviewed-by: default avatarAlex Clarke <alexclarke@chromium.org>
Reviewed-by: default avatarFrançois Doray <fdoray@chromium.org>
Commit-Queue: Gabriel Charette <gab@chromium.org>
Cr-Commit-Position: refs/heads/master@{#676272}
parent 81116d05
...@@ -47,10 +47,10 @@ class BASE_EXPORT TimeDomain { ...@@ -47,10 +47,10 @@ class BASE_EXPORT TimeDomain {
// TODO(alexclarke): Make this main thread only. // TODO(alexclarke): Make this main thread only.
virtual TimeTicks Now() const = 0; virtual TimeTicks Now() const = 0;
// Computes the delay until the time when TimeDomain needs to wake up // Computes the delay until the time when TimeDomain needs to wake up some
// some TaskQueue. Specific time domains (e.g. virtual or throttled) may // TaskQueue on the main thread. Specific time domains (e.g. virtual or
// return TimeDelata() if TaskQueues have any delayed tasks they deem // throttled) may return TimeDelta() if TaskQueues have any delayed tasks they
// eligible to run. It's also allowed to advance time domains's internal // deem eligible to run. It's also allowed to advance time domains's internal
// clock when this method is called. // clock when this method is called.
// Can be called from main thread only. // Can be called from main thread only.
// NOTE: |lazy_now| and the return value are in the SequenceManager's time. // NOTE: |lazy_now| and the return value are in the SequenceManager's time.
......
...@@ -117,6 +117,13 @@ void DelayedTaskManager::ProcessRipeTasks() { ...@@ -117,6 +117,13 @@ void DelayedTaskManager::ProcessRipeTasks() {
} }
} }
Optional<TimeTicks> DelayedTaskManager::NextScheduledRunTime() const {
CheckedAutoLock auto_lock(queue_lock_);
if (delayed_task_queue_.empty())
return nullopt;
return delayed_task_queue_.Min().task.delayed_run_time;
}
TimeTicks DelayedTaskManager::GetTimeToScheduleProcessRipeTasksLockRequired() { TimeTicks DelayedTaskManager::GetTimeToScheduleProcessRipeTasksLockRequired() {
queue_lock_.AssertAcquired(); queue_lock_.AssertAcquired();
if (delayed_task_queue_.empty()) if (delayed_task_queue_.empty())
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#include "base/macros.h" #include "base/macros.h"
#include "base/memory/ptr_util.h" #include "base/memory/ptr_util.h"
#include "base/memory/ref_counted.h" #include "base/memory/ref_counted.h"
#include "base/optional.h"
#include "base/synchronization/atomic_flag.h" #include "base/synchronization/atomic_flag.h"
#include "base/task/common/checked_lock.h" #include "base/task/common/checked_lock.h"
#include "base/task/common/intrusive_heap.h" #include "base/task/common/intrusive_heap.h"
...@@ -49,6 +50,12 @@ class BASE_EXPORT DelayedTaskManager { ...@@ -49,6 +50,12 @@ class BASE_EXPORT DelayedTaskManager {
PostTaskNowCallback post_task_now_callback, PostTaskNowCallback post_task_now_callback,
scoped_refptr<TaskRunner> task_runner); scoped_refptr<TaskRunner> task_runner);
// Pop and post all the ripe tasks in the delayed task queue.
void ProcessRipeTasks();
// Returns the |delayed_run_time| of the next scheduled task, if any.
Optional<TimeTicks> NextScheduledRunTime() const;
private: private:
struct DelayedTask { struct DelayedTask {
DelayedTask(); DelayedTask();
...@@ -86,9 +93,6 @@ class BASE_EXPORT DelayedTaskManager { ...@@ -86,9 +93,6 @@ class BASE_EXPORT DelayedTaskManager {
DISALLOW_COPY_AND_ASSIGN(DelayedTask); DISALLOW_COPY_AND_ASSIGN(DelayedTask);
}; };
// Pop and post all the ripe tasks in the delayed task queue.
void ProcessRipeTasks();
// Get the time at which to schedule the next |ProcessRipeTasks()| execution, // Get the time at which to schedule the next |ProcessRipeTasks()| execution,
// or TimeTicks::Max() if none needs to be scheduled (i.e. no task, or next // or TimeTicks::Max() if none needs to be scheduled (i.e. no task, or next
// task already scheduled). // task already scheduled).
...@@ -108,7 +112,7 @@ class BASE_EXPORT DelayedTaskManager { ...@@ -108,7 +112,7 @@ class BASE_EXPORT DelayedTaskManager {
// it is never modified. It is therefore safe to access // it is never modified. It is therefore safe to access
// |service_thread_task_runner_| without synchronization once it is observed // |service_thread_task_runner_| without synchronization once it is observed
// that it is non-null. // that it is non-null.
CheckedLock queue_lock_; mutable CheckedLock queue_lock_;
scoped_refptr<TaskRunner> service_thread_task_runner_; scoped_refptr<TaskRunner> service_thread_task_runner_;
......
...@@ -160,6 +160,11 @@ class BASE_EXPORT TaskTracker { ...@@ -160,6 +160,11 @@ class BASE_EXPORT TaskTracker {
return tracked_ref_factory_.GetTrackedRef(); return tracked_ref_factory_.GetTrackedRef();
} }
// Returns true if there are task sources that haven't completed their
// execution (still queued or in progress). If it returns false: the side-
// effects of all completed tasks are guaranteed to be visible to the caller.
bool HasIncompleteTaskSourcesForTesting() const;
protected: protected:
// Runs and deletes |task| if |can_run_task| is true. Otherwise, just deletes // Runs and deletes |task| if |can_run_task| is true. Otherwise, just deletes
// |task|. |task| is always deleted in the environment where it runs or would // |task|. |task| is always deleted in the environment where it runs or would
...@@ -172,11 +177,6 @@ class BASE_EXPORT TaskTracker { ...@@ -172,11 +177,6 @@ class BASE_EXPORT TaskTracker {
const TaskTraits& traits, const TaskTraits& traits,
bool can_run_task); bool can_run_task);
// Returns true if there are task sources that haven't completed their
// execution (still queued or in progress). If it returns false: the side-
// effects of all completed tasks are guaranteed to be visible to the caller.
bool HasIncompleteTaskSourcesForTesting() const;
private: private:
friend class RegisteredTaskSource; friend class RegisteredTaskSource;
class State; class State;
......
...@@ -64,11 +64,13 @@ bool HasDisableBestEffortTasksSwitch() { ...@@ -64,11 +64,13 @@ bool HasDisableBestEffortTasksSwitch() {
ThreadPoolImpl::ThreadPoolImpl(StringPiece histogram_label) ThreadPoolImpl::ThreadPoolImpl(StringPiece histogram_label)
: ThreadPoolImpl(histogram_label, : ThreadPoolImpl(histogram_label,
std::make_unique<TaskTrackerImpl>(histogram_label)) {} std::make_unique<TaskTrackerImpl>(histogram_label),
DefaultTickClock::GetInstance()) {}
ThreadPoolImpl::ThreadPoolImpl(StringPiece histogram_label, ThreadPoolImpl::ThreadPoolImpl(StringPiece histogram_label,
std::unique_ptr<TaskTrackerImpl> task_tracker) std::unique_ptr<TaskTrackerImpl> task_tracker,
: thread_pool_clock_(DefaultTickClock::GetInstance()), const TickClock* tick_clock)
: thread_pool_clock_(tick_clock),
task_tracker_(std::move(task_tracker)), task_tracker_(std::move(task_tracker)),
service_thread_(std::make_unique<ServiceThread>( service_thread_(std::make_unique<ServiceThread>(
task_tracker_.get(), task_tracker_.get(),
...@@ -265,6 +267,16 @@ ThreadPoolImpl::CreateUpdateableSequencedTaskRunner(const TaskTraits& traits) { ...@@ -265,6 +267,16 @@ ThreadPoolImpl::CreateUpdateableSequencedTaskRunner(const TaskTraits& traits) {
return MakeRefCounted<PooledSequencedTaskRunner>(new_traits, this); return MakeRefCounted<PooledSequencedTaskRunner>(new_traits, this);
} }
Optional<TimeTicks> ThreadPoolImpl::NextScheduledRunTimeForTesting() const {
if (task_tracker_->HasIncompleteTaskSourcesForTesting())
return ThreadPoolClock::Now();
return delayed_task_manager_.NextScheduledRunTime();
}
void ThreadPoolImpl::ProcessRipeDelayedTasksForTesting() {
delayed_task_manager_.ProcessRipeTasks();
}
int ThreadPoolImpl::GetMaxConcurrentNonBlockedTasksWithTraitsDeprecated( int ThreadPoolImpl::GetMaxConcurrentNonBlockedTasksWithTraitsDeprecated(
const TaskTraits& traits) const { const TaskTraits& traits) const {
// This method does not support getting the maximum number of BEST_EFFORT // This method does not support getting the maximum number of BEST_EFFORT
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
#include "base/macros.h" #include "base/macros.h"
#include "base/memory/ptr_util.h" #include "base/memory/ptr_util.h"
#include "base/memory/ref_counted.h" #include "base/memory/ref_counted.h"
#include "base/optional.h"
#include "base/sequence_checker.h" #include "base/sequence_checker.h"
#include "base/strings/string_piece.h" #include "base/strings/string_piece.h"
#include "base/synchronization/atomic_flag.h" #include "base/synchronization/atomic_flag.h"
...@@ -64,9 +65,11 @@ class BASE_EXPORT ThreadPoolImpl : public ThreadPoolInstance, ...@@ -64,9 +65,11 @@ class BASE_EXPORT ThreadPoolImpl : public ThreadPoolInstance,
//|histogram_label| is used to label histograms, it must not be empty. //|histogram_label| is used to label histograms, it must not be empty.
explicit ThreadPoolImpl(StringPiece histogram_label); explicit ThreadPoolImpl(StringPiece histogram_label);
// For testing only. Creates a ThreadPoolImpl with a custom TaskTracker. // For testing only. Creates a ThreadPoolImpl with a custom TaskTracker and
// TickClock.
ThreadPoolImpl(StringPiece histogram_label, ThreadPoolImpl(StringPiece histogram_label,
std::unique_ptr<TaskTrackerImpl> task_tracker); std::unique_ptr<TaskTrackerImpl> task_tracker,
const TickClock* tick_clock);
~ThreadPoolImpl() override; ~ThreadPoolImpl() override;
...@@ -101,6 +104,16 @@ class BASE_EXPORT ThreadPoolImpl : public ThreadPoolInstance, ...@@ -101,6 +104,16 @@ class BASE_EXPORT ThreadPoolImpl : public ThreadPoolInstance,
scoped_refptr<UpdateableSequencedTaskRunner> scoped_refptr<UpdateableSequencedTaskRunner>
CreateUpdateableSequencedTaskRunner(const TaskTraits& traits); CreateUpdateableSequencedTaskRunner(const TaskTraits& traits);
// Returns the TimeTicks of the next task scheduled on ThreadPool (Now() if
// immediate, nullopt if none). This is thread-safe, i.e., it's safe if tasks
// are being posted in parallel with this call but such a situation obviously
// results in a race as to whether this call will see the new tasks in time.
Optional<TimeTicks> NextScheduledRunTimeForTesting() const;
// Forces ripe delayed tasks to be posted (e.g. when time is mocked and
// advances faster than the real-time delay on ServiceThread).
void ProcessRipeDelayedTasksForTesting();
private: private:
// Invoked after |has_fence_| or |has_best_effort_fence_| is updated. Sets the // Invoked after |has_fence_| or |has_best_effort_fence_| is updated. Sets the
// CanRunPolicy in TaskTracker and wakes up workers as appropriate. // CanRunPolicy in TaskTracker and wakes up workers as appropriate.
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
#include "base/task/sequence_manager/sequence_manager_impl.h" #include "base/task/sequence_manager/sequence_manager_impl.h"
#include "base/task/sequence_manager/time_domain.h" #include "base/task/sequence_manager/time_domain.h"
#include "base/task/thread_pool/thread_pool.h" #include "base/task/thread_pool/thread_pool.h"
#include "base/task/thread_pool/thread_pool_clock.h"
#include "base/task/thread_pool/thread_pool_impl.h" #include "base/task/thread_pool/thread_pool_impl.h"
#include "base/test/bind_test_util.h" #include "base/test/bind_test_util.h"
#include "base/test/test_mock_time_task_runner.h" #include "base/test/test_mock_time_task_runner.h"
...@@ -29,6 +30,7 @@ ...@@ -29,6 +30,7 @@
#include "base/threading/thread_restrictions.h" #include "base/threading/thread_restrictions.h"
#include "base/threading/thread_task_runner_handle.h" #include "base/threading/thread_task_runner_handle.h"
#include "base/time/clock.h" #include "base/time/clock.h"
#include "base/time/default_tick_clock.h"
#include "base/time/tick_clock.h" #include "base/time/tick_clock.h"
#include "base/time/time.h" #include "base/time/time.h"
#include "base/time/time_override.h" #include "base/time/time_override.h"
...@@ -112,6 +114,9 @@ class ScopedTaskEnvironment::TestTaskTracker ...@@ -112,6 +114,9 @@ class ScopedTaskEnvironment::TestTaskTracker
// thread, waiting for it to complete results in a deadlock...). // thread, waiting for it to complete results in a deadlock...).
bool DisallowRunTasks(); bool DisallowRunTasks();
// Returns true if tasks are currently allowed to run.
bool TasksAllowedToRun() const;
private: private:
friend class ScopedTaskEnvironment; friend class ScopedTaskEnvironment;
...@@ -122,7 +127,7 @@ class ScopedTaskEnvironment::TestTaskTracker ...@@ -122,7 +127,7 @@ class ScopedTaskEnvironment::TestTaskTracker
bool can_run_task) override; bool can_run_task) override;
// Synchronizes accesses to members below. // Synchronizes accesses to members below.
Lock lock_; mutable Lock lock_;
// True if running tasks is allowed. // True if running tasks is allowed.
bool can_run_tasks_ GUARDED_BY(lock_) = true; bool can_run_tasks_ GUARDED_BY(lock_) = true;
...@@ -192,6 +197,14 @@ class ScopedTaskEnvironment::MockTimeDomain ...@@ -192,6 +197,14 @@ class ScopedTaskEnvironment::MockTimeDomain
return nullptr; return nullptr;
} }
void SetThreadPool(internal::ThreadPoolImpl* thread_pool,
const TestTaskTracker* thread_pool_task_tracker) {
DCHECK(!thread_pool_);
DCHECK(!thread_pool_task_tracker_);
thread_pool_ = thread_pool;
thread_pool_task_tracker_ = thread_pool_task_tracker;
}
// sequence_manager::TimeDomain: // sequence_manager::TimeDomain:
sequence_manager::LazyNow CreateLazyNow() const override { sequence_manager::LazyNow CreateLazyNow() const override {
...@@ -239,7 +252,8 @@ class ScopedTaskEnvironment::MockTimeDomain ...@@ -239,7 +252,8 @@ class ScopedTaskEnvironment::MockTimeDomain
if (!auto_advance_on_idle_) if (!auto_advance_on_idle_)
return false; return false;
return FastForwardToNextTaskOrCap(TimeTicks::Max()); return FastForwardToNextTaskOrCap(TimeTicks::Max()) ==
NextTaskSource::kMainThread;
} }
const char* GetName() const override { return "MockTimeDomain"; } const char* GetName() const override { return "MockTimeDomain"; }
...@@ -247,20 +261,73 @@ class ScopedTaskEnvironment::MockTimeDomain ...@@ -247,20 +261,73 @@ class ScopedTaskEnvironment::MockTimeDomain
// TickClock implementation: // TickClock implementation:
TimeTicks NowTicks() const override { return Now(); } TimeTicks NowTicks() const override { return Now(); }
// Advances time to the next task or to |fast_forward_cap| (if it's not // Used by FastForwardToNextTaskOrCap() to return which task source time was
// Max()). Returns true if there's additional immediate work as a result // advanced to.
// of this call. enum class NextTaskSource {
bool FastForwardToNextTaskOrCap(TimeTicks fast_forward_cap) { // Out of tasks under |fast_forward_cap|.
kNone,
// There's now >=1 immediate task on the main thread.
kMainThread,
// There's now >=1 immediate task in the thread pool.
kThreadPool,
};
// Advances time to the first of : next main thread task, next thread pool
// task, or |fast_forward_cap| (if it's not Max()).
NextTaskSource FastForwardToNextTaskOrCap(TimeTicks fast_forward_cap) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// We don't need to call ReclaimMemory here because // We don't need to call ReclaimMemory here because
// DelayTillNextTask will have dealt with cancelled delayed tasks for us. // DelayTillNextTask will have dealt with cancelled delayed tasks for us.
Optional<TimeTicks> next_task_time = NextScheduledRunTime(); Optional<TimeTicks> next_main_thread_task_time = NextScheduledRunTime();
// Consider the next thread pool tasks iff they're running.
Optional<TimeTicks> next_thread_pool_task_time;
if (thread_pool_ && thread_pool_task_tracker_->TasksAllowedToRun()) {
next_thread_pool_task_time =
thread_pool_->NextScheduledRunTimeForTesting();
}
// Custom comparison logic to consider nullopt the largest rather than
// smallest value. Could consider using TimeTicks::Max() instead of nullopt
// to represent out-of-tasks?
Optional<TimeTicks> next_task_time;
if (!next_main_thread_task_time) {
next_task_time = next_thread_pool_task_time;
} else if (!next_thread_pool_task_time) {
next_task_time = next_main_thread_task_time;
} else {
next_task_time =
std::min(*next_main_thread_task_time, *next_thread_pool_task_time);
}
if (next_task_time && *next_task_time <= fast_forward_cap) { if (next_task_time && *next_task_time <= fast_forward_cap) {
AutoLock lock(now_ticks_lock_); {
now_ticks_ = *next_task_time; AutoLock lock(now_ticks_lock_);
return true; // It's possible for |next_task_time| to be in the past in the following
// scenario:
// Start with Now() == 100ms
// Thread A : Post 200ms delayed task T (construct and enqueue)
// Thread B : Construct 20ms delayed task U
// => |delayed_run_time| == 120ms.
// Thread A : FastForwardToNextTaskOrCap() => fast-forwards to T @
// 300ms (task U is not yet in queue).
// Thread B : Complete enqueue of task U.
// Thread A : FastForwardToNextTaskOrCap() => must stay at 300ms and run
// U, not go back to 120ms.
// Hence we need std::max() to protect again this because construction
// and enqueuing isn't atomic in time (LazyNow support in
// base/task/thread_pool could help).
now_ticks_ = std::max(now_ticks_, *next_task_time);
}
if (next_task_time == next_thread_pool_task_time) {
// Let the thread pool know that it should post its now ripe delayed
// tasks.
thread_pool_->ProcessRipeDelayedTasksForTesting();
return NextTaskSource::kThreadPool;
}
return NextTaskSource::kMainThread;
} }
if (!fast_forward_cap.is_max()) { if (!fast_forward_cap.is_max()) {
...@@ -270,11 +337,15 @@ class ScopedTaskEnvironment::MockTimeDomain ...@@ -270,11 +337,15 @@ class ScopedTaskEnvironment::MockTimeDomain
now_ticks_ = std::max(now_ticks_, fast_forward_cap); now_ticks_ = std::max(now_ticks_, fast_forward_cap);
} }
return false; return NextTaskSource::kNone;
} }
void set_auto_advance_on_idle(bool auto_advance_on_idle) { // Sets |auto_advance_on_idle_| to |auto_advance_on_idle| and returns its
// previous value.
bool SetAutoAdvanceOnIdle(bool auto_advance_on_idle) {
const auto previous = auto_advance_on_idle_;
auto_advance_on_idle_ = auto_advance_on_idle; auto_advance_on_idle_ = auto_advance_on_idle;
return previous;
} }
private: private:
...@@ -287,6 +358,9 @@ class ScopedTaskEnvironment::MockTimeDomain ...@@ -287,6 +358,9 @@ class ScopedTaskEnvironment::MockTimeDomain
sequence_manager::SequenceManager* const sequence_manager_; sequence_manager::SequenceManager* const sequence_manager_;
internal::ThreadPoolImpl* thread_pool_ = nullptr;
const TestTaskTracker* thread_pool_task_tracker_ = nullptr;
std::unique_ptr<subtle::ScopedTimeClockOverrides> time_overrides_; std::unique_ptr<subtle::ScopedTimeClockOverrides> time_overrides_;
// Protects |now_ticks_| // Protects |now_ticks_|
...@@ -391,8 +465,14 @@ void ScopedTaskEnvironment::InitializeThreadPool() { ...@@ -391,8 +465,14 @@ void ScopedTaskEnvironment::InitializeThreadPool() {
auto task_tracker = std::make_unique<TestTaskTracker>(); auto task_tracker = std::make_unique<TestTaskTracker>();
task_tracker_ = task_tracker.get(); task_tracker_ = task_tracker.get();
ThreadPoolInstance::Set(std::make_unique<internal::ThreadPoolImpl>( const TickClock* tick_clock =
"ScopedTaskEnvironment", std::move(task_tracker))); mock_time_domain_ ? static_cast<TickClock*>(mock_time_domain_.get())
: DefaultTickClock::GetInstance();
auto thread_pool = std::make_unique<internal::ThreadPoolImpl>(
"ScopedTaskEnvironment", std::move(task_tracker), tick_clock);
if (mock_time_domain_)
mock_time_domain_->SetThreadPool(thread_pool.get(), task_tracker_);
ThreadPoolInstance::Set(std::move(thread_pool));
ThreadPoolInstance::Get()->Start(init_params); ThreadPoolInstance::Get()->Start(init_params);
} }
...@@ -571,14 +651,21 @@ void ScopedTaskEnvironment::RunUntilIdle() { ...@@ -571,14 +651,21 @@ void ScopedTaskEnvironment::RunUntilIdle() {
void ScopedTaskEnvironment::FastForwardBy(TimeDelta delta) { void ScopedTaskEnvironment::FastForwardBy(TimeDelta delta) {
DCHECK(mock_time_domain_); DCHECK(mock_time_domain_);
mock_time_domain_->set_auto_advance_on_idle(false); DCHECK_GE(delta, TimeDelta());
const bool was_auto_advancing = mock_time_domain_->SetAutoAdvanceOnIdle(false);
const bool could_run_tasks = task_tracker_->AllowRunTasks();
const TimeTicks fast_forward_until = mock_time_domain_->NowTicks() + delta; const TimeTicks fast_forward_until = mock_time_domain_->NowTicks() + delta;
do { do {
RunUntilIdle(); RunUntilIdle();
} while (mock_time_domain_->FastForwardToNextTaskOrCap(fast_forward_until)); } while (mock_time_domain_->FastForwardToNextTaskOrCap(fast_forward_until) !=
MockTimeDomain::NextTaskSource::kNone);
mock_time_domain_->set_auto_advance_on_idle(true); if (was_auto_advancing)
mock_time_domain_->SetAutoAdvanceOnIdle(true);
if (!could_run_tasks)
task_tracker_->DisallowRunTasks();
} }
void ScopedTaskEnvironment::FastForwardUntilNoTasksRemain() { void ScopedTaskEnvironment::FastForwardUntilNoTasksRemain() {
...@@ -640,6 +727,11 @@ bool ScopedTaskEnvironment::TestTaskTracker::AllowRunTasks() { ...@@ -640,6 +727,11 @@ bool ScopedTaskEnvironment::TestTaskTracker::AllowRunTasks() {
return could_run_tasks; return could_run_tasks;
} }
bool ScopedTaskEnvironment::TestTaskTracker::TasksAllowedToRun() const {
AutoLock auto_lock(lock_);
return can_run_tasks_;
}
bool ScopedTaskEnvironment::TestTaskTracker::DisallowRunTasks() { bool ScopedTaskEnvironment::TestTaskTracker::DisallowRunTasks() {
AutoLock auto_lock(lock_); AutoLock auto_lock(lock_);
......
...@@ -78,16 +78,12 @@ class ScopedTaskEnvironment { ...@@ -78,16 +78,12 @@ class ScopedTaskEnvironment {
DEFAULT, DEFAULT,
// The main thread doesn't pump system messages and uses a mock clock for // The main thread doesn't pump system messages and uses a mock clock for
// delayed tasks (controllable via FastForward*() methods). // delayed tasks (controllable via FastForward*() methods).
// TODO(gab): Make this the default |main_thread_type|. // TODO(gab): Make MOCK_TIME configurable independent of MainThreadType.
// TODO(gab): Also mock the ThreadPoolInstance's clock simultaneously (this
// currently only mocks the main thread's clock).
MOCK_TIME, MOCK_TIME,
// The main thread pumps UI messages. // The main thread pumps UI messages.
UI, UI,
// The main thread pumps UI messages and uses a mock clock for delayed tasks // The main thread pumps UI messages and uses a mock clock for delayed tasks
// (controllable via FastForward*() methods). // (controllable via FastForward*() methods).
// TODO(gab@): Enable mock time on all threads and make MOCK_TIME
// configurable independent of MainThreadType.
UI_MOCK_TIME, UI_MOCK_TIME,
// The main thread pumps asynchronous IO messages and supports the // The main thread pumps asynchronous IO messages and supports the
// FileDescriptorWatcher API on POSIX. // FileDescriptorWatcher API on POSIX.
...@@ -195,9 +191,9 @@ class ScopedTaskEnvironment { ...@@ -195,9 +191,9 @@ class ScopedTaskEnvironment {
void RunUntilIdle(); void RunUntilIdle();
// Only valid for instances with a MOCK_TIME MainThreadType. Fast-forwards // Only valid for instances with a MOCK_TIME MainThreadType. Fast-forwards
// virtual time by |delta|, causing all tasks on the main thread with a // virtual time by |delta|, causing all tasks on the main thread and thread
// remaining delay less than or equal to |delta| to be executed in their // pool with a remaining delay less than or equal to |delta| to be executed in
// natural order before this returns. |delta| must be non-negative. Upon // their natural order before this returns. |delta| must be non-negative. Upon
// returning from this method, NowTicks() will be >= the initial |NowTicks() + // returning from this method, NowTicks() will be >= the initial |NowTicks() +
// delta|. It is guaranteed to be == iff tasks executed in this // delta|. It is guaranteed to be == iff tasks executed in this
// FastForwardBy() didn't result in nested calls to time-advancing-methods. // FastForwardBy() didn't result in nested calls to time-advancing-methods.
...@@ -229,13 +225,13 @@ class ScopedTaskEnvironment { ...@@ -229,13 +225,13 @@ class ScopedTaskEnvironment {
// FastForwardBy(2ms) // FastForwardBy(2ms)
// ================================ // ================================
// Result: NowTicks() is 5ms further in the future. // Result: NowTicks() is 5ms further in the future.
//
// TODO(gab): Make this apply to ThreadPool delayed tasks as well
// (currently only main thread time is mocked).
void FastForwardBy(TimeDelta delta); void FastForwardBy(TimeDelta delta);
// Only valid for instances with a MOCK_TIME MainThreadType. // Only valid for instances with a MOCK_TIME MainThreadType.
// Short for FastForwardBy(TimeDelta::Max()). // Short for FastForwardBy(TimeDelta::Max()).
//
// WARNING: This has the same caveat as RunUntilIdle() and is even more likely
// to spin forever (any RepeatingTimer will cause this).
void FastForwardUntilNoTasksRemain(); void FastForwardUntilNoTasksRemain();
// Only valid for instances with a MOCK_TIME MainThreadType. Returns a // Only valid for instances with a MOCK_TIME MainThreadType. Returns a
......
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
#include "base/test/test_timeouts.h" #include "base/test/test_timeouts.h"
#include "base/threading/platform_thread.h" #include "base/threading/platform_thread.h"
#include "base/threading/sequence_local_storage_slot.h" #include "base/threading/sequence_local_storage_slot.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/threading/thread.h" #include "base/threading/thread.h"
#include "base/threading/thread_task_runner_handle.h" #include "base/threading/thread_task_runner_handle.h"
#include "base/time/clock.h" #include "base/time/clock.h"
...@@ -182,8 +183,6 @@ TEST_P(ScopedTaskEnvironmentTest, DelayedTasks) { ...@@ -182,8 +183,6 @@ TEST_P(ScopedTaskEnvironmentTest, DelayedTasks) {
}, },
Unretained(&counter)), Unretained(&counter)),
kShortTaskDelay); kShortTaskDelay);
// TODO(gab): This currently doesn't run because the ThreadPool's clock
// isn't mocked but it should be.
PostDelayedTask(FROM_HERE, PostDelayedTask(FROM_HERE,
BindOnce( BindOnce(
[](subtle::Atomic32* counter) { [](subtle::Atomic32* counter) {
...@@ -211,6 +210,28 @@ TEST_P(ScopedTaskEnvironmentTest, DelayedTasks) { ...@@ -211,6 +210,28 @@ TEST_P(ScopedTaskEnvironmentTest, DelayedTasks) {
}, },
Unretained(&counter)), Unretained(&counter)),
kLongTaskDelay); kLongTaskDelay);
PostDelayedTask(FROM_HERE,
BindOnce(
[](subtle::Atomic32* counter) {
subtle::NoBarrier_AtomicIncrement(counter, 256);
},
Unretained(&counter)),
kLongTaskDelay * 2);
ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
BindOnce(
[](subtle::Atomic32* counter) {
subtle::NoBarrier_AtomicIncrement(counter, 512);
},
Unretained(&counter)),
kLongTaskDelay * 3);
PostDelayedTask(FROM_HERE,
BindOnce(
[](subtle::Atomic32* counter) {
subtle::NoBarrier_AtomicIncrement(counter, 1024);
},
Unretained(&counter)),
kLongTaskDelay * 4);
ThreadTaskRunnerHandle::Get()->PostTask( ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, BindOnce( FROM_HERE, BindOnce(
...@@ -236,6 +257,8 @@ TEST_P(ScopedTaskEnvironmentTest, DelayedTasks) { ...@@ -236,6 +257,8 @@ TEST_P(ScopedTaskEnvironmentTest, DelayedTasks) {
EXPECT_EQ(expected_value, counter); EXPECT_EQ(expected_value, counter);
if (GetParam() == ScopedTaskEnvironment::MainThreadType::MOCK_TIME) { if (GetParam() == ScopedTaskEnvironment::MainThreadType::MOCK_TIME) {
const TimeTicks start_time = scoped_task_environment.NowTicks();
// Delay inferior to the delay of the first posted task. // Delay inferior to the delay of the first posted task.
constexpr base::TimeDelta kInferiorTaskDelay = TimeDelta::FromSeconds(1); constexpr base::TimeDelta kInferiorTaskDelay = TimeDelta::FromSeconds(1);
static_assert(kInferiorTaskDelay < kShortTaskDelay, static_assert(kInferiorTaskDelay < kShortTaskDelay,
...@@ -246,12 +269,19 @@ TEST_P(ScopedTaskEnvironmentTest, DelayedTasks) { ...@@ -246,12 +269,19 @@ TEST_P(ScopedTaskEnvironmentTest, DelayedTasks) {
scoped_task_environment.FastForwardBy(kShortTaskDelay - kInferiorTaskDelay); scoped_task_environment.FastForwardBy(kShortTaskDelay - kInferiorTaskDelay);
expected_value += 4; expected_value += 4;
expected_value += 128;
EXPECT_EQ(expected_value, counter); EXPECT_EQ(expected_value, counter);
scoped_task_environment.FastForwardUntilNoTasksRemain(); scoped_task_environment.FastForwardUntilNoTasksRemain();
expected_value += 8; expected_value += 8;
expected_value += 16; expected_value += 16;
expected_value += 256;
expected_value += 512;
expected_value += 1024;
EXPECT_EQ(expected_value, counter); EXPECT_EQ(expected_value, counter);
EXPECT_EQ(scoped_task_environment.NowTicks() - start_time,
kLongTaskDelay * 4);
} }
} }
...@@ -456,7 +486,8 @@ TEST_F(ScopedTaskEnvironmentTest, NestedFastForwardBy) { ...@@ -456,7 +486,8 @@ TEST_F(ScopedTaskEnvironmentTest, NestedFastForwardBy) {
EXPECT_EQ(scoped_task_environment.NowTicks(), start_time + kDelayPerTask * 5); EXPECT_EQ(scoped_task_environment.NowTicks(), start_time + kDelayPerTask * 5);
} }
TEST_F(ScopedTaskEnvironmentTest, CrossThreadTaskPostingDoesntAffectMockTime) { TEST_F(ScopedTaskEnvironmentTest,
CrossThreadImmediateTaskPostingDoesntAffectMockTime) {
ScopedTaskEnvironment scoped_task_environment( ScopedTaskEnvironment scoped_task_environment(
ScopedTaskEnvironment::MainThreadType::MOCK_TIME, ScopedTaskEnvironment::MainThreadType::MOCK_TIME,
ScopedTaskEnvironment::NowSource::MAIN_THREAD_MOCK_TIME); ScopedTaskEnvironment::NowSource::MAIN_THREAD_MOCK_TIME);
...@@ -499,6 +530,158 @@ TEST_F(ScopedTaskEnvironmentTest, CrossThreadTaskPostingDoesntAffectMockTime) { ...@@ -499,6 +530,158 @@ TEST_F(ScopedTaskEnvironmentTest, CrossThreadTaskPostingDoesntAffectMockTime) {
scoped_task_environment.RunUntilIdle(); scoped_task_environment.RunUntilIdle();
} }
TEST_F(ScopedTaskEnvironmentTest, MultiThreadedMockTime) {
ScopedTaskEnvironment scoped_task_environment(
ScopedTaskEnvironment::MainThreadType::MOCK_TIME);
constexpr TimeDelta kOneMs = TimeDelta::FromMilliseconds(1);
const TimeTicks start_time = scoped_task_environment.NowTicks();
const TimeTicks end_time = start_time + TimeDelta::FromMilliseconds(1'000);
// Last TimeTicks::Now() seen from either contexts.
TimeTicks last_main_thread_ticks = start_time;
TimeTicks last_thread_pool_ticks = start_time;
RepeatingClosure post_main_thread_delayed_task;
post_main_thread_delayed_task = BindLambdaForTesting([&]() {
// Expect that time only moves forward.
EXPECT_GE(scoped_task_environment.NowTicks(), last_main_thread_ticks);
// Post four tasks to exercise the system some more but only if this is the
// first task at its runtime (otherwise we end up with 4^10'000 tasks by
// the end!).
if (last_main_thread_ticks < scoped_task_environment.NowTicks() &&
scoped_task_environment.NowTicks() < end_time) {
SequencedTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, post_main_thread_delayed_task, kOneMs);
SequencedTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, post_main_thread_delayed_task, kOneMs);
SequencedTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, post_main_thread_delayed_task, kOneMs);
SequencedTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, post_main_thread_delayed_task, kOneMs);
}
last_main_thread_ticks = scoped_task_environment.NowTicks();
});
RepeatingClosure post_thread_pool_delayed_task;
post_thread_pool_delayed_task = BindLambdaForTesting([&]() {
// Expect that time only moves forward.
EXPECT_GE(scoped_task_environment.NowTicks(), last_thread_pool_ticks);
// Post four tasks to exercise the system some more but only if this is the
// first task at its runtime (otherwise we end up with 4^10'000 tasks by
// the end!).
if (last_thread_pool_ticks < scoped_task_environment.NowTicks() &&
scoped_task_environment.NowTicks() < end_time) {
SequencedTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, post_thread_pool_delayed_task, kOneMs);
SequencedTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, post_thread_pool_delayed_task, kOneMs);
SequencedTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, post_thread_pool_delayed_task, kOneMs);
SequencedTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, post_thread_pool_delayed_task, kOneMs);
EXPECT_LT(scoped_task_environment.NowTicks(), end_time);
}
last_thread_pool_ticks = scoped_task_environment.NowTicks();
});
ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, post_main_thread_delayed_task, kOneMs);
CreateSequencedTaskRunnerWithTraits({ThreadPool()})
->PostDelayedTask(FROM_HERE, post_thread_pool_delayed_task, kOneMs);
scoped_task_environment.FastForwardUntilNoTasksRemain();
EXPECT_EQ(last_main_thread_ticks, end_time);
EXPECT_EQ(last_thread_pool_ticks, end_time);
EXPECT_EQ(scoped_task_environment.NowTicks(), end_time);
}
// Verify that ThreadPoolExecutionMode::QUEUED doesn't prevent running tasks and
// advancing time on the main thread.
TEST_F(ScopedTaskEnvironmentTest,
MultiThreadedMockTimeAndThreadPoolQueuedMode) {
ScopedTaskEnvironment scoped_task_environment(
ScopedTaskEnvironment::MainThreadType::MOCK_TIME,
ScopedTaskEnvironment::ThreadPoolExecutionMode::QUEUED);
int count = 0;
const TimeTicks start_time = scoped_task_environment.NowTicks();
RunLoop run_loop;
// Neither of these should run automatically per
// ThreadPoolExecutionMode::QUEUED.
PostTask(FROM_HERE, {ThreadPool()},
BindLambdaForTesting([&]() { count += 128; }));
PostDelayedTask(FROM_HERE, {ThreadPool()},
BindLambdaForTesting([&]() { count += 256; }),
TimeDelta::FromSeconds(5));
// Time should auto-advance to +500s in RunLoop::Run() without having to run
// the above forcefully QUEUED tasks.
ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, BindLambdaForTesting([&]() { count += 1; }));
ThreadTaskRunnerHandle::Get()->PostDelayedTask(FROM_HERE,
BindLambdaForTesting([&]() {
count += 2;
run_loop.Quit();
}),
TimeDelta::FromSeconds(500));
int expected_value = 0;
EXPECT_EQ(expected_value, count);
run_loop.Run();
expected_value += 1;
expected_value += 2;
EXPECT_EQ(expected_value, count);
EXPECT_EQ(scoped_task_environment.NowTicks() - start_time,
TimeDelta::FromSeconds(500));
// Fast-forward through all remaining tasks, this should unblock QUEUED tasks
// in the thread pool but shouldn't need to advance time to process them.
scoped_task_environment.FastForwardUntilNoTasksRemain();
expected_value += 128;
expected_value += 256;
EXPECT_EQ(expected_value, count);
EXPECT_EQ(scoped_task_environment.NowTicks() - start_time,
TimeDelta::FromSeconds(500));
// Test advancing time to a QUEUED task in the future.
PostDelayedTask(FROM_HERE, {ThreadPool()},
BindLambdaForTesting([&]() { count += 512; }),
TimeDelta::FromSeconds(5));
scoped_task_environment.FastForwardBy(TimeDelta::FromSeconds(7));
expected_value += 512;
EXPECT_EQ(expected_value, count);
EXPECT_EQ(scoped_task_environment.NowTicks() - start_time,
TimeDelta::FromSeconds(507));
// Confirm that QUEUED mode is still active after the above fast forwarding
// (only the main thread task should run from RunLoop).
PostTask(FROM_HERE, {ThreadPool()},
BindLambdaForTesting([&]() { count += 1024; }));
ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, BindLambdaForTesting([&]() { count += 2048; }));
PlatformThread::Sleep(TimeDelta::FromMilliseconds(1));
RunLoop().RunUntilIdle();
expected_value += 2048;
EXPECT_EQ(expected_value, count);
EXPECT_EQ(scoped_task_environment.NowTicks() - start_time,
TimeDelta::FromSeconds(507));
// Run the remaining task to avoid use-after-free on |count| from
// ~ScopedTaskEnvironment().
scoped_task_environment.RunUntilIdle();
expected_value += 1024;
EXPECT_EQ(expected_value, count);
}
#if defined(OS_WIN) #if defined(OS_WIN)
// Regression test to ensure that ScopedTaskEnvironment enables the MTA in the // Regression test to ensure that ScopedTaskEnvironment enables the MTA in the
// thread pool (so that the test environment matches that of the browser process // thread pool (so that the test environment matches that of the browser process
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment