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

Commit 3b640450 authored by Ytai Ben-tsvi's avatar Ytai Ben-tsvi Committed by Android (Google) Code Review
Browse files

Merge "Modernize TimeCheck"

parents 7a17f7d8 1ea62c98
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ cc_library {
        "SchedulingPolicyService.cpp",
        "ServiceUtilities.cpp",
        "TimeCheck.cpp",
        "TimerThread.cpp",
    ],
    static_libs: [
        "libc_malloc_debug_backtrace",
@@ -118,3 +119,12 @@ cc_library_headers {

    export_include_dirs: ["include"],
}

cc_test {
    name: "libmediautils_test",
    srcs: ["TimerThread-test.cpp"],
    shared_libs: [
      "libmediautils",
      "libutils",
    ]
}
+25 −72
Original line number Diff line number Diff line
@@ -16,9 +16,11 @@

#define LOG_TAG "TimeCheck"

#include <utils/Log.h>
#include <mediautils/TimeCheck.h>
#include <optional>

#include <mediautils/EventLog.h>
#include <mediautils/TimeCheck.h>
#include <utils/Log.h>
#include "debuggerd/handler.h"

namespace android {
@@ -58,67 +60,21 @@ std::vector<pid_t> TimeCheck::getAudioHalPids() {
}

/* static */
sp<TimeCheck::TimeCheckThread> TimeCheck::getTimeCheckThread()
{
    static sp<TimeCheck::TimeCheckThread> sTimeCheckThread = new TimeCheck::TimeCheckThread();
TimerThread* TimeCheck::getTimeCheckThread() {
    static TimerThread* sTimeCheckThread = new TimerThread();
    return sTimeCheckThread;
}

TimeCheck::TimeCheck(const char* tag, uint32_t timeoutMs)
    : mEndTimeNs(getTimeCheckThread()->startMonitoring(tag, timeoutMs))
{
}
    : mTimerHandle(getTimeCheckThread()->scheduleTask([tag] { crash(tag); },
                                                      std::chrono::milliseconds(timeoutMs))) {}

TimeCheck::~TimeCheck() {
    getTimeCheckThread()->stopMonitoring(mEndTimeNs);
    getTimeCheckThread()->cancelTask(mTimerHandle);
}

TimeCheck::TimeCheckThread::~TimeCheckThread()
{
    AutoMutex _l(mMutex);
    requestExit();
    mMonitorRequests.clear();
    mCond.signal();
}

nsecs_t TimeCheck::TimeCheckThread::startMonitoring(const char *tag, uint32_t timeoutMs) {
    Mutex::Autolock _l(mMutex);
    nsecs_t endTimeNs = systemTime() + milliseconds(timeoutMs);
    for (; mMonitorRequests.indexOfKey(endTimeNs) >= 0; ++endTimeNs);
    mMonitorRequests.add(endTimeNs, tag);
    mCond.signal();
    return endTimeNs;
}

void TimeCheck::TimeCheckThread::stopMonitoring(nsecs_t endTimeNs) {
    Mutex::Autolock _l(mMutex);
    mMonitorRequests.removeItem(endTimeNs);
    mCond.signal();
}

bool TimeCheck::TimeCheckThread::threadLoop()
{
    status_t status = TIMED_OUT;
    {
        AutoMutex _l(mMutex);

        if (exitPending()) {
            return false;
        }

        nsecs_t endTimeNs = INT64_MAX;
        const char *tag = "<unspecified>";
        // KeyedVector mMonitorRequests is ordered so take first entry as next timeout
        if (mMonitorRequests.size() != 0) {
            endTimeNs = mMonitorRequests.keyAt(0);
            tag = mMonitorRequests.valueAt(0);
        }

        const nsecs_t waitTimeNs = endTimeNs - systemTime();
        if (waitTimeNs > 0) {
            status = mCond.waitRelative(mMutex, waitTimeNs);
        }
        if (status != NO_ERROR) {
/* static */
void TimeCheck::crash(const char* tag) {
    // Generate audio HAL processes tombstones and allow time to complete
    // before forcing restart
    std::vector<pid_t> pids = getAudioHalPids();
@@ -134,8 +90,5 @@ bool TimeCheck::TimeCheckThread::threadLoop()
    LOG_EVENT_STRING(LOGTAG_AUDIO_BINDER_TIMEOUT, tag);
    LOG_ALWAYS_FATAL("TimeCheck timeout for %s", tag);
}
    }
    return true;
}

};  // namespace android
+146 −0
Original line number Diff line number Diff line
/*
 * 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 <chrono>
#include <thread>
#include <gtest/gtest.h>
#include <mediautils/TimerThread.h>

using namespace std::chrono_literals;

namespace android {
namespace {

constexpr auto kJitter = 10ms;

TEST(TimerThread, Basic) {
    std::atomic<bool> taskRan = false;
    TimerThread thread;
    thread.scheduleTask([&taskRan] { taskRan = true; }, 100ms);
    std::this_thread::sleep_for(100ms - kJitter);
    ASSERT_FALSE(taskRan);
    std::this_thread::sleep_for(2 * kJitter);
    ASSERT_TRUE(taskRan);
}

TEST(TimerThread, Cancel) {
    std::atomic<bool> taskRan = false;
    TimerThread thread;
    TimerThread::Handle handle = thread.scheduleTask([&taskRan] { taskRan = true; }, 100ms);
    std::this_thread::sleep_for(100ms - kJitter);
    ASSERT_FALSE(taskRan);
    thread.cancelTask(handle);
    std::this_thread::sleep_for(2 * kJitter);
    ASSERT_FALSE(taskRan);
}

TEST(TimerThread, CancelAfterRun) {
    std::atomic<bool> taskRan = false;
    TimerThread thread;
    TimerThread::Handle handle = thread.scheduleTask([&taskRan] { taskRan = true; }, 100ms);
    std::this_thread::sleep_for(100ms + kJitter);
    ASSERT_TRUE(taskRan);
    thread.cancelTask(handle);
}

TEST(TimerThread, MultipleTasks) {
    std::array<std::atomic<bool>, 6> taskRan;
    TimerThread thread;

    auto startTime = std::chrono::steady_clock::now();

    thread.scheduleTask([&taskRan] { taskRan[0] = true; }, 300ms);
    thread.scheduleTask([&taskRan] { taskRan[1] = true; }, 100ms);
    thread.scheduleTask([&taskRan] { taskRan[2] = true; }, 200ms);
    thread.scheduleTask([&taskRan] { taskRan[3] = true; }, 400ms);
    auto handle4 = thread.scheduleTask([&taskRan] { taskRan[4] = true; }, 200ms);
    thread.scheduleTask([&taskRan] { taskRan[5] = true; }, 200ms);

    // 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]);

    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]);

    // 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]);

    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]);

    // 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]);

    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]);

    // 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]);

    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]);
}


}  // namespace
}  // namespace android
+76 −0
Original line number Diff line number Diff line
/*
 * 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.
 */

#define LOG_TAG "TimerThread"

#include <optional>

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

namespace android {

TimerThread::TimerThread() : mThread([this] { threadFunc(); }) {
    pthread_setname_np(mThread.native_handle(), "TimeCheckThread");
    pthread_setschedprio(mThread.native_handle(), PRIORITY_URGENT_AUDIO);
}

TimerThread::~TimerThread() {
    {
        std::lock_guard _l(mMutex);
        mShouldExit = true;
        mCond.notify_all();
    }
    mThread.join();
}

TimerThread::Handle TimerThread::scheduleTaskAtDeadline(std::function<void()>&& func,
                                                        TimePoint deadline) {
    std::lock_guard _l(mMutex);

    // To avoid key collisions, advance by 1 tick until the key is unique.
    for (; mMonitorRequests.find(deadline) != mMonitorRequests.end();
         deadline += TimePoint::duration(1))
        ;
    mMonitorRequests.emplace(deadline, std::move(func));
    mCond.notify_all();
    return deadline;
}

void TimerThread::cancelTask(Handle handle) {
    std::lock_guard _l(mMutex);
    mMonitorRequests.erase(handle);
}

void TimerThread::threadFunc() {
    std::unique_lock _l(mMutex);

    while (!mShouldExit) {
        if (!mMonitorRequests.empty()) {
            TimePoint nextDeadline = mMonitorRequests.begin()->first;
            if (nextDeadline < std::chrono::steady_clock::now()) {
                // Deadline expired.
                mMonitorRequests.begin()->second();
                mMonitorRequests.erase(mMonitorRequests.begin());
            }
            mCond.wait_until(_l, nextDeadline);
        } else {
            mCond.wait(_l);
        }
    }
}

}  // namespace android
+13 −42
Original line number Diff line number Diff line
@@ -14,14 +14,12 @@
 * limitations under the License.
 */

#pragma once

#ifndef ANDROID_TIME_CHECK_H
#define ANDROID_TIME_CHECK_H

#include <utils/KeyedVector.h>
#include <utils/Thread.h>
#include <vector>

#include <mediautils/TimerThread.h>

namespace android {

// A class monitoring execution time for a code block (scoped variable) and causing an assert
@@ -29,7 +27,6 @@ namespace android {

class TimeCheck {
  public:

    // The default timeout is chosen to be less than system server watchdog timeout
    static constexpr uint32_t kDefaultTimeOutMs = 5000;

@@ -39,37 +36,11 @@ public:
    static std::vector<pid_t> getAudioHalPids();

  private:

    class TimeCheckThread : public Thread {
    public:

                            TimeCheckThread() {}
        virtual             ~TimeCheckThread() override;

                nsecs_t     startMonitoring(const char *tag, uint32_t timeoutMs);
                void        stopMonitoring(nsecs_t endTimeNs);

    private:

                // RefBase
        virtual void        onFirstRef() override { run("TimeCheckThread", PRIORITY_URGENT_AUDIO); }

                // Thread
        virtual bool        threadLoop() override;

                Condition           mCond;
                Mutex               mMutex;
                // using the end time in ns as key is OK given the risk is low that two entries
                // are added in such a way that <add time> + <timeout> are the same for both.
                KeyedVector< nsecs_t, const char*>  mMonitorRequests;
    };

    static sp<TimeCheckThread> getTimeCheckThread();
    static TimerThread* getTimeCheckThread();
    static void accessAudioHalPids(std::vector<pid_t>* pids, bool update);
    static void crash(const char* tag);

    const           nsecs_t mEndTimeNs;
    const TimerThread::Handle mTimerHandle;
};

};  // namespace android

#endif  // ANDROID_TIME_CHECK_H
Loading