| /* |
| * 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 "absl/memory/memory.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; |
| 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; |
| 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; |
| 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; |
| 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; |
| 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; |
| 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; |
| 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 |