| /* |
| * Copyright 2018 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 "OneShotTimer.h" |
| #include <utils/Log.h> |
| #include <utils/Timers.h> |
| #include <chrono> |
| #include <sstream> |
| #include <thread> |
| |
| namespace { |
| using namespace std::chrono_literals; |
| |
| constexpr int64_t kNsToSeconds = std::chrono::duration_cast<std::chrono::nanoseconds>(1s).count(); |
| |
| // The syscall interface uses a pair of integers for the timestamp. The first |
| // (tv_sec) is the whole count of seconds. The second (tv_nsec) is the |
| // nanosecond part of the count. This function takes care of translation. |
| void calculateTimeoutTime(std::chrono::nanoseconds timestamp, timespec* spec) { |
| const nsecs_t timeout = systemTime(CLOCK_MONOTONIC) + timestamp.count(); |
| spec->tv_sec = static_cast<__kernel_time_t>(timeout / kNsToSeconds); |
| spec->tv_nsec = timeout % kNsToSeconds; |
| } |
| } // namespace |
| |
| namespace android { |
| namespace scheduler { |
| |
| OneShotTimer::OneShotTimer(std::string name, const Interval& interval, |
| const ResetCallback& resetCallback, |
| const TimeoutCallback& timeoutCallback, std::unique_ptr<Clock> clock) |
| : mClock(std::move(clock)), |
| mName(std::move(name)), |
| mInterval(interval), |
| mResetCallback(resetCallback), |
| mTimeoutCallback(timeoutCallback) { |
| mLastResetTime = std::chrono::steady_clock::time_point::min(); |
| LOG_ALWAYS_FATAL_IF(!mClock, "Clock must not be provided"); |
| } |
| |
| OneShotTimer::~OneShotTimer() { |
| stop(); |
| } |
| |
| void OneShotTimer::start() { |
| int result = sem_init(&mSemaphore, 0, 0); |
| LOG_ALWAYS_FATAL_IF(result, "sem_init failed"); |
| |
| if (!mThread.joinable()) { |
| // Only create thread if it has not been created. |
| mThread = std::thread(&OneShotTimer::loop, this); |
| } |
| } |
| |
| void OneShotTimer::stop() { |
| mStopTriggered = true; |
| int result = sem_post(&mSemaphore); |
| LOG_ALWAYS_FATAL_IF(result, "sem_post failed"); |
| |
| if (mThread.joinable()) { |
| mThread.join(); |
| result = sem_destroy(&mSemaphore); |
| LOG_ALWAYS_FATAL_IF(result, "sem_destroy failed"); |
| } |
| } |
| |
| void OneShotTimer::loop() { |
| if (pthread_setname_np(pthread_self(), mName.c_str())) { |
| ALOGW("Failed to set thread name on dispatch thread"); |
| } |
| |
| TimerState state = TimerState::RESET; |
| while (true) { |
| bool triggerReset = false; |
| bool triggerTimeout = false; |
| |
| state = checkForResetAndStop(state); |
| if (state == TimerState::STOPPED) { |
| break; |
| } |
| |
| if (state == TimerState::IDLE) { |
| int result = sem_wait(&mSemaphore); |
| if (result && errno != EINTR) { |
| std::stringstream ss; |
| ss << "sem_wait failed (" << errno << ")"; |
| LOG_ALWAYS_FATAL("%s", ss.str().c_str()); |
| } |
| continue; |
| } |
| |
| if (state == TimerState::RESET) { |
| triggerReset = true; |
| } |
| |
| if (triggerReset && mResetCallback) { |
| mResetCallback(); |
| } |
| |
| state = checkForResetAndStop(state); |
| if (state == TimerState::STOPPED) { |
| break; |
| } |
| |
| auto triggerTime = mClock->now() + mInterval; |
| state = TimerState::WAITING; |
| while (true) { |
| // Wait until triggerTime time to check if we need to reset or drop into the idle state. |
| if (const auto triggerInterval = triggerTime - mClock->now(); triggerInterval > 0ns) { |
| mWaiting = true; |
| struct timespec ts; |
| calculateTimeoutTime(triggerInterval, &ts); |
| int result = sem_clockwait(&mSemaphore, CLOCK_MONOTONIC, &ts); |
| if (result && errno != ETIMEDOUT && errno != EINTR) { |
| std::stringstream ss; |
| ss << "sem_clockwait failed (" << errno << ")"; |
| LOG_ALWAYS_FATAL("%s", ss.str().c_str()); |
| } |
| } |
| |
| mWaiting = false; |
| state = checkForResetAndStop(state); |
| if (state == TimerState::STOPPED) { |
| break; |
| } |
| |
| if (state == TimerState::WAITING && (triggerTime - mClock->now()) <= 0ns) { |
| triggerTimeout = true; |
| state = TimerState::IDLE; |
| break; |
| } |
| |
| if (state == TimerState::RESET) { |
| triggerTime = mLastResetTime.load() + mInterval; |
| state = TimerState::WAITING; |
| } |
| } |
| |
| if (triggerTimeout && mTimeoutCallback) { |
| mTimeoutCallback(); |
| } |
| } |
| } |
| |
| OneShotTimer::TimerState OneShotTimer::checkForResetAndStop(TimerState state) { |
| // Stop takes precedence of the reset. |
| if (mStopTriggered.exchange(false)) { |
| return TimerState::STOPPED; |
| } |
| // If the state was stopped, the thread was joined, and we cannot reset |
| // the timer anymore. |
| if (state != TimerState::STOPPED && mResetTriggered.exchange(false)) { |
| return TimerState::RESET; |
| } |
| return state; |
| } |
| |
| void OneShotTimer::reset() { |
| mLastResetTime = mClock->now(); |
| mResetTriggered = true; |
| // If mWaiting is true, then we are guaranteed to be in a block where we are waiting on |
| // mSemaphore for a timeout, rather than idling. So we can avoid a sem_post call since we can |
| // just check that we triggered a reset on timeout. |
| if (!mWaiting) { |
| LOG_ALWAYS_FATAL_IF(sem_post(&mSemaphore), "sem_post failed"); |
| } |
| } |
| |
| } // namespace scheduler |
| } // namespace android |