Last active
August 29, 2015 14:18
-
-
Save iscgar/501ad7df92b6e9531a50 to your computer and use it in GitHub Desktop.
A simple timer wrapper for Linux intended to be consumed by a single thread with timed wait.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @file Timer.cpp | |
* | |
* A simple timer wrapper implementation for Linux (can be adjusted for other OSs easily). | |
* | |
* @author Isaac Garzon | |
* @since 05/04/2015 | |
*/ | |
#if !defined(__linux__) | |
# error Timer is implemented for linux only | |
#else // defined(__linux__) | |
# include <linux/version.h> | |
# if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,22) | |
# error Timer requires kernel version >= 2.6.22 | |
# endif // LINUX_VERSION_CODE < KERNEL_VERSION(2,6,22) | |
#endif // defined(__linux__) | |
#include <fcntl.h> | |
#include <sys/epoll.h> | |
#include <sys/eventfd.h> | |
#include <sys/timerfd.h> | |
#include <unistd.h> | |
#include "Timer.h" | |
#define INVALID_FD -1 | |
namespace utils | |
{ | |
Timer::Timer() : | |
m_fired(0), | |
m_timer(INVALID_FD), | |
m_poll(INVALID_FD), | |
m_event((::eventfd)(0, 0)), | |
m_running(false) | |
{ | |
int flags; | |
struct epoll_event ev; | |
ev.events = EPOLLIN; | |
ev.data.fd = this->m_timer; | |
if ((this->m_event != INVALID_FD) && | |
// Make sure we can poll | |
(((this->m_poll = (::epoll_create)(1)) == INVALID_FD) || | |
((this->m_timer = (::timerfd_create)(CLOCK_MONOTONIC, 0)) == INVALID_FD) || | |
// And that read will never block | |
((flags = (::fcntl)(this->m_timer, F_GETFL, 0)) < 0) || | |
((::fcntl)(this->m_timer, F_SETFL, flags | O_NONBLOCK) != 0) || | |
((flags = (::fcntl)(this->m_event, F_GETFL, 0)) < 0) || | |
((::fcntl)(this->m_event, F_SETFL, flags | O_NONBLOCK) != 0) || | |
// And that poll listeners are in place | |
((::epoll_ctl)(this->m_poll, EPOLL_CTL_ADD, this->m_timer, &ev) != 0) || | |
((ev.data.fd = this->m_event), | |
((::epoll_ctl)(this->m_poll, EPOLL_CTL_ADD, this->m_event, &ev) != 0)))) | |
{ | |
this->Destroy(); | |
} | |
} | |
Timer::~Timer() | |
{ | |
// Destroy only if there's something to destroy | |
if (this->m_event != INVALID_FD) | |
{ | |
this->Destroy(); | |
} | |
} | |
void Timer::Destroy() | |
{ | |
this->Stop(); | |
(::close)(this->m_event); | |
this->m_event = INVALID_FD; | |
(::close)(this->m_timer); | |
this->m_timer = INVALID_FD; | |
} | |
bool Timer::Start(uint32_t ResetMillis, bool AutoReload) | |
{ | |
// Start only if input value is valid, initialized, and not running | |
if ((!ResetMillis) || | |
(this->m_event == INVALID_FD) || | |
(this->IsRunning())) | |
{ | |
return false; | |
} | |
uint64_t val; | |
// Discard old event and timer data | |
(::read)(this->m_event, &val, sizeof(val)); | |
(::read)(this->m_timer, &val, sizeof(val)); | |
struct itimerspec period; | |
period.it_value.tv_sec = ResetMillis / 1000; | |
period.it_value.tv_nsec = (ResetMillis % 1000) * 1000000; | |
// Copy data to interval if in auto reload mode | |
if (AutoReload) | |
{ | |
period.it_interval = period.it_value; | |
} | |
this->m_fired = 0; | |
return (this->m_running = ((::timerfd_settime)(this->m_timer, 0, &period, NULL) == 0)); | |
} | |
bool Timer::Stop() | |
{ | |
// Stop only if currently running | |
if (this->IsRunning()) | |
{ | |
struct itimerspec period = { { 0, 0 }, { 0, 0 } }; | |
// Stop timer by setting counters to 0 | |
if ((::timerfd_settime)(this->m_timer, 0, &period, NULL) != 0) | |
{ | |
return false; | |
} | |
this->m_running = false; | |
this->m_fired = 0; | |
// Clear poll | |
uint64_t val = 1; | |
(::write)(this->m_event, &val, sizeof(val)); | |
} | |
return true; | |
} | |
bool Timer::Wait(int Millis) | |
{ | |
// There isn't an easy way to write this function such that | |
// it'll handle multiple threads waiting on a timer to expire, | |
// so unfortunately this function can be used only by a single | |
// consumer thread. | |
// If fired, don't wait | |
if (this->m_fired > 0) | |
{ | |
(void)__sync_sub_and_fetch(&this->m_fired, 1); | |
} | |
else | |
{ | |
struct epoll_event ev; | |
// Try to poll the event fd | |
if ((!this->IsRunning()) || | |
((::epoll_wait)(this->m_poll, &ev, 1, Millis) <= 0)) | |
{ | |
return false; | |
} | |
uint64_t val; | |
// Check if aborted | |
if ((!this->IsRunning()) || | |
((::read)(this->m_timer, &val, sizeof(val)) != sizeof(val)) || | |
(!val)) | |
{ | |
return false; | |
} | |
(void)__sync_add_and_fetch(&this->m_fired, val - 1); | |
} | |
return true; | |
} | |
} // namespace utils |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @file Timer.h | |
* | |
* Declarations and definitions for a simple timer wrapper class. | |
* | |
* @author Isaac Garzon | |
* @since 05/04/2015 | |
*/ | |
#ifndef _UTILS_TIMER_H | |
#define _UTILS_TIMER_H | |
#include <stdint.h> | |
namespace utils | |
{ | |
/** | |
* A simple timer wrapper class. | |
*/ | |
class Timer | |
{ | |
public: | |
/** | |
* Timer constructor. | |
*/ | |
Timer(); | |
/** | |
* Default destructor. | |
*/ | |
~Timer(); | |
/** | |
* Provides an indication if the timer is currently operating. | |
* | |
* @return True if the timer has been started. False otherwise. | |
*/ | |
inline bool IsRunning() const | |
{ | |
return this->m_running; | |
} | |
/** | |
* Starts the timer. | |
* Requires IsRunning() == false. | |
* | |
* @param ResetMillis The timer's reset value in milliseconds. Must be a positive number. | |
* @param AutoReload Defines if the timer should automatically reload after expiration. | |
* | |
* @return True if the timer is initialized and started successfully. False otherwise. | |
*/ | |
bool Start(uint32_t ResetMillis, bool AutoReload); | |
/** | |
* Stops the timer. | |
* | |
* @note Has no effect if IsRunning() == false | |
* | |
* @return True if the timer is initialized and stopped successfully. False otherwise. | |
*/ | |
bool Stop(); | |
/** | |
* Waits for the timer to expire. | |
* | |
* @note This function is not meant to be called by more than one thread at a time. | |
* | |
* @param Millis The amount of milliseconds to wait for the timer to expire. | |
* -1 to wait inifinitely. | |
* | |
* @return True if the timer expired. False otherwise. | |
*/ | |
bool Wait(int Millis = 0); | |
private: | |
/** | |
* Destroys the timer. | |
*/ | |
void Destroy(); | |
uint64_t m_fired; | |
int m_timer; | |
int m_poll; | |
int m_event; | |
bool m_running; | |
}; // class Timer | |
} // namespace utils | |
#endif // !_UTILS_TIMER_H |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment