/* * Copyright 2019 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. */ #undef LOG_TAG #define LOG_TAG "SchedulerTimer" #define ATRACE_TAG ATRACE_TAG_GRAPHICS #include #include #include #include #include #include #include #include #include "SchedulerUtils.h" #include "Timer.h" namespace android::scheduler { using base::StringAppendF; static constexpr size_t kReadPipe = 0; static constexpr size_t kWritePipe = 1; Timer::Timer() { reset(); mDispatchThread = std::thread([this]() { threadMain(); }); } Timer::~Timer() { endDispatch(); mDispatchThread.join(); cleanup(); } void Timer::reset() { cleanup(); mTimerFd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK); mEpollFd = epoll_create1(EPOLL_CLOEXEC); if (pipe2(mPipes.data(), O_CLOEXEC | O_NONBLOCK)) { ALOGE("could not create TimerDispatch mPipes"); return; }; setDebugState(DebugState::Reset); } void Timer::cleanup() { if (mTimerFd != -1) { close(mTimerFd); mTimerFd = -1; } if (mEpollFd != -1) { close(mEpollFd); mEpollFd = -1; } if (mPipes[kReadPipe] != -1) { close(mPipes[kReadPipe]); mPipes[kReadPipe] = -1; } if (mPipes[kWritePipe] != -1) { close(mPipes[kWritePipe]); mPipes[kWritePipe] = -1; } } void Timer::endDispatch() { static constexpr unsigned char end = 'e'; write(mPipes[kWritePipe], &end, sizeof(end)); } nsecs_t Timer::now() const { return systemTime(SYSTEM_TIME_MONOTONIC); } void Timer::alarmAt(std::function const& cb, nsecs_t time) { std::lock_guard lock(mMutex); using namespace std::literals; static constexpr int ns_per_s = std::chrono::duration_cast(1s).count(); mCallback = cb; struct itimerspec old_timer; struct itimerspec new_timer { .it_interval = {.tv_sec = 0, .tv_nsec = 0}, .it_value = {.tv_sec = static_cast(time / ns_per_s), .tv_nsec = static_cast(time % ns_per_s)}, }; if (timerfd_settime(mTimerFd, TFD_TIMER_ABSTIME, &new_timer, &old_timer)) { ALOGW("Failed to set timerfd %s (%i)", strerror(errno), errno); } } void Timer::alarmCancel() { std::lock_guard lock(mMutex); struct itimerspec old_timer; struct itimerspec new_timer { .it_interval = {.tv_sec = 0, .tv_nsec = 0}, .it_value = { .tv_sec = 0, .tv_nsec = 0, }, }; if (timerfd_settime(mTimerFd, 0, &new_timer, &old_timer)) { ALOGW("Failed to disarm timerfd"); } } void Timer::threadMain() { while (dispatch()) { reset(); } } bool Timer::dispatch() { setDebugState(DebugState::Running); struct sched_param param = {0}; param.sched_priority = 2; if (pthread_setschedparam(pthread_self(), SCHED_FIFO, ¶m) != 0) { ALOGW("Failed to set SCHED_FIFO on dispatch thread"); } if (pthread_setname_np(pthread_self(), "TimerDispatch")) { ALOGW("Failed to set thread name on dispatch thread"); } enum DispatchType : uint32_t { TIMER, TERMINATE, MAX_DISPATCH_TYPE }; epoll_event timerEvent; timerEvent.events = EPOLLIN; timerEvent.data.u32 = DispatchType::TIMER; if (epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mTimerFd, &timerEvent) == -1) { ALOGE("Error adding timer fd to epoll dispatch loop"); return true; } epoll_event terminateEvent; terminateEvent.events = EPOLLIN; terminateEvent.data.u32 = DispatchType::TERMINATE; if (epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mPipes[kReadPipe], &terminateEvent) == -1) { ALOGE("Error adding control fd to dispatch loop"); return true; } uint64_t iteration = 0; char const traceNamePrefix[] = "TimerIteration #"; static constexpr size_t maxlen = arrayLen(traceNamePrefix) + max64print; std::array str_buffer; while (true) { setDebugState(DebugState::Waiting); epoll_event events[DispatchType::MAX_DISPATCH_TYPE]; int nfds = epoll_wait(mEpollFd, events, DispatchType::MAX_DISPATCH_TYPE, -1); setDebugState(DebugState::Running); if (ATRACE_ENABLED()) { snprintf(str_buffer.data(), str_buffer.size(), "%s%" PRIu64, traceNamePrefix, iteration++); ATRACE_NAME(str_buffer.data()); } if (nfds == -1) { if (errno != EINTR) { ALOGE("Error waiting on epoll: %s", strerror(errno)); return true; } } for (auto i = 0; i < nfds; i++) { if (events[i].data.u32 == DispatchType::TIMER) { static uint64_t mIgnored = 0; setDebugState(DebugState::Reading); read(mTimerFd, &mIgnored, sizeof(mIgnored)); setDebugState(DebugState::Running); std::function cb; { std::lock_guard lock(mMutex); cb = mCallback; } if (cb) { setDebugState(DebugState::InCallback); cb(); setDebugState(DebugState::Running); } } if (events[i].data.u32 == DispatchType::TERMINATE) { ALOGE("Terminated"); setDebugState(DebugState::Running); return false; } } } } void Timer::setDebugState(DebugState state) { std::lock_guard lock(mMutex); mDebugState = state; } const char* Timer::strDebugState(DebugState state) const { switch (state) { case DebugState::Reset: return "Reset"; case DebugState::Running: return "Running"; case DebugState::Waiting: return "Waiting"; case DebugState::Reading: return "Reading"; case DebugState::InCallback: return "InCallback"; case DebugState::Terminated: return "Terminated"; } } void Timer::dump(std::string& result) const { std::lock_guard lock(mMutex); StringAppendF(&result, "\t\tDebugState: %s\n", strDebugState(mDebugState)); } } // namespace android::scheduler