mirror of
https://github.com/mollyim/webrtc.git
synced 2025-05-12 21:30:45 +01:00

Bug: None Change-Id: I4abece77b021a866175253cbb2bd212ff618910c Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/341022 Auto-Submit: Danil Chapovalov <danilchap@webrtc.org> Commit-Queue: Mirko Bonadei <mbonadei@webrtc.org> Reviewed-by: Jeremy Leconte <jleconte@google.com> Reviewed-by: Mirko Bonadei <mbonadei@webrtc.org> Cr-Commit-Position: refs/heads/main@{#41813}
440 lines
15 KiB
C++
440 lines
15 KiB
C++
/*
|
|
* 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 "absl/types/optional.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
|