TimeCheck: Add second chance queue
Split the timeout into two waits.
This reduces the chance of false timeout when the monotonic clock
advances without an active CPU during an aborted suspend.
Removed default arguments for TimeCheck constructor
to avoid accidental argument confusion.
Test: overnight stress test
Test: atest libmediautils_test
Test: atest timecheck_tests
Bug: 227594853
Change-Id: If6507a053e5bf15ddd3a3f8f53bdc0d3643e5924
diff --git a/media/utils/tests/TimerThread-test.cpp b/media/utils/tests/TimerThread-test.cpp
index 8656f63..8a92a26 100644
--- a/media/utils/tests/TimerThread-test.cpp
+++ b/media/utils/tests/TimerThread-test.cpp
@@ -33,12 +33,27 @@
return std::count(s.begin(), s.end(), c);
}
-TEST(TimerThread, Basic) {
+
+// 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<float> {
+protected:
+
+static void testBasic() {
+ const auto frac = GetParam();
+
std::atomic<bool> taskRan = false;
TimerThread thread;
TimerThread::Handle handle =
thread.scheduleTask("Basic", [&taskRan](TimerThread::Handle handle __unused) {
- taskRan = true; }, 100ms);
+ taskRan = true; }, DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(100, frac));
ASSERT_TRUE(TimerThread::isTimeoutHandle(handle));
std::this_thread::sleep_for(100ms - kJitter);
ASSERT_FALSE(taskRan);
@@ -49,12 +64,14 @@
ASSERT_EQ(0ul, countChars(thread.retiredToString(), REQUEST_START));
}
-TEST(TimerThread, Cancel) {
+static void testCancel() {
+ const auto frac = GetParam();
+
std::atomic<bool> taskRan = false;
TimerThread thread;
TimerThread::Handle handle =
thread.scheduleTask("Cancel", [&taskRan](TimerThread::Handle handle __unused) {
- taskRan = true; }, 100ms);
+ taskRan = true; }, DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(100, frac));
ASSERT_TRUE(TimerThread::isTimeoutHandle(handle));
std::this_thread::sleep_for(100ms - kJitter);
ASSERT_FALSE(taskRan);
@@ -66,13 +83,16 @@
ASSERT_EQ(1ul, countChars(thread.retiredToString(), REQUEST_START));
}
-TEST(TimerThread, CancelAfterRun) {
+static void testCancelAfterRun() {
+ const auto frac = GetParam();
+
std::atomic<bool> taskRan = false;
TimerThread thread;
TimerThread::Handle handle =
thread.scheduleTask("CancelAfterRun",
[&taskRan](TimerThread::Handle handle __unused) {
- taskRan = true; }, 100ms);
+ 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.
@@ -82,83 +102,70 @@
ASSERT_EQ(0ul, countChars(thread.retiredToString(), REQUEST_START));
}
-TEST(TimerThread, MultipleTasks) {
+static void testMultipleTasks() {
+ const auto frac = GetParam();
+
std::array<std::atomic<bool>, 6> taskRan{};
TimerThread thread;
auto startTime = std::chrono::steady_clock::now();
thread.scheduleTask("0", [&taskRan](TimerThread::Handle handle __unused) {
- taskRan[0] = true; }, 300ms);
+ taskRan[0] = true; }, DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(300, frac));
thread.scheduleTask("1", [&taskRan](TimerThread::Handle handle __unused) {
- taskRan[1] = true; }, 100ms);
+ taskRan[1] = true; }, DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(100, frac));
thread.scheduleTask("2", [&taskRan](TimerThread::Handle handle __unused) {
- taskRan[2] = true; }, 200ms);
+ taskRan[2] = true; }, DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(200, frac));
thread.scheduleTask("3", [&taskRan](TimerThread::Handle handle __unused) {
- taskRan[3] = true; }, 400ms);
+ taskRan[3] = true; }, DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(400, frac));
auto handle4 = thread.scheduleTask("4", [&taskRan](TimerThread::Handle handle __unused) {
- taskRan[4] = true; }, 200ms);
+ taskRan[4] = true; }, DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(200, frac));
thread.scheduleTask("5", [&taskRan](TimerThread::Handle handle __unused) {
- taskRan[5] = true; }, 200ms);
+ 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<std::atomic<bool>, 6> expected{};
+
// Task 1 should trigger around 100ms.
std::this_thread::sleep_until(startTime + 100ms - kJitter);
- ASSERT_FALSE(taskRan[0]);
- ASSERT_FALSE(taskRan[1]);
- ASSERT_FALSE(taskRan[2]);
- ASSERT_FALSE(taskRan[3]);
- ASSERT_FALSE(taskRan[4]);
- ASSERT_FALSE(taskRan[5]);
+
+ ASSERT_EQ(expected, taskRan);
+
std::this_thread::sleep_until(startTime + 100ms + kJitter);
- ASSERT_FALSE(taskRan[0]);
- ASSERT_TRUE(taskRan[1]);
- ASSERT_FALSE(taskRan[2]);
- ASSERT_FALSE(taskRan[3]);
- ASSERT_FALSE(taskRan[4]);
- ASSERT_FALSE(taskRan[5]);
+
+ 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_FALSE(taskRan[0]);
- ASSERT_TRUE(taskRan[1]);
- ASSERT_FALSE(taskRan[2]);
- ASSERT_FALSE(taskRan[3]);
- ASSERT_FALSE(taskRan[4]);
- ASSERT_FALSE(taskRan[5]);
+
+ ASSERT_EQ(expected, taskRan);
+
std::this_thread::sleep_until(startTime + 200ms + kJitter);
- ASSERT_FALSE(taskRan[0]);
- ASSERT_TRUE(taskRan[1]);
- ASSERT_TRUE(taskRan[2]);
- ASSERT_FALSE(taskRan[3]);
- ASSERT_FALSE(taskRan[4]);
- ASSERT_TRUE(taskRan[5]);
+
+ 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_FALSE(taskRan[0]);
- ASSERT_TRUE(taskRan[1]);
- ASSERT_TRUE(taskRan[2]);
- ASSERT_FALSE(taskRan[3]);
- ASSERT_FALSE(taskRan[4]);
- ASSERT_TRUE(taskRan[5]);
+
+ ASSERT_EQ(expected, taskRan);
std::this_thread::sleep_until(startTime + 300ms + kJitter);
- ASSERT_TRUE(taskRan[0]);
- ASSERT_TRUE(taskRan[1]);
- ASSERT_TRUE(taskRan[2]);
- ASSERT_FALSE(taskRan[3]);
- ASSERT_FALSE(taskRan[4]);
- ASSERT_TRUE(taskRan[5]);
+
+ expected[0] = true;
+ ASSERT_EQ(expected, taskRan);
// 1 task pending
ASSERT_EQ(1ul, countChars(thread.pendingToString(), REQUEST_START));
@@ -168,24 +175,17 @@
// Task 3 should trigger around 400ms.
std::this_thread::sleep_until(startTime + 400ms - kJitter);
- ASSERT_TRUE(taskRan[0]);
- ASSERT_TRUE(taskRan[1]);
- ASSERT_TRUE(taskRan[2]);
- ASSERT_FALSE(taskRan[3]);
- ASSERT_FALSE(taskRan[4]);
- ASSERT_TRUE(taskRan[5]);
+
+ 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);
- ASSERT_TRUE(taskRan[0]);
- ASSERT_TRUE(taskRan[1]);
- ASSERT_TRUE(taskRan[2]);
- ASSERT_TRUE(taskRan[3]);
- ASSERT_FALSE(taskRan[4]);
- ASSERT_TRUE(taskRan[5]);
+
+ expected[3] = true;
+ ASSERT_EQ(expected, taskRan);
// 0 tasks pending
ASSERT_EQ(0ul, countChars(thread.pendingToString(), REQUEST_START));
@@ -194,6 +194,30 @@
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;