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

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

Merge changes If6507a05,Ie35de689

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


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


TimeCheck::~TimeCheck() {
TimeCheck::~TimeCheck() {
    if (mTimeCheckHandler) {
    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
void TimeCheck::TimeCheckHandler::onCancel(TimerThread::Handle timerHandle) const
{
{
    if (TimeCheck::getTimeCheckThread().cancelTask(timerHandle) && onTimer) {
    if (TimeCheck::getTimeCheckThread().cancelTask(timerHandle) && onTimer) {
        const std::chrono::system_clock::time_point endTime = std::chrono::system_clock::now();
        const std::chrono::steady_clock::time_point endSteadyTime =
        onTimer(false /* timeout */,
                std::chrono::steady_clock::now();
                std::chrono::duration_cast<std::chrono::duration<float, std::milli>>(
        const float elapsedSteadyMs = std::chrono::duration_cast<FloatMs>(
                        endTime - startTime).count());
                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) {
    if (onTimer) {
        onTimer(true /* timeout */,
        onTimer(true /* timeout */, elapsedSteadyMs);
                std::chrono::duration_cast<std::chrono::duration<float, std::milli>>(
                        endTime - startTime).count());
    }
    }


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


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


}  // namespace android::mediautils
}  // namespace android::mediautils
+110 −41
Original line number Original line Diff line number Diff line
@@ -23,28 +23,35 @@


#include <mediautils/MediaUtilsDelayed.h>
#include <mediautils/MediaUtilsDelayed.h>
#include <mediautils/TimerThread.h>
#include <mediautils/TimerThread.h>
#include <utils/Log.h>
#include <utils/ThreadDefs.h>
#include <utils/ThreadDefs.h>


using namespace std::chrono_literals;

namespace android::mediautils {
namespace android::mediautils {


extern std::string formatTime(std::chrono::system_clock::time_point t);
extern std::string formatTime(std::chrono::system_clock::time_point t);
extern std::string_view timeSuffix(std::string_view time1, std::string_view time2);
extern std::string_view timeSuffix(std::string_view time1, std::string_view time2);


TimerThread::Handle TimerThread::scheduleTask(
TimerThread::Handle TimerThread::scheduleTask(
        std::string_view tag, std::function<void()>&& func, std::chrono::milliseconds timeout) {
        std::string_view tag, TimerCallback&& func,
        Duration timeoutDuration, Duration secondChanceDuration) {
    const auto now = std::chrono::system_clock::now();
    const auto now = std::chrono::system_clock::now();
    auto request = std::make_shared<const Request>(now, now + timeout, gettid(), tag);
    auto request = std::make_shared<const Request>(now, now +
    return mMonitorThread.add(std::move(request), std::move(func), timeout);
            std::chrono::duration_cast<std::chrono::system_clock::duration>(timeoutDuration),
            secondChanceDuration, gettid(), tag);
    return mMonitorThread.add(std::move(request), std::move(func), timeoutDuration);
}
}


TimerThread::Handle TimerThread::trackTask(std::string_view tag) {
TimerThread::Handle TimerThread::trackTask(std::string_view tag) {
    const auto now = std::chrono::system_clock::now();
    const auto now = std::chrono::system_clock::now();
    auto request = std::make_shared<const Request>(now, now, gettid(), tag);
    auto request = std::make_shared<const Request>(now, now,
            Duration{} /* secondChanceDuration */, gettid(), tag);
    return mNoTimeoutMap.add(std::move(request));
    return mNoTimeoutMap.add(std::move(request));
}
}


bool TimerThread::cancelTask(Handle handle) {
bool TimerThread::cancelTask(Handle handle) {
    std::shared_ptr<const Request> request = mNoTimeoutMap.isValidHandle(handle) ?
    std::shared_ptr<const Request> request = isNoTimeoutHandle(handle) ?
             mNoTimeoutMap.remove(handle) : mMonitorThread.remove(handle);
             mNoTimeoutMap.remove(handle) : mMonitorThread.remove(handle);
    if (!request) return false;
    if (!request) return false;
    mRetiredQueue.add(std::move(request));
    mRetiredQueue.add(std::move(request));
@@ -82,6 +89,8 @@ std::string TimerThread::toString(size_t retiredCount) const {


    return std::string("now ")
    return std::string("now ")
            .append(formatTime(std::chrono::system_clock::now()))
            .append(formatTime(std::chrono::system_clock::now()))
            .append("\nsecondChanceCount ")
            .append(std::to_string(mMonitorThread.getSecondChanceCount()))
            .append(analysisSummary)
            .append(analysisSummary)
            .append("\ntimeout [ ")
            .append("\ntimeout [ ")
            .append(requestsToString(timeoutRequests))
            .append(requestsToString(timeoutRequests))
@@ -125,12 +134,12 @@ struct TimerThread::Analysis TimerThread::analyzeTimeout(
    std::vector<std::shared_ptr<const Request>> pendingExact;
    std::vector<std::shared_ptr<const Request>> pendingExact;
    std::vector<std::shared_ptr<const Request>> pendingPossible;
    std::vector<std::shared_ptr<const Request>> pendingPossible;


    // We look at pending requests that were scheduled no later than kDuration
    // We look at pending requests that were scheduled no later than kPendingDuration
    // after the timeout request. This prevents false matches with calls
    // after the timeout request. This prevents false matches with calls
    // that naturally block for a short period of time
    // that naturally block for a short period of time
    // such as HAL write() and read().
    // such as HAL write() and read().
    //
    //
    auto constexpr kDuration = std::chrono::milliseconds(1000);
    constexpr Duration kPendingDuration = 1000ms;
    for (const auto& pending : pendingRequests) {
    for (const auto& pending : pendingRequests) {
        // If the pending tid is the same as timeout tid, problem identified.
        // If the pending tid is the same as timeout tid, problem identified.
        if (pending->tid == timeout->tid) {
        if (pending->tid == timeout->tid) {
@@ -139,7 +148,7 @@ struct TimerThread::Analysis TimerThread::analyzeTimeout(
        }
        }


        // if the pending tid is scheduled within time limit
        // if the pending tid is scheduled within time limit
        if (pending->scheduled - timeout->scheduled < kDuration) {
        if (pending->scheduled - timeout->scheduled < kPendingDuration) {
            pendingPossible.emplace_back(pending);
            pendingPossible.emplace_back(pending);
        }
        }
    }
    }
@@ -239,15 +248,11 @@ void TimerThread::RequestQueue::copyRequests(
    }
    }
}
}


bool TimerThread::NoTimeoutMap::isValidHandle(Handle handle) const {
    return handle > getIndexedHandle(mNoTimeoutRequests);
}

TimerThread::Handle TimerThread::NoTimeoutMap::add(std::shared_ptr<const Request> request) {
TimerThread::Handle TimerThread::NoTimeoutMap::add(std::shared_ptr<const Request> request) {
    std::lock_guard lg(mNTMutex);
    std::lock_guard lg(mNTMutex);
    // A unique handle is obtained by mNoTimeoutRequests.fetch_add(1),
    // A unique handle is obtained by mNoTimeoutRequests.fetch_add(1),
    // This need not be under a lock, but we do so anyhow.
    // This need not be under a lock, but we do so anyhow.
    const Handle handle = getIndexedHandle(mNoTimeoutRequests++);
    const Handle handle = getUniqueHandle_l();
    mMap[handle] = request;
    mMap[handle] = request;
    return handle;
    return handle;
}
}
@@ -269,16 +274,6 @@ void TimerThread::NoTimeoutMap::copyRequests(
    }
    }
}
}


TimerThread::Handle TimerThread::MonitorThread::getUniqueHandle_l(
        std::chrono::milliseconds timeout) {
    // To avoid key collisions, advance by 1 tick until the key is unique.
    auto deadline = std::chrono::steady_clock::now() + timeout;
    for (; mMonitorRequests.find(deadline) != mMonitorRequests.end();
         deadline += std::chrono::steady_clock::duration(1))
        ;
    return deadline;
}

TimerThread::MonitorThread::MonitorThread(RequestQueue& timeoutQueue)
TimerThread::MonitorThread::MonitorThread(RequestQueue& timeoutQueue)
        : mTimeoutQueue(timeoutQueue)
        : mTimeoutQueue(timeoutQueue)
        , mThread([this] { threadFunc(); }) {
        , mThread([this] { threadFunc(); }) {
@@ -298,24 +293,78 @@ TimerThread::MonitorThread::~MonitorThread() {
void TimerThread::MonitorThread::threadFunc() {
void TimerThread::MonitorThread::threadFunc() {
    std::unique_lock _l(mMutex);
    std::unique_lock _l(mMutex);
    while (!mShouldExit) {
    while (!mShouldExit) {
        Handle nextDeadline = INVALID_HANDLE;
        Handle now = INVALID_HANDLE;
        if (!mMonitorRequests.empty()) {
        if (!mMonitorRequests.empty()) {
            Handle nextDeadline = mMonitorRequests.begin()->first;
            nextDeadline = mMonitorRequests.begin()->first;
            if (nextDeadline < std::chrono::steady_clock::now()) {
            now = std::chrono::steady_clock::now();
            if (nextDeadline < now) {
                auto node = mMonitorRequests.extract(mMonitorRequests.begin());
                // Deadline has expired, handle the request.
                // Deadline has expired, handle the request.
                auto secondChanceDuration = node.mapped().first->secondChanceDuration;
                if (secondChanceDuration.count() != 0) {
                    // We now apply the second chance duration to find the clock
                    // monotonic second deadline.  The unique key is then the
                    // pair<second_deadline, first_deadline>.
                    //
                    // The second chance prevents a false timeout should there be
                    // any clock monotonic advancement during suspend.
                    auto newHandle = now + secondChanceDuration;
                    ALOGD("%s: TimeCheck second chance applied for %s",
                            __func__, node.mapped().first->tag.c_str()); // should be rare event.
                    mSecondChanceRequests.emplace_hint(mSecondChanceRequests.end(),
                            std::make_pair(newHandle, nextDeadline),
                            std::move(node.mapped()));
                    // increment second chance counter.
                    mSecondChanceCount.fetch_add(1 /* arg */, std::memory_order_relaxed);
                } else {
                    {
                    {
                    auto node = mMonitorRequests.extract(mMonitorRequests.begin());
                        _l.unlock();
                        _l.unlock();
                        // We add Request to retired queue early so that it can be dumped out.
                        // We add Request to retired queue early so that it can be dumped out.
                        mTimeoutQueue.add(std::move(node.mapped().first));
                        mTimeoutQueue.add(std::move(node.mapped().first));
                    node.mapped().second(); // Caution: we don't hold lock here - but do we care?
                        node.mapped().second(nextDeadline);
                                            // this is the timeout case!  We will crash soon,
                        // Caution: we don't hold lock when we call TimerCallback,
                        // but this is the timeout case!  We will crash soon,
                        // maybe before returning.
                        // maybe before returning.
                        // anything left over is released here outside lock.
                        // anything left over is released here outside lock.
                    }
                    }
                    // reacquire the lock - if something was added, we loop immediately to check.
                    // reacquire the lock - if something was added, we loop immediately to check.
                    _l.lock();
                    _l.lock();
                }
                // always process expiring monitor requests first.
                continue;
            }
        }
        // now process any second chance requests.
        if (!mSecondChanceRequests.empty()) {
            Handle secondDeadline = mSecondChanceRequests.begin()->first.first;
            if (now == INVALID_HANDLE) now = std::chrono::steady_clock::now();
            if (secondDeadline < now) {
                auto node = mSecondChanceRequests.extract(mSecondChanceRequests.begin());
                {
                    _l.unlock();
                    // We add Request to retired queue early so that it can be dumped out.
                    mTimeoutQueue.add(std::move(node.mapped().first));
                    const Handle originalHandle = node.key().second;
                    node.mapped().second(originalHandle);
                    // Caution: we don't hold lock when we call TimerCallback.
                    // This is benign issue - we permit concurrent operations
                    // while in the callback to the MonitorQueue.
                    //
                    // Anything left over is released here outside lock.
                }
                // reacquire the lock - if something was added, we loop immediately to check.
                _l.lock();
                continue;
                continue;
            }
            }
            // update the deadline.
            if (nextDeadline == INVALID_HANDLE) {
                nextDeadline = secondDeadline;
            } else {
                nextDeadline = std::min(nextDeadline, secondDeadline);
            }
        }
        if (nextDeadline != INVALID_HANDLE) {
            mCond.wait_until(_l, nextDeadline);
            mCond.wait_until(_l, nextDeadline);
        } else {
        } else {
            mCond.wait(_l);
            mCond.wait(_l);
@@ -324,26 +373,39 @@ void TimerThread::MonitorThread::threadFunc() {
}
}


TimerThread::Handle TimerThread::MonitorThread::add(
TimerThread::Handle TimerThread::MonitorThread::add(
        std::shared_ptr<const Request> request, std::function<void()>&& func,
        std::shared_ptr<const Request> request, TimerCallback&& func, Duration timeout) {
        std::chrono::milliseconds timeout) {
    std::lock_guard _l(mMutex);
    std::lock_guard _l(mMutex);
    const Handle handle = getUniqueHandle_l(timeout);
    const Handle handle = getUniqueHandle_l(timeout);
    mMonitorRequests.emplace(handle, std::make_pair(std::move(request), std::move(func)));
    mMonitorRequests.emplace_hint(mMonitorRequests.end(),
            handle, std::make_pair(std::move(request), std::move(func)));
    mCond.notify_all();
    mCond.notify_all();
    return handle;
    return handle;
}
}


std::shared_ptr<const TimerThread::Request> TimerThread::MonitorThread::remove(Handle handle) {
std::shared_ptr<const TimerThread::Request> TimerThread::MonitorThread::remove(Handle handle) {
    std::pair<std::shared_ptr<const Request>, TimerCallback> data;
    std::unique_lock ul(mMutex);
    std::unique_lock ul(mMutex);
    const auto it = mMonitorRequests.find(handle);
    if (const auto it = mMonitorRequests.find(handle);
    if (it == mMonitorRequests.end()) {
        it != mMonitorRequests.end()) {
        return {};
        data = std::move(it->second);
    }
    std::shared_ptr<const TimerThread::Request> request = std::move(it->second.first);
    std::function<void()> func = std::move(it->second.second);
        mMonitorRequests.erase(it);
        mMonitorRequests.erase(it);
    ul.unlock();  // manually release lock here so func is released outside of lock.
        ul.unlock();  // manually release lock here so func (data.second)
    return request;
                      // is released outside of lock.
        return data.first;  // request
    }

    // this check is O(N), but since the second chance requests are ordered
    // in terms of earliest expiration time, we would expect better than average results.
    for (auto it = mSecondChanceRequests.begin(); it != mSecondChanceRequests.end(); ++it) {
        if (it->first.second == handle) {
            data = std::move(it->second);
            mSecondChanceRequests.erase(it);
            ul.unlock();  // manually release lock here so func (data.second)
                          // is released outside of lock.
            return data.first; // request
        }
    }
    return {};
}
}


void TimerThread::MonitorThread::copyRequests(
void TimerThread::MonitorThread::copyRequests(
@@ -352,6 +414,13 @@ void TimerThread::MonitorThread::copyRequests(
    for (const auto &[deadline, monitorpair] : mMonitorRequests) {
    for (const auto &[deadline, monitorpair] : mMonitorRequests) {
        requests.emplace_back(monitorpair.first);
        requests.emplace_back(monitorpair.first);
    }
    }
    // we combine the second map with the first map - this is
    // everything that is pending on the monitor thread.
    // The second map will be older than the first map so this
    // is in order.
    for (const auto &[deadline, monitorpair] : mSecondChanceRequests) {
        requests.emplace_back(monitorpair.first);
    }
}
}


}  // namespace android::mediautils
}  // namespace android::mediautils
+3 −1
Original line number Original line 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);
    std::string name = data_provider.ConsumeRandomLengthString(kMaxStringLen);


    // 3. The constructor, which is fuzzed here:
    // 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
    // We will leave some buffer to avoid sleeping too long
    uint8_t sleep_amount_ms = data_provider.ConsumeIntegralInRange<uint8_t>(0, timeoutMs / 2);
    uint8_t sleep_amount_ms = data_provider.ConsumeIntegralInRange<uint8_t>(0, timeoutMs / 2);


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


#pragma once
#pragma once


#include <chrono>
#include <vector>
#include <vector>


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


class TimeCheck {
class TimeCheck {
  public:
  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 */ )>;
    using OnTimerFunc = std::function<void(bool /* timeout */, float /* elapsedMs */ )>;


    // The default timeout is chosen to be less than system server watchdog timeout
    // 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
     * TimeCheck is a RAII object which will notify a callback
@@ -44,24 +68,24 @@ class TimeCheck {
     * the deallocation.
     * the deallocation.
     *
     *
     * \param tag       string associated with the TimeCheck object.
     * \param tag       string associated with the TimeCheck object.
     * \param onTimer   callback function with 2 parameters
     * \param onTimer   callback function with 2 parameters (described above in OnTimerFunc).
     *                      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).
     *                  The callback when timeout is true will be called on a different thread.
     *                  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
     *                  This will cancel the callback on the destructor but is not guaranteed
     *                  to block for callback completion if it is already in progress
     *                  to block for callback completion if it is already in progress
     *                  (for maximum concurrency and reduced deadlock potential), so use proper
     *                  (for maximum concurrency and reduced deadlock potential), so use proper
     *                  lifetime analysis (e.g. shared or weak pointers).
     *                  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 -
     *                  A zero timeout means no timeout is set -
     *                  the callback is called only when
     *                  the callback is called only when
     *                  the TimeCheck object is destroyed or leaves scope.
     *                  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.
     * \param crashOnTimeout true if the object issues an abort on timeout.
     */
     */
    explicit TimeCheck(std::string_view tag, OnTimerFunc&& onTimer = {},
    explicit TimeCheck(std::string_view tag, OnTimerFunc&& onTimer,
            uint32_t timeoutMs = kDefaultTimeOutMs, bool crashOnTimeout = true);
            Duration requestedTimeoutDuration, Duration secondChanceDuration,
            bool crashOnTimeout);


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


        void onCancel(TimerThread::Handle handle) const;
        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 TimerThread& getTimeCheckThread();
    static void accessAudioHalPids(std::vector<pid_t>* pids, bool update);
    static void accessAudioHalPids(std::vector<pid_t>* pids, bool update);


+118 −26

File changed.

Preview size limit exceeded, changes collapsed.

Loading