Skip to content

Instantly share code, notes, and snippets.

@iscgar
Last active August 29, 2015 14:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save iscgar/501ad7df92b6e9531a50 to your computer and use it in GitHub Desktop.
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.
/**
* @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
/**
* @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