/* * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include using namespace std::chrono_literals; using namespace android::mediautils; namespace { constexpr auto kJitter = 10ms; // Each task written by *ToString() will start with a left brace. constexpr char REQUEST_START = '{'; inline size_t countChars(std::string_view s, char c) { return std::count(s.begin(), s.end(), c); } // Split msec time between timeout and second chance time // This tests expiration times weighted between timeout and the second chance time. #define DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(msec, frac) \ std::chrono::milliseconds(int((msec) * (frac)) + 1), \ std::chrono::milliseconds(int((msec) * (1.f - (frac)))) // The TimerThreadTest is parameterized on a fraction between 0.f and 1.f which // is how the total timeout time is split between the first timeout and the second chance time. // class TimerThreadTest : public ::testing::TestWithParam { protected: static void testBasic() { const auto frac = GetParam(); std::atomic taskRan = false; TimerThread thread; TimerThread::Handle handle = thread.scheduleTask("Basic", [&taskRan](TimerThread::Handle) { taskRan = true; }, DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(100, frac)); ASSERT_TRUE(TimerThread::isTimeoutHandle(handle)); std::this_thread::sleep_for(100ms - kJitter); ASSERT_FALSE(taskRan); std::this_thread::sleep_for(2 * kJitter); ASSERT_TRUE(taskRan); // timed-out called. ASSERT_EQ(1ul, countChars(thread.timeoutToString(), REQUEST_START)); // nothing cancelled ASSERT_EQ(0ul, countChars(thread.retiredToString(), REQUEST_START)); } static void testCancel() { const auto frac = GetParam(); std::atomic taskRan = false; TimerThread thread; TimerThread::Handle handle = thread.scheduleTask("Cancel", [&taskRan](TimerThread::Handle) { taskRan = true; }, DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(100, frac)); ASSERT_TRUE(TimerThread::isTimeoutHandle(handle)); std::this_thread::sleep_for(100ms - kJitter); ASSERT_FALSE(taskRan); ASSERT_TRUE(thread.cancelTask(handle)); std::this_thread::sleep_for(2 * kJitter); ASSERT_FALSE(taskRan); // timed-out did not call. ASSERT_EQ(0ul, countChars(thread.timeoutToString(), REQUEST_START)); // task cancelled. ASSERT_EQ(1ul, countChars(thread.retiredToString(), REQUEST_START)); } static void testCancelAfterRun() { const auto frac = GetParam(); std::atomic taskRan = false; TimerThread thread; TimerThread::Handle handle = thread.scheduleTask("CancelAfterRun", [&taskRan](TimerThread::Handle) { taskRan = true; }, DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(100, frac)); ASSERT_TRUE(TimerThread::isTimeoutHandle(handle)); std::this_thread::sleep_for(100ms + kJitter); ASSERT_TRUE(taskRan); // timed-out called. ASSERT_FALSE(thread.cancelTask(handle)); ASSERT_EQ(1ul, countChars(thread.timeoutToString(), REQUEST_START)); // nothing actually cancelled ASSERT_EQ(0ul, countChars(thread.retiredToString(), REQUEST_START)); } static void testMultipleTasks() { const auto frac = GetParam(); std::array, 6> taskRan{}; TimerThread thread; auto startTime = std::chrono::steady_clock::now(); thread.scheduleTask("0", [&taskRan](TimerThread::Handle) { taskRan[0] = true; }, DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(300, frac)); thread.scheduleTask("1", [&taskRan](TimerThread::Handle) { taskRan[1] = true; }, DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(100, frac)); thread.scheduleTask("2", [&taskRan](TimerThread::Handle) { taskRan[2] = true; }, DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(200, frac)); thread.scheduleTask("3", [&taskRan](TimerThread::Handle) { taskRan[3] = true; }, DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(400, frac)); auto handle4 = thread.scheduleTask("4", [&taskRan](TimerThread::Handle) { taskRan[4] = true; }, DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(200, frac)); thread.scheduleTask("5", [&taskRan](TimerThread::Handle) { taskRan[5] = true; }, DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(200, frac)); // 6 tasks pending ASSERT_EQ(6ul, countChars(thread.pendingToString(), REQUEST_START)); // 0 tasks completed ASSERT_EQ(0ul, countChars(thread.retiredToString(), REQUEST_START)); // None of the tasks are expected to have finished at the start. std::array, 6> expected{}; // Task 1 should trigger around 100ms. std::this_thread::sleep_until(startTime + 100ms - kJitter); ASSERT_EQ(expected, taskRan); std::this_thread::sleep_until(startTime + 100ms + kJitter); expected[1] = true; ASSERT_EQ(expected, taskRan); // Cancel task 4 before it gets a chance to run. thread.cancelTask(handle4); // Tasks 2 and 5 should trigger around 200ms. std::this_thread::sleep_until(startTime + 200ms - kJitter); ASSERT_EQ(expected, taskRan); std::this_thread::sleep_until(startTime + 200ms + kJitter); expected[2] = true; expected[5] = true; ASSERT_EQ(expected, taskRan); // Task 0 should trigger around 300ms. std::this_thread::sleep_until(startTime + 300ms - kJitter); ASSERT_EQ(expected, taskRan); std::this_thread::sleep_until(startTime + 300ms + kJitter); expected[0] = true; ASSERT_EQ(expected, taskRan); // 1 task pending ASSERT_EQ(1ul, countChars(thread.pendingToString(), REQUEST_START)); // 4 tasks called on timeout, and 1 cancelled ASSERT_EQ(4ul, countChars(thread.timeoutToString(), REQUEST_START)); ASSERT_EQ(1ul, countChars(thread.retiredToString(), REQUEST_START)); // Task 3 should trigger around 400ms. std::this_thread::sleep_until(startTime + 400ms - kJitter); ASSERT_EQ(expected, taskRan); // 4 tasks called on timeout and 1 cancelled ASSERT_EQ(4ul, countChars(thread.timeoutToString(), REQUEST_START)); ASSERT_EQ(1ul, countChars(thread.retiredToString(), REQUEST_START)); std::this_thread::sleep_until(startTime + 400ms + kJitter); expected[3] = true; ASSERT_EQ(expected, taskRan); // 0 tasks pending ASSERT_EQ(0ul, countChars(thread.pendingToString(), REQUEST_START)); // 5 tasks called on timeout and 1 cancelled ASSERT_EQ(5ul, countChars(thread.timeoutToString(), REQUEST_START)); ASSERT_EQ(1ul, countChars(thread.retiredToString(), REQUEST_START)); } }; // class TimerThreadTest TEST_P(TimerThreadTest, Basic) { testBasic(); } TEST_P(TimerThreadTest, Cancel) { testCancel(); } TEST_P(TimerThreadTest, CancelAfterRun) { testCancelAfterRun(); } TEST_P(TimerThreadTest, MultipleTasks) { testMultipleTasks(); } INSTANTIATE_TEST_CASE_P( TimerThread, TimerThreadTest, ::testing::Values(0.f, 0.5f, 1.f) ); TEST(TimerThread, TrackedTasks) { TimerThread thread; auto handle0 = thread.trackTask("0"); auto handle1 = thread.trackTask("1"); auto handle2 = thread.trackTask("2"); ASSERT_TRUE(TimerThread::isNoTimeoutHandle(handle0)); ASSERT_TRUE(TimerThread::isNoTimeoutHandle(handle1)); ASSERT_TRUE(TimerThread::isNoTimeoutHandle(handle2)); // 3 tasks pending ASSERT_EQ(3ul, countChars(thread.pendingToString(), REQUEST_START)); // 0 tasks retired ASSERT_EQ(0ul, countChars(thread.retiredToString(), REQUEST_START)); ASSERT_TRUE(thread.cancelTask(handle0)); ASSERT_TRUE(thread.cancelTask(handle1)); // 1 task pending ASSERT_EQ(1ul, countChars(thread.pendingToString(), REQUEST_START)); // 2 tasks retired ASSERT_EQ(2ul, countChars(thread.retiredToString(), REQUEST_START)); // handle1 is stale, cancel returns false. ASSERT_FALSE(thread.cancelTask(handle1)); // 1 task pending ASSERT_EQ(1ul, countChars(thread.pendingToString(), REQUEST_START)); // 2 tasks retired ASSERT_EQ(2ul, countChars(thread.retiredToString(), REQUEST_START)); // Add another tracked task. auto handle3 = thread.trackTask("3"); ASSERT_TRUE(TimerThread::isNoTimeoutHandle(handle3)); // 2 tasks pending ASSERT_EQ(2ul, countChars(thread.pendingToString(), REQUEST_START)); // 2 tasks retired ASSERT_EQ(2ul, countChars(thread.retiredToString(), REQUEST_START)); ASSERT_TRUE(thread.cancelTask(handle2)); // 1 tasks pending ASSERT_EQ(1ul, countChars(thread.pendingToString(), REQUEST_START)); // 3 tasks retired ASSERT_EQ(3ul, countChars(thread.retiredToString(), REQUEST_START)); ASSERT_TRUE(thread.cancelTask(handle3)); // 0 tasks pending ASSERT_EQ(0ul, countChars(thread.pendingToString(), REQUEST_START)); // 4 tasks retired ASSERT_EQ(4ul, countChars(thread.retiredToString(), REQUEST_START)); } } // namespace