Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1c7fb942 authored by Andy Hung's avatar Andy Hung Committed by Android (Google) Code Review
Browse files

Merge changes If6507a05,Ie35de689 into tm-qpr-dev

* changes:
  TimeCheck: Add second chance queue
  TimeCheck: Use clock monotonic for elapsed time
parents a803f790 741b3dd8
Loading
Loading
Loading
Loading
+77 −19
Original line number Diff line number Diff line
@@ -139,22 +139,24 @@ std::string TimeCheck::toString() {
    return getTimeCheckThread().toString();
}

TimeCheck::TimeCheck(std::string_view tag, OnTimerFunc&& onTimer, uint32_t timeoutMs,
        bool crashOnTimeout)
TimeCheck::TimeCheck(std::string_view tag, OnTimerFunc&& onTimer, Duration requestedTimeoutDuration,
        Duration secondChanceDuration, bool crashOnTimeout)
    : mTimeCheckHandler{ std::make_shared<TimeCheckHandler>(
            tag, std::move(onTimer), crashOnTimeout,
            std::chrono::system_clock::now(), gettid()) }
    , mTimerHandle(timeoutMs == 0
            tag, std::move(onTimer), crashOnTimeout, requestedTimeoutDuration,
            secondChanceDuration, std::chrono::system_clock::now(), gettid()) }
    , mTimerHandle(requestedTimeoutDuration.count() == 0
              /* for TimeCheck we don't consider a non-zero secondChanceDuration here */
              ? getTimeCheckThread().trackTask(mTimeCheckHandler->tag)
              : getTimeCheckThread().scheduleTask(
                      mTimeCheckHandler->tag,
                      // Pass in all the arguments by value to this task for safety.
                      // The thread could call the callback before the constructor is finished.
                      // The destructor is not blocked on callback.
                      [ timeCheckHandler = mTimeCheckHandler ] {
                          timeCheckHandler->onTimeout();
                      [ timeCheckHandler = mTimeCheckHandler ](TimerThread::Handle timerHandle) {
                          timeCheckHandler->onTimeout(timerHandle);
                      },
                      std::chrono::milliseconds(timeoutMs))) {}
                      requestedTimeoutDuration,
                      secondChanceDuration)) {}

TimeCheck::~TimeCheck() {
    if (mTimeCheckHandler) {
@@ -162,23 +164,77 @@ TimeCheck::~TimeCheck() {
    }
}

/* static */
std::string TimeCheck::analyzeTimeouts(
        float requestedTimeoutMs, float elapsedSteadyMs, float elapsedSystemMs) {
    // Track any OS clock issues with suspend.
    // It is possible that the elapsedSystemMs is much greater than elapsedSteadyMs if
    // a suspend occurs; however, we always expect the timeout ms should always be slightly
    // less than the elapsed steady ms regardless of whether a suspend occurs or not.

    std::string s("Timeout ms ");
    s.append(std::to_string(requestedTimeoutMs))
        .append(" elapsed steady ms ").append(std::to_string(elapsedSteadyMs))
        .append(" elapsed system ms ").append(std::to_string(elapsedSystemMs));

    // Is there something unusual?
    static constexpr float TOLERANCE_CONTEXT_SWITCH_MS = 200.f;

    if (requestedTimeoutMs > elapsedSteadyMs || requestedTimeoutMs > elapsedSystemMs) {
        s.append("\nError: early expiration - "
                "requestedTimeoutMs should be less than elapsed time");
    }

    if (elapsedSteadyMs > elapsedSystemMs + TOLERANCE_CONTEXT_SWITCH_MS) {
        s.append("\nWarning: steady time should not advance faster than system time");
    }

    // This has been found in suspend stress testing.
    if (elapsedSteadyMs > requestedTimeoutMs + TOLERANCE_CONTEXT_SWITCH_MS) {
        s.append("\nWarning: steady time significantly exceeds timeout "
                "- possible thread stall or aborted suspend");
    }

    // This has been found in suspend stress testing.
    if (elapsedSystemMs > requestedTimeoutMs + TOLERANCE_CONTEXT_SWITCH_MS) {
        s.append("\nInformation: system time significantly exceeds timeout "
                "- possible suspend");
    }
    return s;
}

// To avoid any potential race conditions, the timer handle
// (expiration = clock steady start + timeout) is passed into the callback.
void TimeCheck::TimeCheckHandler::onCancel(TimerThread::Handle timerHandle) const
{
    if (TimeCheck::getTimeCheckThread().cancelTask(timerHandle) && onTimer) {
        const std::chrono::system_clock::time_point endTime = std::chrono::system_clock::now();
        onTimer(false /* timeout */,
                std::chrono::duration_cast<std::chrono::duration<float, std::milli>>(
                        endTime - startTime).count());
        const std::chrono::steady_clock::time_point endSteadyTime =
                std::chrono::steady_clock::now();
        const float elapsedSteadyMs = std::chrono::duration_cast<FloatMs>(
                endSteadyTime - timerHandle + timeoutDuration).count();
        // send the elapsed steady time for statistics.
        onTimer(false /* timeout */, elapsedSteadyMs);
    }
}

void TimeCheck::TimeCheckHandler::onTimeout() const
// To avoid any potential race conditions, the timer handle
// (expiration = clock steady start + timeout) is passed into the callback.
void TimeCheck::TimeCheckHandler::onTimeout(TimerThread::Handle timerHandle) const
{
    const std::chrono::system_clock::time_point endTime = std::chrono::system_clock::now();
    const std::chrono::steady_clock::time_point endSteadyTime = std::chrono::steady_clock::now();
    const std::chrono::system_clock::time_point endSystemTime = std::chrono::system_clock::now();
    // timerHandle incorporates the timeout
    const float elapsedSteadyMs = std::chrono::duration_cast<FloatMs>(
            endSteadyTime - (timerHandle - timeoutDuration)).count();
    const float elapsedSystemMs = std::chrono::duration_cast<FloatMs>(
            endSystemTime - startSystemTime).count();
    const float requestedTimeoutMs = std::chrono::duration_cast<FloatMs>(
            timeoutDuration).count();
    const float secondChanceMs = std::chrono::duration_cast<FloatMs>(
            secondChanceDuration).count();

    if (onTimer) {
        onTimer(true /* timeout */,
                std::chrono::duration_cast<std::chrono::duration<float, std::milli>>(
                        endTime - startTime).count());
        onTimer(true /* timeout */, elapsedSteadyMs);
    }

    if (!crashOnTimeout) return;
@@ -208,8 +264,10 @@ void TimeCheck::TimeCheckHandler::onTimeout() const
    // Create abort message string - caution: this can be very large.
    const std::string abortMessage = std::string("TimeCheck timeout for ")
            .append(tag)
            .append(" scheduled ").append(formatTime(startTime))
            .append(" scheduled ").append(formatTime(startSystemTime))
            .append(" on thread ").append(std::to_string(tid)).append("\n")
            .append(analyzeTimeouts(requestedTimeoutMs + secondChanceMs,
                    elapsedSteadyMs, elapsedSystemMs)).append("\n")
            .append(halPids).append("\n")
            .append(summary);

@@ -241,7 +299,7 @@ mediautils::TimeCheck makeTimeCheckStatsForClassMethod(
                    } else {
                        stats->event(safeMethodName.asStringView(), elapsedMs);
                    }
            }, 0 /* timeoutMs */);
            }, {} /* timeoutDuration */, {} /* secondChanceDuration */, false /* crashOnTimeout */);
}

}  // namespace android::mediautils
+104 −61
Original line number Diff line number Diff line
@@ -33,10 +33,28 @@ inline size_t countChars(std::string_view s, char c) {
    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;
    thread.scheduleTask("Basic", [&taskRan] { taskRan = true; }, 100ms);
    TimerThread::Handle handle =
            thread.scheduleTask("Basic", [&taskRan](TimerThread::Handle handle __unused) {
                    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);
@@ -44,11 +62,15 @@ TEST(TimerThread, Basic) {
    ASSERT_EQ(1, 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] { taskRan = true; }, 100ms);
            thread.scheduleTask("Cancel", [&taskRan](TimerThread::Handle handle __unused) {
                    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));
@@ -57,88 +79,87 @@ TEST(TimerThread, Cancel) {
    ASSERT_EQ(1, 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] { taskRan = true; }, 100ms);
            thread.scheduleTask("CancelAfterRun",
                    [&taskRan](TimerThread::Handle handle __unused) {
                            taskRan = true; },
                            DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(100, frac));
    ASSERT_TRUE(TimerThread::isTimeoutHandle(handle));
    std::this_thread::sleep_for(100ms + kJitter);
    ASSERT_TRUE(taskRan);
    ASSERT_FALSE(thread.cancelTask(handle));
    ASSERT_EQ(1, 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] { taskRan[0] = true; }, 300ms);
    thread.scheduleTask("1", [&taskRan] { taskRan[1] = true; }, 100ms);
    thread.scheduleTask("2", [&taskRan] { taskRan[2] = true; }, 200ms);
    thread.scheduleTask("3", [&taskRan] { taskRan[3] = true; }, 400ms);
    auto handle4 = thread.scheduleTask("4", [&taskRan] { taskRan[4] = true; }, 200ms);
    thread.scheduleTask("5", [&taskRan] { taskRan[5] = true; }, 200ms);
    thread.scheduleTask("0", [&taskRan](TimerThread::Handle handle __unused) {
            taskRan[0] = true; }, DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(300, frac));
    thread.scheduleTask("1", [&taskRan](TimerThread::Handle handle __unused) {
            taskRan[1] = true; }, DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(100, frac));
    thread.scheduleTask("2", [&taskRan](TimerThread::Handle handle __unused) {
            taskRan[2] = true; }, DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(200, frac));
    thread.scheduleTask("3", [&taskRan](TimerThread::Handle handle __unused) {
            taskRan[3] = true; }, DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(400, frac));
    auto handle4 = thread.scheduleTask("4", [&taskRan](TimerThread::Handle handle __unused) {
            taskRan[4] = true; }, DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(200, frac));
    thread.scheduleTask("5", [&taskRan](TimerThread::Handle handle __unused) {
            taskRan[5] = true; }, DISTRIBUTE_TIMEOUT_SECONDCHANCE_MS_FRAC(200, frac));

    // 6 tasks pending
    ASSERT_EQ(6, countChars(thread.pendingToString(), REQUEST_START));
    // 0 tasks completed
    ASSERT_EQ(0, 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(1, countChars(thread.pendingToString(), REQUEST_START));
@@ -147,23 +168,16 @@ TEST(TimerThread, MultipleTasks) {

    // 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 ran and 1 cancelled
    ASSERT_EQ(4 + 1, 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(0, countChars(thread.pendingToString(), REQUEST_START));
@@ -171,6 +185,30 @@ TEST(TimerThread, MultipleTasks) {
    ASSERT_EQ(5 + 1, 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;

@@ -178,6 +216,10 @@ TEST(TimerThread, TrackedTasks) {
    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(3, countChars(thread.pendingToString(), REQUEST_START));
    // 0 tasks retired
@@ -201,6 +243,7 @@ TEST(TimerThread, TrackedTasks) {

    // Add another tracked task.
    auto handle3 = thread.trackTask("3");
    ASSERT_TRUE(TimerThread::isNoTimeoutHandle(handle3));

    // 2 tasks pending
    ASSERT_EQ(2, countChars(thread.pendingToString(), REQUEST_START));
+110 −41

File changed.

Preview size limit exceeded, changes collapsed.

+3 −1
Original line number Diff line number Diff line
@@ -48,7 +48,9 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
    std::string name = data_provider.ConsumeRandomLengthString(kMaxStringLen);

    // 3. The constructor, which is fuzzed here:
    android::mediautils::TimeCheck timeCheck(name.c_str(), {} /* onTimer */, timeoutMs);
    android::mediautils::TimeCheck timeCheck(name.c_str(), {} /* onTimer */,
            std::chrono::milliseconds(timeoutMs),
            {} /* secondChanceDuration */, true /* crashOnTimeout */);
    // We will leave some buffer to avoid sleeping too long
    uint8_t sleep_amount_ms = data_provider.ConsumeIntegralInRange<uint8_t>(0, timeoutMs / 2);

+47 −13
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

#pragma once

#include <chrono>
#include <vector>

#include <mediautils/TimerThread.h>
@@ -27,10 +28,33 @@ namespace android::mediautils {

class TimeCheck {
  public:

    // Duration for TimeCheck is based on steady_clock, typically nanoseconds.
    using Duration = std::chrono::steady_clock::duration;

    // Duration for printing is in milliseconds, using float for additional precision.
    using FloatMs = std::chrono::duration<float, std::milli>;

    // OnTimerFunc is the callback function with 2 parameters.
    //  bool timeout  (which is true when the TimeCheck object
    //                 times out, false when the TimeCheck object is
    //                 destroyed or leaves scope before the timer expires.)
    //  float elapsedMs (the elapsed time to this event).
    using OnTimerFunc = std::function<void(bool /* timeout */, float /* elapsedMs */ )>;

    // The default timeout is chosen to be less than system server watchdog timeout
    static constexpr uint32_t kDefaultTimeOutMs = 5000;
    // Note: kDefaultTimeOutMs should be no less than 2 seconds, otherwise spurious timeouts
    // may occur with system suspend.
    static constexpr TimeCheck::Duration kDefaultTimeoutDuration = std::chrono::milliseconds(3000);

    // Due to suspend abort not incrementing the monotonic clock,
    // we allow another second chance timeout after the first timeout expires.
    //
    // The total timeout is therefore kDefaultTimeoutDuration + kDefaultSecondChanceDuration,
    // and the result is more stable when the monotonic clock increments during suspend.
    //
    static constexpr TimeCheck::Duration kDefaultSecondChanceDuration =
            std::chrono::milliseconds(2000);

    /**
     * TimeCheck is a RAII object which will notify a callback
@@ -44,24 +68,24 @@ class TimeCheck {
     * the deallocation.
     *
     * \param tag       string associated with the TimeCheck object.
     * \param onTimer   callback function with 2 parameters
     *                      bool timeout  (which is true when the TimeCheck object
     *                                    times out, false when the TimeCheck object is
     *                                    destroyed or leaves scope before the timer expires.)
     *                      float elapsedMs (the elapsed time to this event).
     * \param onTimer   callback function with 2 parameters (described above in OnTimerFunc).
     *                  The callback when timeout is true will be called on a different thread.
     *                  This will cancel the callback on the destructor but is not guaranteed
     *                  to block for callback completion if it is already in progress
     *                  (for maximum concurrency and reduced deadlock potential), so use proper
     *                  lifetime analysis (e.g. shared or weak pointers).
     * \param timeoutMs timeout in milliseconds.
     * \param requestedTimeoutDuration timeout in milliseconds.
     *                  A zero timeout means no timeout is set -
     *                  the callback is called only when
     *                  the TimeCheck object is destroyed or leaves scope.
     * \param secondChanceDuration additional milliseconds to wait if the first timeout expires.
     *                  This is used to prevent false timeouts if the steady (monotonic)
     *                  clock advances on aborted suspend.
     * \param crashOnTimeout true if the object issues an abort on timeout.
     */
    explicit TimeCheck(std::string_view tag, OnTimerFunc&& onTimer = {},
            uint32_t timeoutMs = kDefaultTimeOutMs, bool crashOnTimeout = true);
    explicit TimeCheck(std::string_view tag, OnTimerFunc&& onTimer,
            Duration requestedTimeoutDuration, Duration secondChanceDuration,
            bool crashOnTimeout);

    TimeCheck() = default;
    // Remove copy constructors as there should only be one call to the destructor.
@@ -81,24 +105,34 @@ class TimeCheck {
    public:
        template <typename S, typename F>
        TimeCheckHandler(S&& _tag, F&& _onTimer, bool _crashOnTimeout,
            const std::chrono::system_clock::time_point& _startTime,
            Duration _timeoutDuration, Duration _secondChanceDuration,
            std::chrono::system_clock::time_point _startSystemTime,
            pid_t _tid)
            : tag(std::forward<S>(_tag))
            , onTimer(std::forward<F>(_onTimer))
            , crashOnTimeout(_crashOnTimeout)
            , startTime(_startTime)
            , timeoutDuration(_timeoutDuration)
            , secondChanceDuration(_secondChanceDuration)
            , startSystemTime(_startSystemTime)
            , tid(_tid)
            {}
        const FixedString62 tag;
        const OnTimerFunc onTimer;
        const bool crashOnTimeout;
        const std::chrono::system_clock::time_point startTime;
        const Duration timeoutDuration;
        const Duration secondChanceDuration;
        const std::chrono::system_clock::time_point startSystemTime;
        const pid_t tid;

        void onCancel(TimerThread::Handle handle) const;
        void onTimeout() const;
        void onTimeout(TimerThread::Handle handle) const;
    };

    // Returns a string that represents the timeout vs elapsed time,
    // and diagnostics if there are any potential issues.
    static std::string analyzeTimeouts(
            float timeoutMs, float elapsedSteadyMs, float elapsedSystemMs);

    static TimerThread& getTimeCheckThread();
    static void accessAudioHalPids(std::vector<pid_t>* pids, bool update);

Loading