/* * 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 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 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 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(); // 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 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