|  | /* | 
|  | *  Copyright 2019 The WebRTC project authors. All Rights Reserved. | 
|  | * | 
|  | *  Use of this source code is governed by a BSD-style license | 
|  | *  that can be found in the LICENSE file in the root of the source | 
|  | *  tree. An additional intellectual property rights grant can be found | 
|  | *  in the file PATENTS.  All contributing project authors may | 
|  | *  be found in the AUTHORS file in the root of the source tree. | 
|  | */ | 
|  |  | 
|  | #include "rtc_base/task_utils/repeating_task.h" | 
|  |  | 
|  | #include <atomic> | 
|  | #include <memory> | 
|  |  | 
|  | #include "absl/functional/any_invocable.h" | 
|  | #include "api/task_queue/task_queue_base.h" | 
|  | #include "api/task_queue/test/mock_task_queue_base.h" | 
|  | #include "api/units/time_delta.h" | 
|  | #include "api/units/timestamp.h" | 
|  | #include "rtc_base/event.h" | 
|  | #include "rtc_base/task_queue_for_test.h" | 
|  | #include "system_wrappers/include/clock.h" | 
|  | #include "test/gmock.h" | 
|  | #include "test/gtest.h" | 
|  |  | 
|  | // NOTE: Since these tests rely on real time behavior, they will be flaky | 
|  | // if run on heavily loaded systems. | 
|  | namespace webrtc { | 
|  | namespace { | 
|  | using ::testing::AtLeast; | 
|  | using ::testing::Invoke; | 
|  | using ::testing::MockFunction; | 
|  | using ::testing::NiceMock; | 
|  | using ::testing::Return; | 
|  | using ::testing::WithArg; | 
|  |  | 
|  | constexpr TimeDelta kTimeout = TimeDelta::Millis(1000); | 
|  |  | 
|  | class MockClosure { | 
|  | public: | 
|  | MOCK_METHOD(TimeDelta, Call, ()); | 
|  | MOCK_METHOD(void, Delete, ()); | 
|  | }; | 
|  |  | 
|  | class MockTaskQueue : public MockTaskQueueBase { | 
|  | public: | 
|  | MockTaskQueue() : task_queue_setter_(this) {} | 
|  |  | 
|  | private: | 
|  | CurrentTaskQueueSetter task_queue_setter_; | 
|  | }; | 
|  |  | 
|  | class FakeTaskQueue : public TaskQueueBase { | 
|  | public: | 
|  | explicit FakeTaskQueue(SimulatedClock* clock) | 
|  | : task_queue_setter_(this), clock_(clock) {} | 
|  |  | 
|  | void Delete() override {} | 
|  |  | 
|  | void PostTaskImpl(absl::AnyInvocable<void() &&> task, | 
|  | const PostTaskTraits& /*traits*/, | 
|  | const Location& /*location*/) override { | 
|  | last_task_ = std::move(task); | 
|  | last_precision_ = absl::nullopt; | 
|  | last_delay_ = TimeDelta::Zero(); | 
|  | } | 
|  |  | 
|  | void PostDelayedTaskImpl(absl::AnyInvocable<void() &&> task, | 
|  | TimeDelta delay, | 
|  | const PostDelayedTaskTraits& traits, | 
|  | const Location& /*location*/) override { | 
|  | last_task_ = std::move(task); | 
|  | last_precision_ = traits.high_precision | 
|  | ? TaskQueueBase::DelayPrecision::kHigh | 
|  | : TaskQueueBase::DelayPrecision::kLow; | 
|  | last_delay_ = delay; | 
|  | } | 
|  |  | 
|  | bool AdvanceTimeAndRunLastTask() { | 
|  | EXPECT_TRUE(last_task_); | 
|  | EXPECT_TRUE(last_delay_.IsFinite()); | 
|  | clock_->AdvanceTime(last_delay_); | 
|  | last_delay_ = TimeDelta::MinusInfinity(); | 
|  | auto task = std::move(last_task_); | 
|  | std::move(task)(); | 
|  | return last_task_ == nullptr; | 
|  | } | 
|  |  | 
|  | bool IsTaskQueued() { return !!last_task_; } | 
|  |  | 
|  | TimeDelta last_delay() const { | 
|  | EXPECT_TRUE(last_delay_.IsFinite()); | 
|  | return last_delay_; | 
|  | } | 
|  |  | 
|  | absl::optional<TaskQueueBase::DelayPrecision> last_precision() const { | 
|  | return last_precision_; | 
|  | } | 
|  |  | 
|  | private: | 
|  | CurrentTaskQueueSetter task_queue_setter_; | 
|  | SimulatedClock* clock_; | 
|  | absl::AnyInvocable<void() &&> last_task_; | 
|  | TimeDelta last_delay_ = TimeDelta::MinusInfinity(); | 
|  | absl::optional<TaskQueueBase::DelayPrecision> last_precision_; | 
|  | }; | 
|  |  | 
|  | // NOTE: Since this utility class holds a raw pointer to a variable that likely | 
|  | // lives on the stack, it's important that any repeating tasks that use this | 
|  | // class be explicitly stopped when the test criteria have been met. If the | 
|  | // task is not stopped, an instance of this class can be deleted when the | 
|  | // pointed-to MockClosure has been deleted and we end up trying to call a | 
|  | // virtual method on a deleted object in the dtor. | 
|  | class MoveOnlyClosure { | 
|  | public: | 
|  | explicit MoveOnlyClosure(MockClosure* mock) : mock_(mock) {} | 
|  | MoveOnlyClosure(const MoveOnlyClosure&) = delete; | 
|  | MoveOnlyClosure(MoveOnlyClosure&& other) : mock_(other.mock_) { | 
|  | other.mock_ = nullptr; | 
|  | } | 
|  | ~MoveOnlyClosure() { | 
|  | if (mock_) | 
|  | mock_->Delete(); | 
|  | } | 
|  | TimeDelta operator()() { return mock_->Call(); } | 
|  |  | 
|  | private: | 
|  | MockClosure* mock_; | 
|  | }; | 
|  | }  // namespace | 
|  |  | 
|  | TEST(RepeatingTaskTest, TaskIsStoppedOnStop) { | 
|  | const TimeDelta kShortInterval = TimeDelta::Millis(50); | 
|  |  | 
|  | SimulatedClock clock(Timestamp::Zero()); | 
|  | FakeTaskQueue task_queue(&clock); | 
|  | std::atomic_int counter(0); | 
|  | auto handle = RepeatingTaskHandle::Start( | 
|  | &task_queue, | 
|  | [&] { | 
|  | counter++; | 
|  | return kShortInterval; | 
|  | }, | 
|  | TaskQueueBase::DelayPrecision::kLow, &clock); | 
|  | EXPECT_EQ(task_queue.last_delay(), TimeDelta::Zero()); | 
|  | EXPECT_FALSE(task_queue.AdvanceTimeAndRunLastTask()); | 
|  | EXPECT_EQ(counter.load(), 1); | 
|  |  | 
|  | // The handle reposted at the short interval. | 
|  | EXPECT_EQ(task_queue.last_delay(), kShortInterval); | 
|  |  | 
|  | // Stop the handle. This prevernts the counter from incrementing. | 
|  | handle.Stop(); | 
|  | EXPECT_TRUE(task_queue.AdvanceTimeAndRunLastTask()); | 
|  | EXPECT_EQ(counter.load(), 1); | 
|  | } | 
|  |  | 
|  | TEST(RepeatingTaskTest, CompensatesForLongRunTime) { | 
|  | const TimeDelta kRepeatInterval = TimeDelta::Millis(2); | 
|  | // Sleeping inside the task for longer than the repeat interval once, should | 
|  | // be compensated for by repeating the task faster to catch up. | 
|  | const TimeDelta kSleepDuration = TimeDelta::Millis(20); | 
|  |  | 
|  | std::atomic_int counter(0); | 
|  | SimulatedClock clock(Timestamp::Zero()); | 
|  | FakeTaskQueue task_queue(&clock); | 
|  | RepeatingTaskHandle::Start( | 
|  | &task_queue, | 
|  | [&] { | 
|  | ++counter; | 
|  | // Task takes longer than the repeat duration. | 
|  | clock.AdvanceTime(kSleepDuration); | 
|  | return kRepeatInterval; | 
|  | }, | 
|  | TaskQueueBase::DelayPrecision::kLow, &clock); | 
|  |  | 
|  | EXPECT_EQ(task_queue.last_delay(), TimeDelta::Zero()); | 
|  | EXPECT_FALSE(task_queue.AdvanceTimeAndRunLastTask()); | 
|  |  | 
|  | // Task is posted right away since it took longer to run then the repeat | 
|  | // interval. | 
|  | EXPECT_EQ(task_queue.last_delay(), TimeDelta::Zero()); | 
|  | EXPECT_EQ(counter.load(), 1); | 
|  | } | 
|  |  | 
|  | TEST(RepeatingTaskTest, CompensatesForShortRunTime) { | 
|  | SimulatedClock clock(Timestamp::Zero()); | 
|  | FakeTaskQueue task_queue(&clock); | 
|  | std::atomic_int counter(0); | 
|  | RepeatingTaskHandle::Start( | 
|  | &task_queue, | 
|  | [&] { | 
|  | // Simulate the task taking 100ms, which should be compensated for. | 
|  | counter++; | 
|  | clock.AdvanceTime(TimeDelta::Millis(100)); | 
|  | return TimeDelta::Millis(300); | 
|  | }, | 
|  | TaskQueueBase::DelayPrecision::kLow, &clock); | 
|  |  | 
|  | // Expect instant post task. | 
|  | EXPECT_EQ(task_queue.last_delay(), TimeDelta::Zero()); | 
|  | // Task should be retained by the handler since it is not cancelled. | 
|  | EXPECT_FALSE(task_queue.AdvanceTimeAndRunLastTask()); | 
|  | // New delay should be 200ms since repeat delay was 300ms but task took 100ms. | 
|  | EXPECT_EQ(task_queue.last_delay(), TimeDelta::Millis(200)); | 
|  | } | 
|  |  | 
|  | TEST(RepeatingTaskTest, CancelDelayedTaskBeforeItRuns) { | 
|  | rtc::Event done; | 
|  | MockClosure mock; | 
|  | EXPECT_CALL(mock, Call).Times(0); | 
|  | EXPECT_CALL(mock, Delete).WillOnce(Invoke([&done] { done.Set(); })); | 
|  | TaskQueueForTest task_queue("queue"); | 
|  | auto handle = RepeatingTaskHandle::DelayedStart( | 
|  | task_queue.Get(), TimeDelta::Millis(100), MoveOnlyClosure(&mock)); | 
|  | task_queue.PostTask( | 
|  | [handle = std::move(handle)]() mutable { handle.Stop(); }); | 
|  | EXPECT_TRUE(done.Wait(kTimeout)); | 
|  | } | 
|  |  | 
|  | TEST(RepeatingTaskTest, CancelTaskAfterItRuns) { | 
|  | rtc::Event done; | 
|  | MockClosure mock; | 
|  | EXPECT_CALL(mock, Call).WillOnce(Return(TimeDelta::Millis(100))); | 
|  | EXPECT_CALL(mock, Delete).WillOnce(Invoke([&done] { done.Set(); })); | 
|  | TaskQueueForTest task_queue("queue"); | 
|  | auto handle = | 
|  | RepeatingTaskHandle::Start(task_queue.Get(), MoveOnlyClosure(&mock)); | 
|  | task_queue.PostTask( | 
|  | [handle = std::move(handle)]() mutable { handle.Stop(); }); | 
|  | EXPECT_TRUE(done.Wait(kTimeout)); | 
|  | } | 
|  |  | 
|  | TEST(RepeatingTaskTest, TaskCanStopItself) { | 
|  | std::atomic_int counter(0); | 
|  | SimulatedClock clock(Timestamp::Zero()); | 
|  | FakeTaskQueue task_queue(&clock); | 
|  | RepeatingTaskHandle handle = RepeatingTaskHandle::Start(&task_queue, [&] { | 
|  | ++counter; | 
|  | handle.Stop(); | 
|  | return TimeDelta::Millis(2); | 
|  | }); | 
|  | EXPECT_EQ(task_queue.last_delay(), TimeDelta::Zero()); | 
|  | // Task cancelled itself so wants to be released. | 
|  | EXPECT_TRUE(task_queue.AdvanceTimeAndRunLastTask()); | 
|  | EXPECT_EQ(counter.load(), 1); | 
|  | } | 
|  |  | 
|  | TEST(RepeatingTaskTest, TaskCanStopItselfByReturningInfinity) { | 
|  | std::atomic_int counter(0); | 
|  | SimulatedClock clock(Timestamp::Zero()); | 
|  | FakeTaskQueue task_queue(&clock); | 
|  | RepeatingTaskHandle handle = RepeatingTaskHandle::Start(&task_queue, [&] { | 
|  | ++counter; | 
|  | return TimeDelta::PlusInfinity(); | 
|  | }); | 
|  | EXPECT_EQ(task_queue.last_delay(), TimeDelta::Zero()); | 
|  | // Task cancelled itself so wants to be released. | 
|  | EXPECT_TRUE(task_queue.AdvanceTimeAndRunLastTask()); | 
|  | EXPECT_EQ(counter.load(), 1); | 
|  | } | 
|  |  | 
|  | TEST(RepeatingTaskTest, ZeroReturnValueRepostsTheTask) { | 
|  | NiceMock<MockClosure> closure; | 
|  | rtc::Event done; | 
|  | EXPECT_CALL(closure, Call()) | 
|  | .WillOnce(Return(TimeDelta::Zero())) | 
|  | .WillOnce(Invoke([&] { | 
|  | done.Set(); | 
|  | return TimeDelta::PlusInfinity(); | 
|  | })); | 
|  | TaskQueueForTest task_queue("queue"); | 
|  | RepeatingTaskHandle::Start(task_queue.Get(), MoveOnlyClosure(&closure)); | 
|  | EXPECT_TRUE(done.Wait(kTimeout)); | 
|  | } | 
|  |  | 
|  | TEST(RepeatingTaskTest, StartPeriodicTask) { | 
|  | MockFunction<TimeDelta()> closure; | 
|  | rtc::Event done; | 
|  | EXPECT_CALL(closure, Call()) | 
|  | .WillOnce(Return(TimeDelta::Millis(20))) | 
|  | .WillOnce(Return(TimeDelta::Millis(20))) | 
|  | .WillOnce(Invoke([&] { | 
|  | done.Set(); | 
|  | return TimeDelta::PlusInfinity(); | 
|  | })); | 
|  | TaskQueueForTest task_queue("queue"); | 
|  | RepeatingTaskHandle::Start(task_queue.Get(), closure.AsStdFunction()); | 
|  | EXPECT_TRUE(done.Wait(kTimeout)); | 
|  | } | 
|  |  | 
|  | TEST(RepeatingTaskTest, Example) { | 
|  | class ObjectOnTaskQueue { | 
|  | public: | 
|  | void DoPeriodicTask() {} | 
|  | TimeDelta TimeUntilNextRun() { return TimeDelta::Millis(100); } | 
|  | void StartPeriodicTask(RepeatingTaskHandle* handle, | 
|  | TaskQueueBase* task_queue) { | 
|  | *handle = RepeatingTaskHandle::Start(task_queue, [this] { | 
|  | DoPeriodicTask(); | 
|  | return TimeUntilNextRun(); | 
|  | }); | 
|  | } | 
|  | }; | 
|  | TaskQueueForTest task_queue("queue"); | 
|  | auto object = std::make_unique<ObjectOnTaskQueue>(); | 
|  | // Create and start the periodic task. | 
|  | RepeatingTaskHandle handle; | 
|  | object->StartPeriodicTask(&handle, task_queue.Get()); | 
|  | // Restart the task | 
|  | task_queue.PostTask( | 
|  | [handle = std::move(handle)]() mutable { handle.Stop(); }); | 
|  | object->StartPeriodicTask(&handle, task_queue.Get()); | 
|  | task_queue.PostTask( | 
|  | [handle = std::move(handle)]() mutable { handle.Stop(); }); | 
|  | struct Destructor { | 
|  | void operator()() { object.reset(); } | 
|  | std::unique_ptr<ObjectOnTaskQueue> object; | 
|  | }; | 
|  | task_queue.PostTask(Destructor{std::move(object)}); | 
|  | // Do not wait for the destructor closure in order to create a race between | 
|  | // task queue destruction and running the desctructor closure. | 
|  | } | 
|  |  | 
|  | TEST(RepeatingTaskTest, ClockIntegration) { | 
|  | absl::AnyInvocable<void() &&> delayed_task; | 
|  | TimeDelta expected_delay = TimeDelta::Zero(); | 
|  | SimulatedClock clock(Timestamp::Zero()); | 
|  |  | 
|  | NiceMock<MockTaskQueue> task_queue; | 
|  | ON_CALL(task_queue, PostDelayedTaskImpl) | 
|  | .WillByDefault([&](absl::AnyInvocable<void() &&> task, TimeDelta delay, | 
|  | const MockTaskQueue::PostDelayedTaskTraits&, | 
|  | const Location&) { | 
|  | EXPECT_EQ(delay, expected_delay); | 
|  | delayed_task = std::move(task); | 
|  | }); | 
|  |  | 
|  | expected_delay = TimeDelta::Millis(100); | 
|  | RepeatingTaskHandle handle = RepeatingTaskHandle::DelayedStart( | 
|  | &task_queue, TimeDelta::Millis(100), | 
|  | [&clock]() { | 
|  | EXPECT_EQ(Timestamp::Millis(100), clock.CurrentTime()); | 
|  | // Simulate work happening for 10ms. | 
|  | clock.AdvanceTimeMilliseconds(10); | 
|  | return TimeDelta::Millis(100); | 
|  | }, | 
|  | TaskQueueBase::DelayPrecision::kLow, &clock); | 
|  |  | 
|  | clock.AdvanceTimeMilliseconds(100); | 
|  | absl::AnyInvocable<void()&&> task_to_run = std::move(delayed_task); | 
|  | expected_delay = TimeDelta::Millis(90); | 
|  | std::move(task_to_run)(); | 
|  | EXPECT_NE(delayed_task, nullptr); | 
|  | handle.Stop(); | 
|  | } | 
|  |  | 
|  | TEST(RepeatingTaskTest, CanBeStoppedAfterTaskQueueDeletedTheRepeatingTask) { | 
|  | absl::AnyInvocable<void() &&> repeating_task; | 
|  |  | 
|  | MockTaskQueue task_queue; | 
|  | EXPECT_CALL(task_queue, PostDelayedTaskImpl) | 
|  | .WillOnce(WithArg<0>([&](absl::AnyInvocable<void() &&> task) { | 
|  | repeating_task = std::move(task); | 
|  | })); | 
|  |  | 
|  | RepeatingTaskHandle handle = | 
|  | RepeatingTaskHandle::DelayedStart(&task_queue, TimeDelta::Millis(100), | 
|  | [] { return TimeDelta::Millis(100); }); | 
|  |  | 
|  | // shutdown task queue: delete all pending tasks and run 'regular' task. | 
|  | repeating_task = nullptr; | 
|  | handle.Stop(); | 
|  | } | 
|  |  | 
|  | TEST(RepeatingTaskTest, DefaultPrecisionIsLow) { | 
|  | SimulatedClock clock(Timestamp::Zero()); | 
|  | FakeTaskQueue task_queue(&clock); | 
|  | // Closure that repeats twice. | 
|  | MockFunction<TimeDelta()> closure; | 
|  | EXPECT_CALL(closure, Call()) | 
|  | .WillOnce(Return(TimeDelta::Millis(1))) | 
|  | .WillOnce(Return(TimeDelta::PlusInfinity())); | 
|  | RepeatingTaskHandle::Start(&task_queue, closure.AsStdFunction()); | 
|  | // Initial task is a PostTask(). | 
|  | EXPECT_FALSE(task_queue.last_precision().has_value()); | 
|  | EXPECT_FALSE(task_queue.AdvanceTimeAndRunLastTask()); | 
|  | // Repeated task is a delayed task with the default precision: low. | 
|  | EXPECT_TRUE(task_queue.last_precision().has_value()); | 
|  | EXPECT_EQ(task_queue.last_precision().value(), | 
|  | TaskQueueBase::DelayPrecision::kLow); | 
|  | // No more tasks. | 
|  | EXPECT_TRUE(task_queue.AdvanceTimeAndRunLastTask()); | 
|  | } | 
|  |  | 
|  | TEST(RepeatingTaskTest, CanSpecifyToPostTasksWithLowPrecision) { | 
|  | SimulatedClock clock(Timestamp::Zero()); | 
|  | FakeTaskQueue task_queue(&clock); | 
|  | // Closure that repeats twice. | 
|  | MockFunction<TimeDelta()> closure; | 
|  | EXPECT_CALL(closure, Call()) | 
|  | .WillOnce(Return(TimeDelta::Millis(1))) | 
|  | .WillOnce(Return(TimeDelta::PlusInfinity())); | 
|  | RepeatingTaskHandle::Start(&task_queue, closure.AsStdFunction(), | 
|  | TaskQueueBase::DelayPrecision::kLow); | 
|  | // Initial task is a PostTask(). | 
|  | EXPECT_FALSE(task_queue.last_precision().has_value()); | 
|  | EXPECT_FALSE(task_queue.AdvanceTimeAndRunLastTask()); | 
|  | // Repeated task is a delayed task with the specified precision. | 
|  | EXPECT_TRUE(task_queue.last_precision().has_value()); | 
|  | EXPECT_EQ(task_queue.last_precision().value(), | 
|  | TaskQueueBase::DelayPrecision::kLow); | 
|  | // No more tasks. | 
|  | EXPECT_TRUE(task_queue.AdvanceTimeAndRunLastTask()); | 
|  | } | 
|  |  | 
|  | TEST(RepeatingTaskTest, CanSpecifyToPostTasksWithHighPrecision) { | 
|  | SimulatedClock clock(Timestamp::Zero()); | 
|  | FakeTaskQueue task_queue(&clock); | 
|  | // Closure that repeats twice. | 
|  | MockFunction<TimeDelta()> closure; | 
|  | EXPECT_CALL(closure, Call()) | 
|  | .WillOnce(Return(TimeDelta::Millis(1))) | 
|  | .WillOnce(Return(TimeDelta::PlusInfinity())); | 
|  | RepeatingTaskHandle::Start(&task_queue, closure.AsStdFunction(), | 
|  | TaskQueueBase::DelayPrecision::kHigh); | 
|  | // Initial task is a PostTask(). | 
|  | EXPECT_FALSE(task_queue.last_precision().has_value()); | 
|  | EXPECT_FALSE(task_queue.AdvanceTimeAndRunLastTask()); | 
|  | // Repeated task is a delayed task with the specified precision. | 
|  | EXPECT_TRUE(task_queue.last_precision().has_value()); | 
|  | EXPECT_EQ(task_queue.last_precision().value(), | 
|  | TaskQueueBase::DelayPrecision::kHigh); | 
|  | // No more tasks. | 
|  | EXPECT_TRUE(task_queue.AdvanceTimeAndRunLastTask()); | 
|  | } | 
|  |  | 
|  | }  // namespace webrtc |