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 {
// TODO(alexclarke): Make this main thread only.
virtual TimeTicks Now() const = 0;
// Computes the delay until the time when TimeDomain needs to wake up
// some TaskQueue. Specific time domains (e.g. virtual or throttled) may
// return TimeDelata() if TaskQueues have any delayed tasks they deem
// eligible to run. It's also allowed to advance time domains's internal
// Computes the delay until the time when TimeDomain needs to wake up some
// TaskQueue on the main thread. Specific time domains (e.g. virtual or
// throttled) may return TimeDelta() if TaskQueues have any delayed tasks they
// deem eligible to run. It's also allowed to advance time domains's internal
// clock when this method is called.
// Can be called from main thread only.
// NOTE: |lazy_now| and the return value are in the SequenceManager's time.
......
......@@ -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() {
queue_lock_.AssertAcquired();
if (delayed_task_queue_.empty())
......
......@@ -13,6 +13,7 @@
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/memory/ref_counted.h"
#include "base/optional.h"
#include "base/synchronization/atomic_flag.h"
#include "base/task/common/checked_lock.h"
#include "base/task/common/intrusive_heap.h"
......@@ -49,6 +50,12 @@ class BASE_EXPORT DelayedTaskManager {
PostTaskNowCallback post_task_now_callback,
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:
struct DelayedTask {
DelayedTask();
......@@ -86,9 +93,6 @@ class BASE_EXPORT DelayedTaskManager {
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,
// or TimeTicks::Max() if none needs to be scheduled (i.e. no task, or next
// task already scheduled).
......@@ -108,7 +112,7 @@ class BASE_EXPORT DelayedTaskManager {
// it is never modified. It is therefore safe to access
// |service_thread_task_runner_| without synchronization once it is observed
// that it is non-null.
CheckedLock queue_lock_;
mutable CheckedLock queue_lock_;
scoped_refptr<TaskRunner> service_thread_task_runner_;
......
......@@ -160,6 +160,11 @@ class BASE_EXPORT TaskTracker {
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:
// 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
......@@ -172,11 +177,6 @@ class BASE_EXPORT TaskTracker {
const TaskTraits& traits,
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:
friend class RegisteredTaskSource;
class State;
......
......@@ -64,11 +64,13 @@ bool HasDisableBestEffortTasksSwitch() {
ThreadPoolImpl::ThreadPoolImpl(StringPiece histogram_label)
: ThreadPoolImpl(histogram_label,
std::make_unique<TaskTrackerImpl>(histogram_label)) {}
std::make_unique<TaskTrackerImpl>(histogram_label),
DefaultTickClock::GetInstance()) {}
ThreadPoolImpl::ThreadPoolImpl(StringPiece histogram_label,
std::unique_ptr<TaskTrackerImpl> task_tracker)
: thread_pool_clock_(DefaultTickClock::GetInstance()),
std::unique_ptr<TaskTrackerImpl> task_tracker,
const TickClock* tick_clock)
: thread_pool_clock_(tick_clock),
task_tracker_(std::move(task_tracker)),
service_thread_(std::make_unique<ServiceThread>(
task_tracker_.get(),
......@@ -265,6 +267,16 @@ ThreadPoolImpl::CreateUpdateableSequencedTaskRunner(const TaskTraits& traits) {
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(
const TaskTraits& traits) const {
// This method does not support getting the maximum number of BEST_EFFORT
......
......@@ -14,6 +14,7 @@
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/memory/ref_counted.h"
#include "base/optional.h"
#include "base/sequence_checker.h"
#include "base/strings/string_piece.h"
#include "base/synchronization/atomic_flag.h"
......@@ -64,9 +65,11 @@ class BASE_EXPORT ThreadPoolImpl : public ThreadPoolInstance,
//|histogram_label| is used to label histograms, it must not be empty.
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,
std::unique_ptr<TaskTrackerImpl> task_tracker);
std::unique_ptr<TaskTrackerImpl> task_tracker,
const TickClock* tick_clock);
~ThreadPoolImpl() override;
......@@ -101,6 +104,16 @@ class BASE_EXPORT ThreadPoolImpl : public ThreadPoolInstance,
scoped_refptr<UpdateableSequencedTaskRunner>
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:
// Invoked after |has_fence_| or |has_best_effort_fence_| is updated. Sets the
// CanRunPolicy in TaskTracker and wakes up workers as appropriate.
......
......@@ -19,6 +19,7 @@
#include "base/task/sequence_manager/sequence_manager_impl.h"
#include "base/task/sequence_manager/time_domain.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/test/bind_test_util.h"
#include "base/test/test_mock_time_task_runner.h"
......@@ -29,6 +30,7 @@
#include "base/threading/thread_restrictions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/clock.h"
#include "base/time/default_tick_clock.h"
#include "base/time/tick_clock.h"
#include "base/time/time.h"
#include "base/time/time_override.h"
......@@ -112,6 +114,9 @@ class ScopedTaskEnvironment::TestTaskTracker
// thread, waiting for it to complete results in a deadlock...).
bool DisallowRunTasks();
// Returns true if tasks are currently allowed to run.
bool TasksAllowedToRun() const;
private:
friend class ScopedTaskEnvironment;
......@@ -122,7 +127,7 @@ class ScopedTaskEnvironment::TestTaskTracker
bool can_run_task) override;
// Synchronizes accesses to members below.
Lock lock_;
mutable Lock lock_;
// True if running tasks is allowed.
bool can_run_tasks_ GUARDED_BY(lock_) = true;
......@@ -192,6 +197,14 @@ class ScopedTaskEnvironment::MockTimeDomain
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::LazyNow CreateLazyNow() const override {
......@@ -239,7 +252,8 @@ class ScopedTaskEnvironment::MockTimeDomain
if (!auto_advance_on_idle_)
return false;
return FastForwardToNextTaskOrCap(TimeTicks::Max());
return FastForwardToNextTaskOrCap(TimeTicks::Max()) ==
NextTaskSource::kMainThread;
}
const char* GetName() const override { return "MockTimeDomain"; }
......@@ -247,20 +261,73 @@ class ScopedTaskEnvironment::MockTimeDomain
// TickClock implementation:
TimeTicks NowTicks() const override { return Now(); }
// Advances time to the next task or to |fast_forward_cap| (if it's not
// Max()). Returns true if there's additional immediate work as a result
// of this call.
bool FastForwardToNextTaskOrCap(TimeTicks fast_forward_cap) {
// Used by FastForwardToNextTaskOrCap() to return which task source time was
// advanced to.
enum class NextTaskSource {
// 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_);
// We don't need to call ReclaimMemory here because
// 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) {
{
AutoLock lock(now_ticks_lock_);
now_ticks_ = *next_task_time;
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()) {
......@@ -270,11 +337,15 @@ class ScopedTaskEnvironment::MockTimeDomain
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;
return previous;
}
private:
......@@ -287,6 +358,9 @@ class ScopedTaskEnvironment::MockTimeDomain
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_;
// Protects |now_ticks_|
......@@ -391,8 +465,14 @@ void ScopedTaskEnvironment::InitializeThreadPool() {
auto task_tracker = std::make_unique<TestTaskTracker>();
task_tracker_ = task_tracker.get();
ThreadPoolInstance::Set(std::make_unique<internal::ThreadPoolImpl>(
"ScopedTaskEnvironment", std::move(task_tracker)));
const TickClock* tick_clock =
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);
}
......@@ -571,14 +651,21 @@ void ScopedTaskEnvironment::RunUntilIdle() {
void ScopedTaskEnvironment::FastForwardBy(TimeDelta delta) {
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;
do {
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() {
......@@ -640,6 +727,11 @@ bool ScopedTaskEnvironment::TestTaskTracker::AllowRunTasks() {
return could_run_tasks;
}
bool ScopedTaskEnvironment::TestTaskTracker::TasksAllowedToRun() const {
AutoLock auto_lock(lock_);
return can_run_tasks_;
}
bool ScopedTaskEnvironment::TestTaskTracker::DisallowRunTasks() {
AutoLock auto_lock(lock_);
......
......@@ -78,16 +78,12 @@ class ScopedTaskEnvironment {
DEFAULT,
// The main thread doesn't pump system messages and uses a mock clock for
// delayed tasks (controllable via FastForward*() methods).
// TODO(gab): Make this the default |main_thread_type|.
// TODO(gab): Also mock the ThreadPoolInstance's clock simultaneously (this
// currently only mocks the main thread's clock).
// TODO(gab): Make MOCK_TIME configurable independent of MainThreadType.
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.
......@@ -195,9 +191,9 @@ class ScopedTaskEnvironment {
void RunUntilIdle();
// Only valid for instances with a MOCK_TIME MainThreadType. Fast-forwards
// virtual time by |delta|, causing all tasks on the main thread with a
// remaining delay less than or equal to |delta| to be executed in their
// natural order before this returns. |delta| must be non-negative. Upon
// virtual time by |delta|, causing all tasks on the main thread and thread
// pool with a remaining delay less than or equal to |delta| to be executed in
// their natural order before this returns. |delta| must be non-negative. Upon
// returning from this method, NowTicks() will be >= the initial |NowTicks() +
// delta|. It is guaranteed to be == iff tasks executed in this
// FastForwardBy() didn't result in nested calls to time-advancing-methods.
......@@ -229,13 +225,13 @@ class ScopedTaskEnvironment {
// FastForwardBy(2ms)
// ================================
// 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);
// Only valid for instances with a MOCK_TIME MainThreadType.
// 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();
// Only valid for instances with a MOCK_TIME MainThreadType. Returns a
......
......@@ -24,6 +24,7 @@
#include "base/test/test_timeouts.h"
#include "base/threading/platform_thread.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_task_runner_handle.h"
#include "base/time/clock.h"
......@@ -182,8 +183,6 @@ TEST_P(ScopedTaskEnvironmentTest, DelayedTasks) {
},
Unretained(&counter)),
kShortTaskDelay);
// TODO(gab): This currently doesn't run because the ThreadPool's clock
// isn't mocked but it should be.
PostDelayedTask(FROM_HERE,
BindOnce(
[](subtle::Atomic32* counter) {
......@@ -211,6 +210,28 @@ TEST_P(ScopedTaskEnvironmentTest, DelayedTasks) {
},
Unretained(&counter)),
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(
FROM_HERE, BindOnce(
......@@ -236,6 +257,8 @@ TEST_P(ScopedTaskEnvironmentTest, DelayedTasks) {
EXPECT_EQ(expected_value, counter);
if (GetParam() == ScopedTaskEnvironment::MainThreadType::MOCK_TIME) {
const TimeTicks start_time = scoped_task_environment.NowTicks();
// Delay inferior to the delay of the first posted task.
constexpr base::TimeDelta kInferiorTaskDelay = TimeDelta::FromSeconds(1);
static_assert(kInferiorTaskDelay < kShortTaskDelay,
......@@ -246,12 +269,19 @@ TEST_P(ScopedTaskEnvironmentTest, DelayedTasks) {
scoped_task_environment.FastForwardBy(kShortTaskDelay - kInferiorTaskDelay);
expected_value += 4;
expected_value += 128;
EXPECT_EQ(expected_value, counter);
scoped_task_environment.FastForwardUntilNoTasksRemain();
expected_value += 8;
expected_value += 16;
expected_value += 256;
expected_value += 512;
expected_value += 1024;
EXPECT_EQ(expected_value, counter);
EXPECT_EQ(scoped_task_environment.NowTicks() - start_time,
kLongTaskDelay * 4);
}
}
......@@ -456,7 +486,8 @@ TEST_F(ScopedTaskEnvironmentTest, NestedFastForwardBy) {
EXPECT_EQ(scoped_task_environment.NowTicks(), start_time + kDelayPerTask * 5);
}
TEST_F(ScopedTaskEnvironmentTest, CrossThreadTaskPostingDoesntAffectMockTime) {
TEST_F(ScopedTaskEnvironmentTest,
CrossThreadImmediateTaskPostingDoesntAffectMockTime) {
ScopedTaskEnvironment scoped_task_environment(
ScopedTaskEnvironment::MainThreadType::MOCK_TIME,
ScopedTaskEnvironment::NowSource::MAIN_THREAD_MOCK_TIME);
......@@ -499,6 +530,158 @@ TEST_F(ScopedTaskEnvironmentTest, CrossThreadTaskPostingDoesntAffectMockTime) {
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)
// Regression test to ensure that ScopedTaskEnvironment enables the MTA in the
// 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