mirror of
https://github.com/mollyim/webrtc.git
synced 2025-05-16 15:20:42 +01:00

using shared pointer to boolean flag. Bug: None Change-Id: I9d7ad7d7b187fefa7daa0247a1379e1ddd7e2b24 Reviewed-on: https://webrtc-review.googlesource.com/96300 Commit-Queue: Danil Chapovalov <danilchap@webrtc.org> Reviewed-by: Karl Wiberg <kwiberg@webrtc.org> Reviewed-by: Per Kjellander <perkj@webrtc.org> Cr-Commit-Position: refs/heads/master@{#24511}
228 lines
6.8 KiB
C++
228 lines
6.8 KiB
C++
/*
|
|
* Copyright 2018 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/cancelable_periodic_task.h"
|
|
|
|
#include "rtc_base/event.h"
|
|
#include "test/gmock.h"
|
|
|
|
namespace {
|
|
|
|
using ::testing::AtLeast;
|
|
using ::testing::Invoke;
|
|
using ::testing::NiceMock;
|
|
using ::testing::MockFunction;
|
|
using ::testing::Return;
|
|
|
|
constexpr int kTimeoutMs = 1000;
|
|
|
|
class MockClosure {
|
|
public:
|
|
MOCK_METHOD0(Call, int());
|
|
MOCK_METHOD0(Delete, void());
|
|
};
|
|
|
|
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();
|
|
}
|
|
int operator()() { return mock_->Call(); }
|
|
|
|
private:
|
|
MockClosure* mock_;
|
|
};
|
|
|
|
class CopyableClosure {
|
|
public:
|
|
explicit CopyableClosure(MockClosure* mock) : mock_(mock) {}
|
|
CopyableClosure(const CopyableClosure& other) : mock_(other.mock_) {}
|
|
~CopyableClosure() {
|
|
if (mock_) {
|
|
mock_->Delete();
|
|
mock_ = nullptr;
|
|
}
|
|
}
|
|
int operator()() { return mock_->Call(); }
|
|
|
|
private:
|
|
MockClosure* mock_;
|
|
};
|
|
|
|
TEST(CancelablePeriodicTaskTest, CanCallCancelOnEmptyHandle) {
|
|
rtc::CancelableTaskHandle handle;
|
|
handle.Cancel();
|
|
}
|
|
|
|
TEST(CancelablePeriodicTaskTest, CancelTaskBeforeItRuns) {
|
|
rtc::Event done(false, false);
|
|
MockClosure mock;
|
|
EXPECT_CALL(mock, Call).Times(0);
|
|
EXPECT_CALL(mock, Delete).WillOnce(Invoke([&done] { done.Set(); }));
|
|
|
|
rtc::TaskQueue task_queue("queue");
|
|
|
|
auto task = rtc::CreateCancelablePeriodicTask(MoveOnlyClosure(&mock));
|
|
rtc::CancelableTaskHandle handle = task->GetCancellationHandle();
|
|
task_queue.PostTask([handle] { handle.Cancel(); });
|
|
task_queue.PostTask(std::move(task));
|
|
|
|
EXPECT_TRUE(done.Wait(kTimeoutMs));
|
|
}
|
|
|
|
TEST(CancelablePeriodicTaskTest, CancelDelayedTaskBeforeItRuns) {
|
|
rtc::Event done(false, false);
|
|
MockClosure mock;
|
|
EXPECT_CALL(mock, Call).Times(0);
|
|
EXPECT_CALL(mock, Delete).WillOnce(Invoke([&done] { done.Set(); }));
|
|
|
|
rtc::TaskQueue task_queue("queue");
|
|
|
|
auto task = rtc::CreateCancelablePeriodicTask(MoveOnlyClosure(&mock));
|
|
rtc::CancelableTaskHandle handle = task->GetCancellationHandle();
|
|
task_queue.PostDelayedTask(std::move(task), 100);
|
|
task_queue.PostTask([handle] { handle.Cancel(); });
|
|
|
|
EXPECT_TRUE(done.Wait(kTimeoutMs));
|
|
}
|
|
|
|
TEST(CancelablePeriodicTaskTest, CancelTaskAfterItRuns) {
|
|
rtc::Event done(false, false);
|
|
MockClosure mock;
|
|
EXPECT_CALL(mock, Call).WillOnce(Return(100));
|
|
EXPECT_CALL(mock, Delete).WillOnce(Invoke([&done] { done.Set(); }));
|
|
|
|
rtc::TaskQueue task_queue("queue");
|
|
|
|
auto task = rtc::CreateCancelablePeriodicTask(MoveOnlyClosure(&mock));
|
|
rtc::CancelableTaskHandle handle = task->GetCancellationHandle();
|
|
task_queue.PostTask(std::move(task));
|
|
task_queue.PostTask([handle] { handle.Cancel(); });
|
|
|
|
EXPECT_TRUE(done.Wait(kTimeoutMs));
|
|
}
|
|
|
|
TEST(CancelablePeriodicTaskTest, ZeroReturnValueRepostsTheTask) {
|
|
NiceMock<MockClosure> closure;
|
|
rtc::Event done(false, false);
|
|
EXPECT_CALL(closure, Call()).WillOnce(Return(0)).WillOnce(Invoke([&done] {
|
|
done.Set();
|
|
return kTimeoutMs;
|
|
}));
|
|
rtc::TaskQueue task_queue("queue");
|
|
task_queue.PostTask(
|
|
rtc::CreateCancelablePeriodicTask(MoveOnlyClosure(&closure)));
|
|
EXPECT_TRUE(done.Wait(kTimeoutMs));
|
|
}
|
|
|
|
TEST(CancelablePeriodicTaskTest, StartPeriodicTask) {
|
|
MockFunction<int()> closure;
|
|
rtc::Event done(false, false);
|
|
EXPECT_CALL(closure, Call())
|
|
.WillOnce(Return(20))
|
|
.WillOnce(Return(20))
|
|
.WillOnce(Invoke([&done] {
|
|
done.Set();
|
|
return kTimeoutMs;
|
|
}));
|
|
rtc::TaskQueue task_queue("queue");
|
|
task_queue.PostTask(
|
|
rtc::CreateCancelablePeriodicTask(closure.AsStdFunction()));
|
|
EXPECT_TRUE(done.Wait(kTimeoutMs));
|
|
}
|
|
|
|
// Validates perfect forwarding doesn't keep reference to deleted copy.
|
|
TEST(CancelablePeriodicTaskTest, CreateWithCopyOfAClosure) {
|
|
rtc::Event done(false, false);
|
|
MockClosure mock;
|
|
EXPECT_CALL(mock, Call).WillOnce(Invoke([&done] {
|
|
done.Set();
|
|
return kTimeoutMs;
|
|
}));
|
|
EXPECT_CALL(mock, Delete).Times(AtLeast(2));
|
|
CopyableClosure closure(&mock);
|
|
std::unique_ptr<rtc::BaseCancelableTask> task;
|
|
{
|
|
CopyableClosure copy = closure;
|
|
task = rtc::CreateCancelablePeriodicTask(copy);
|
|
}
|
|
|
|
rtc::TaskQueue task_queue("queue");
|
|
task_queue.PostTask(std::move(task));
|
|
EXPECT_TRUE(done.Wait(kTimeoutMs));
|
|
}
|
|
|
|
TEST(CancelablePeriodicTaskTest, DeletingHandleDoesntStopTheTask) {
|
|
rtc::Event run(false, false);
|
|
rtc::TaskQueue task_queue("queue");
|
|
auto task = rtc::CreateCancelablePeriodicTask(([&] {
|
|
run.Set();
|
|
return kTimeoutMs;
|
|
}));
|
|
rtc::CancelableTaskHandle handle = task->GetCancellationHandle();
|
|
handle = {}; // delete the handle.
|
|
task_queue.PostTask(std::move(task));
|
|
EXPECT_TRUE(run.Wait(kTimeoutMs));
|
|
}
|
|
|
|
// Example to test there are no thread races and use after free for suggested
|
|
// typical usage of the CancelablePeriodicTask
|
|
TEST(CancelablePeriodicTaskTest, Example) {
|
|
class ObjectOnTaskQueue {
|
|
public:
|
|
void DoPeriodicTask() {}
|
|
int TimeUntilNextRunMs() { return 100; }
|
|
|
|
rtc::CancelableTaskHandle StartPeriodicTask(rtc::TaskQueue* task_queue) {
|
|
auto periodic_task = rtc::CreateCancelablePeriodicTask([this] {
|
|
DoPeriodicTask();
|
|
return TimeUntilNextRunMs();
|
|
});
|
|
rtc::CancelableTaskHandle handle = periodic_task->GetCancellationHandle();
|
|
task_queue->PostTask(std::move(periodic_task));
|
|
return handle;
|
|
}
|
|
};
|
|
|
|
rtc::TaskQueue task_queue("queue");
|
|
|
|
auto object = absl::make_unique<ObjectOnTaskQueue>();
|
|
// Create and start the periodic task.
|
|
rtc::CancelableTaskHandle handle = object->StartPeriodicTask(&task_queue);
|
|
|
|
// Restart the task
|
|
task_queue.PostTask([handle] { handle.Cancel(); });
|
|
handle = object->StartPeriodicTask(&task_queue);
|
|
|
|
// Stop the task and destroy the object.
|
|
struct Destructor {
|
|
void operator()() {
|
|
// Cancel must be run on the task_queue, but if task failed to start
|
|
// because of task queue destruction, there is no need to run Cancel.
|
|
handle.Cancel();
|
|
}
|
|
// Destruction will happen either on the task queue or because task
|
|
// queue is destroyed.
|
|
|
|
std::unique_ptr<ObjectOnTaskQueue> object;
|
|
rtc::CancelableTaskHandle handle;
|
|
};
|
|
task_queue.PostTask(Destructor{std::move(object), std::move(handle)});
|
|
// Do not wait for the Destructor closure in order to create a race between
|
|
// task queue destruction and running the Desctructor closure.
|
|
}
|
|
|
|
} // namespace
|