/* * @copyright Copyright (c) 2022 Woven Alpha, Inc. * * 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 "timer.h" #include #include #include #include #include #include #include "logging.h" #define DEBUG_EVENT_TIMER 0 uint32_t EventTimer::watched_fd = 0; int EventTimer::evfd = -1; EventTimer::EventTimer(int clock, evtimer_cb callback, void* callback_data) : m_callback(callback), m_callback_data(callback_data) { SPDLOG_TRACE("+ EventTimer()"); if (evfd < 0) { // initialize class-global epoll fd evfd = epoll_create1(EPOLL_CLOEXEC); if (evfd < 0) { if (errno != EINVAL) { spdlog::critical("Failed to create epoll fd cloexec. {}", strerror(errno)); exit(-1); } else { // fallback evfd = epoll_create(1); if (evfd < 0) { spdlog::critical("Failed to create epoll fd. {}", strerror(errno)); exit(-1); } } } } m_timerfd = timerfd_create(clock, TFD_CLOEXEC | TFD_NONBLOCK); if (m_timerfd == -1) { spdlog::critical("Failed to create timerfd. {}", strerror(errno)); exit(-1); } watch_timerfd(); } EventTimer::~EventTimer() { SPDLOG_TRACE("+ ~EventTimer()"); unwatch_timerfd(); close(m_timerfd); m_timerfd = -1; if (watched_fd == 0) close_evfd(); SPDLOG_TRACE("- ~EventTimer()"); } void EventTimer::close_evfd() { SPDLOG_TRACE("+ EventTimer::close_evfd()"); close(evfd); evfd = -1; SPDLOG_TRACE("- EventTimer::close_evfd()"); } void EventTimer::set_timerspec(int32_t rate, int32_t delay) { SPDLOG_TRACE("+ EventTimer::set_timerspec()"); if (rate == 0) return; const int32_t repeat_rate_sec = rate / 1000; rate -= (repeat_rate_sec * 1000); const int32_t repeat_rate_nsec = rate * 1000 * 1000; const int32_t repeat_delay_sec = delay / 1000; delay -= (repeat_delay_sec * 1000); const int32_t repeat_delay_nsec = delay * 1000 * 1000; m_timerspec.it_interval.tv_sec = repeat_rate_sec; m_timerspec.it_interval.tv_nsec = repeat_rate_nsec; m_timerspec.it_value.tv_sec = repeat_delay_sec; m_timerspec.it_value.tv_nsec = repeat_delay_nsec; SPDLOG_TRACE("- EventTimer::set_timerspec()"); } void EventTimer::_arm(const int fd, itimerspec const* timerspec) { if (timerfd_settime(fd, 0, timerspec, nullptr) < 0) { spdlog::critical("Failed to release timer. {}", strerror(errno)); exit(-1); } } void EventTimer::arm() const { SPDLOG_TRACE("+ EventTimer::arm()"); _arm(m_timerfd, &m_timerspec); SPDLOG_TRACE("- EventTimer::arm()"); } void EventTimer::disarm() const { SPDLOG_TRACE("+ EventTimer::disarm()"); constexpr itimerspec timerspec{}; _arm(m_timerfd, &timerspec); SPDLOG_TRACE("- EventTimer::disarm()"); } void EventTimer::_watch_fd(int fd, uint32_t events, struct timer_task* task) { struct epoll_event ep {}; if (evfd < 0) { spdlog::critical("Unexpected call _watch_fd(). Ignored."); return; } ep.events = events; ep.data.ptr = task; epoll_ctl(evfd, EPOLL_CTL_ADD, fd, &ep); watched_fd++; } void EventTimer::watch_timerfd() { SPDLOG_TRACE("+ EventTimer::watch_timerfd()"); m_task.run = EventTimer::run; m_task.data = reinterpret_cast(this); _watch_fd(m_timerfd, EPOLLIN, &m_task); SPDLOG_TRACE("- EventTimer::watch_timerfd()"); } void EventTimer::_unwatch_fd(int fd) { if (evfd < 0) { spdlog::critical("Unexpected call _unwatch_fd(). Ignored."); return; } epoll_ctl(evfd, EPOLL_CTL_DEL, fd, nullptr); watched_fd--; } void EventTimer::unwatch_timerfd() const { SPDLOG_TRACE("+ EventTimer::unwatch_timerfd()"); _unwatch_fd(m_timerfd); SPDLOG_TRACE("- EventTimer::unwatch_timerfd()"); } void EventTimer::wait_event() { epoll_event ep[10]; const auto ready = epoll_wait(evfd, ep, std::size(ep), 0); for (auto i = 0; i < ready; i++) { const auto task = static_cast(ep[i].data.ptr); task->run(task, ep[i].events); } } void EventTimer::run(timer_task const* task, uint32_t events) { uint64_t event; const auto timer = static_cast(task->data); if (events != EPOLLIN) spdlog::critical("Found Unexpected timerfd events: 0x{:x}", events); if (!(events & EPOLLIN)) return; if (read(timer->m_timerfd, &event, sizeof(event)) != sizeof(event)) { if (errno != EAGAIN) spdlog::critical("Failed to read timer. {}", strerror(errno)); return; } timer->m_callback(timer->m_callback_data); }