New make target: reset, which removes all generated files. Doxygen file and doc target added. New singleton Logger class. New TimerThread class. Cafe of broken dreams: ./other: ConcurrentMultiMap, Semaphore.cpp. run_test asks y/n question to run gdb if core created. test_Common.hpp to init Logger for tests.
parent
a87978afef
commit
e7e383fd7f
@ -1,35 +1,65 @@
|
||||
#ifndef COMMON_HPP
|
||||
#define COMMON_HPP
|
||||
|
||||
#include <cstring> // rindex
|
||||
#include <string> // string
|
||||
|
||||
#include <iostream>
|
||||
#include "Logger.hpp"
|
||||
|
||||
#ifndef NOTRACE
|
||||
#define TRACE(x) std::cout << x << " " << __PRETTY_FUNCTION__ << \
|
||||
" : " << __LINE__ << std::endl
|
||||
|
||||
#else
|
||||
/// @todo Get rid of the "warning: statement has no effect" compiler msgs
|
||||
#define TRACE(x) ""
|
||||
#endif
|
||||
#include <time.h> // timespec, CLOCK_REALTIME
|
||||
|
||||
#include <typeinfo> //typeid
|
||||
#include <stdexcept> // runtime_error
|
||||
#include <sstream> // ostringstream
|
||||
|
||||
#define STRINGIZE(c) #c
|
||||
|
||||
|
||||
#include <time.h>
|
||||
|
||||
inline timespec intIntervalTotimespec(const int & interval)
|
||||
inline timespec addSecTotimespec(const long int & sec)
|
||||
{
|
||||
timespec abs_time;
|
||||
clock_gettime ( CLOCK_REALTIME, &abs_time );
|
||||
abs_time.tv_nsec += interval * 1000000;
|
||||
if ( abs_time.tv_nsec >= 1000000000 ) {
|
||||
abs_time.tv_nsec -= 1000000000;
|
||||
abs_time.tv_sec += 1;
|
||||
}
|
||||
abs_time.tv_sec += sec;
|
||||
return abs_time;
|
||||
}
|
||||
|
||||
|
||||
inline const char* extractFilename( const char *path )
|
||||
{
|
||||
char const * f = rindex(path, '/');
|
||||
if (f) return ++f;
|
||||
return path;
|
||||
}
|
||||
|
||||
|
||||
class BadConversion : public std::runtime_error
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
BadConversion(std::string const& s) : std::runtime_error(s) { }
|
||||
|
||||
};
|
||||
|
||||
|
||||
template<typename T>
|
||||
inline std::string stringify(T const& x)
|
||||
{
|
||||
std::ostringstream o;
|
||||
if (!(o << x))
|
||||
throw BadConversion(std::string("stringify(") + typeid(x).name() + ")");
|
||||
return o.str();
|
||||
}
|
||||
|
||||
|
||||
inline std::string getTime( void )
|
||||
{
|
||||
time_t tim = time( NULL );
|
||||
struct tm * now = localtime( &tim );
|
||||
|
||||
std::string ret = stringify(now->tm_hour) + ":" +
|
||||
stringify(now->tm_min) + ":" +
|
||||
stringify(now->tm_sec);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif // COMMON_HPP
|
||||
|
@ -0,0 +1,88 @@
|
||||
#ifndef LOGGER_HPP
|
||||
#define LOGGER_HPP
|
||||
|
||||
#include "Singleton.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <set>
|
||||
#include <ostream>
|
||||
|
||||
|
||||
class Logger : public Singleton<Logger>
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
enum LogLevel {
|
||||
EMERG = 0, // system is unusable
|
||||
ALERT, // action must be taken immediately
|
||||
CRIT, // critical conditions
|
||||
ERR, // error conditions
|
||||
WARNING, // warning conditions
|
||||
NOTICE, // normal but significant condition
|
||||
INFO, // informational
|
||||
DEBUG, // debug-level messages
|
||||
FINEST
|
||||
};
|
||||
|
||||
Logger() {}
|
||||
virtual ~Logger() { m_ostream->flush(); }
|
||||
|
||||
static void init(std::ostream& log_stream );
|
||||
|
||||
static void setLogLevel ( const LogLevel loglevel );
|
||||
inline static LogLevel getLoglevel() { return m_logLevel; }
|
||||
|
||||
static void log_pointer( const LogLevel loglevel,
|
||||
const void* msg,
|
||||
const char* file,
|
||||
int line,
|
||||
const char* function);
|
||||
|
||||
static void log_string( const LogLevel loglevel,
|
||||
const char* msg,
|
||||
const char* file,
|
||||
int line,
|
||||
const char* function);
|
||||
|
||||
static void msg (const char* text);
|
||||
|
||||
|
||||
private:
|
||||
|
||||
Logger( const Logger& );
|
||||
Logger& operator=( const Logger& );
|
||||
|
||||
static LogLevel m_logLevel;
|
||||
static std::ostream *m_ostream;
|
||||
};
|
||||
|
||||
|
||||
#ifdef NO_TRACE
|
||||
#define MAX_LOFLEVEL Logger::DEBUG
|
||||
#else
|
||||
#define MAX_LOFLEVEL Logger::FINEST
|
||||
#endif
|
||||
|
||||
|
||||
#define TRACE \
|
||||
if(MAX_LOFLEVEL >= Logger::FINEST && \
|
||||
Logger::getInstance()->getLoglevel() >= Logger::FINEST ) \
|
||||
Logger::getInstance()->log_pointer( \
|
||||
Logger::FINEST, this, __FILE__, __LINE__, __PRETTY_FUNCTION__); \
|
||||
else (void)0
|
||||
|
||||
|
||||
#define LOG(level, msg) \
|
||||
if (MAX_LOFLEVEL >= level && \
|
||||
Logger::getInstance()->getLoglevel() >= Logger::FINEST ) \
|
||||
Logger::getInstance()->log_string( \
|
||||
level, msg, __FILE__, __LINE__, __PRETTY_FUNCTION__); \
|
||||
else (void)0
|
||||
|
||||
|
||||
/// @todo remove this
|
||||
#define MSG(text) Logger::getInstance()->msg(text)
|
||||
|
||||
|
||||
#endif // LOGGER_HPP
|
@ -0,0 +1,65 @@
|
||||
#ifndef TIMERTHREAD_HPP
|
||||
#define TIMERTHREAD_HPP
|
||||
|
||||
|
||||
#include "Thread.hpp"
|
||||
#include "Mutex.hpp"
|
||||
#include "ConditionVariable.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <ctime> // time_t
|
||||
|
||||
class TimerUser
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
virtual void timerExpired( void ) = 0;
|
||||
virtual void timerDestroyed( void ) = 0;
|
||||
|
||||
virtual ~TimerUser() {}
|
||||
|
||||
}; // class TimerUser
|
||||
|
||||
|
||||
|
||||
class TimerThread : public Thread
|
||||
{
|
||||
|
||||
private:
|
||||
|
||||
typedef struct {
|
||||
time_t periodTime;
|
||||
TimerUser* user;
|
||||
} UserEntry;
|
||||
|
||||
|
||||
public:
|
||||
|
||||
TimerThread();
|
||||
|
||||
virtual ~TimerThread();
|
||||
|
||||
void addTimerUser(TimerUser* user,
|
||||
const time_t expiration,
|
||||
const time_t periodTime = 0 );
|
||||
|
||||
bool removeTimerUser ( UserEntry userEntry );
|
||||
|
||||
// override to signal as well
|
||||
void stop();
|
||||
|
||||
|
||||
private:
|
||||
|
||||
void* run( void );
|
||||
|
||||
|
||||
Mutex m_mutex;
|
||||
ConditionVariable m_condVar;
|
||||
std::multimap<time_t, UserEntry> m_users;
|
||||
|
||||
}; // class TimerThread
|
||||
|
||||
|
||||
#endif // TIMERTHREAD_HPP
|
@ -0,0 +1,138 @@
|
||||
#ifndef CONCURRENTMULTIMAP_HPP
|
||||
#define CONCURRENTMULTIMAP_HPP
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include "Mutex.hpp"
|
||||
#include "ConditionVariable.hpp"
|
||||
#include "ScopedLock.hpp"
|
||||
#include "Common.hpp"
|
||||
|
||||
|
||||
template < typename T1, typename T2 >
|
||||
class ConcurrentMultiMap
|
||||
{
|
||||
public:
|
||||
|
||||
class CancelledException {};
|
||||
|
||||
ConcurrentMultiMap()
|
||||
: m_mutex()
|
||||
, m_condVar(m_mutex)
|
||||
, m_cancelled(false)
|
||||
, m_map()
|
||||
{
|
||||
TRACE;
|
||||
}
|
||||
|
||||
~ConcurrentMultiMap()
|
||||
{
|
||||
TRACE;
|
||||
}
|
||||
|
||||
typename std::multimap<T1, T2>::iterator begin()
|
||||
{
|
||||
return m_map.begin();
|
||||
}
|
||||
|
||||
typename std::multimap<T1, T2>::const_iterator begin() const
|
||||
{
|
||||
return m_map.begin();
|
||||
}
|
||||
|
||||
typename std::multimap<T1, T2>::iterator end()
|
||||
{
|
||||
return m_map.end();
|
||||
}
|
||||
|
||||
typename std::multimap<T1, T2>::const_iterator end() const
|
||||
{
|
||||
return m_map.end();
|
||||
}
|
||||
|
||||
|
||||
void add(T1 key, T2 value)
|
||||
{
|
||||
TRACE;
|
||||
ScopedLock sl(m_mutex);
|
||||
if (m_cancelled) throw CancelledException();
|
||||
m_map.insert( std::make_pair<T1, T1>( key, value) );
|
||||
m_condVar.signal();
|
||||
}
|
||||
|
||||
bool erase( T1 key )
|
||||
{
|
||||
TRACE;
|
||||
ScopedLock sl( m_mutex );
|
||||
if ( m_map.erase(key) > 0 ) {
|
||||
m_condVar.signal();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool removeValue( T2 value )
|
||||
{
|
||||
TRACE;
|
||||
ScopedLock sl(m_mutex);
|
||||
typename std::multimap<T1, T2>::iterator it;
|
||||
for ( it = m_map.begin(); it != m_map.end(); it++ ) {
|
||||
if (it->second == value) {
|
||||
m_map.erase(it);
|
||||
m_condVar.signal();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
typename std::pair< std::multimap<T1, T2>::iterator, std::multimap<T1, T2>::iterator > equal_range ( const T1& key )
|
||||
{
|
||||
TRACE;
|
||||
ScopedLock sl( m_mutex );
|
||||
return m_map.equal_range(key);
|
||||
}
|
||||
|
||||
T1 waitAndGetFirstKey()
|
||||
{
|
||||
TRACE;
|
||||
ScopedLock sl(m_mutex);
|
||||
|
||||
while ( m_map.empty() and not m_cancelled) {
|
||||
m_condVar.wait();
|
||||
}
|
||||
if (m_cancelled) throw CancelledException();
|
||||
|
||||
return m_map.begin()->first;
|
||||
}
|
||||
|
||||
bool empty() const
|
||||
{
|
||||
TRACE;
|
||||
ScopedLock sl(m_mutex);
|
||||
if (m_cancelled) throw CancelledException();
|
||||
return m_map.empty();
|
||||
}
|
||||
|
||||
void cancel( void )
|
||||
{
|
||||
TRACE;
|
||||
ScopedLock sl(m_mutex);
|
||||
m_cancelled = true;
|
||||
m_condVar.broadcast();
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
ConcurrentMultiMap& operator=( const ConcurrentMultiMap& );
|
||||
ConcurrentMultiMap( const ConcurrentMultiMap& );
|
||||
|
||||
mutable Mutex m_mutex;
|
||||
ConditionVariable m_condVar;
|
||||
bool m_cancelled;
|
||||
std::multimap<T1, T2> m_map;
|
||||
|
||||
};
|
||||
|
||||
#endif // CONCURRENTMULTIMAP_HPP
|
@ -0,0 +1,58 @@
|
||||
#include "Semaphore.hpp"
|
||||
|
||||
#include "ScopedLock.hpp"
|
||||
#include "Common.hpp"
|
||||
|
||||
Semaphore::Semaphore( int maxCount )
|
||||
: m_maxCount( maxCount )
|
||||
, m_mutex()
|
||||
{
|
||||
TRACE(this);
|
||||
sem_init( &m_semaphore, 0, maxCount );
|
||||
}
|
||||
|
||||
|
||||
Semaphore::~Semaphore( void )
|
||||
{
|
||||
TRACE(this);
|
||||
sem_destroy( &m_semaphore );
|
||||
}
|
||||
|
||||
|
||||
int Semaphore::lock( int interval )
|
||||
{
|
||||
TRACE(this);
|
||||
if ( interval == 0 ) {
|
||||
return sem_wait( &m_semaphore );
|
||||
} else {
|
||||
timespec tspec = intIntervalTotimespec( interval );
|
||||
return sem_timedwait( &m_semaphore, &tspec );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int Semaphore::tryLock( void )
|
||||
{
|
||||
TRACE(this);
|
||||
return sem_trywait( &m_semaphore );
|
||||
}
|
||||
|
||||
|
||||
int Semaphore::unLock( void )
|
||||
{
|
||||
TRACE(this);
|
||||
ScopedLock sl(m_mutex); // is it needed?
|
||||
int value;
|
||||
sem_getvalue( &m_semaphore, &value);
|
||||
if ( value == m_maxCount ) return -1;
|
||||
return sem_post( &m_semaphore );
|
||||
}
|
||||
|
||||
|
||||
int Semaphore::getCount( void ) const
|
||||
{
|
||||
TRACE(this);
|
||||
int retval;
|
||||
sem_getvalue( &m_semaphore, &retval );
|
||||
return retval;
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
#include "Common.hpp"
|
||||
|
@ -0,0 +1,55 @@
|
||||
#include "Logger.hpp"
|
||||
|
||||
#include <time.h> //time
|
||||
|
||||
|
||||
void Logger::init(std::ostream& log_stream )
|
||||
{
|
||||
m_ostream = &log_stream;
|
||||
}
|
||||
|
||||
|
||||
void Logger::setLogLevel ( const LogLevel loglevel )
|
||||
{
|
||||
m_logLevel = loglevel;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Logger::log_pointer( const LogLevel loglevel,
|
||||
const void* msg,
|
||||
const char* file,
|
||||
int line,
|
||||
const char* function)
|
||||
{
|
||||
*m_ostream << getTime() << " "
|
||||
<< extractFilename(file) << ":"
|
||||
<< line << " "
|
||||
<< function << " "
|
||||
<< "\"" << msg << "\""
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
|
||||
void Logger::log_string( const LogLevel loglevel,
|
||||
const char* msg,
|
||||
const char* file,
|
||||
int line,
|
||||
const char* function)
|
||||
{
|
||||
*m_ostream << getTime() << " "
|
||||
<< extractFilename(file) << ":"
|
||||
<< line << " "
|
||||
<< function << " "
|
||||
<< "\"" << msg << "\""
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
void Logger::msg(const char* text)
|
||||
{
|
||||
*m_ostream << text << std::endl;
|
||||
}
|
||||
|
||||
|
||||
Logger::LogLevel Logger::m_logLevel = Logger::FINEST;
|
||||
std::ostream* Logger::m_ostream = 0;
|
@ -0,0 +1,117 @@
|
||||
#include "TimerThread.hpp"
|
||||
|
||||
#include "Common.hpp"
|
||||
#include "ScopedLock.hpp"
|
||||
|
||||
#include <errno.h> // ETIMEDOUT
|
||||
|
||||
|
||||
|
||||
TimerThread::TimerThread()
|
||||
: m_mutex()
|
||||
, m_condVar(m_mutex)
|
||||
, m_users()
|
||||
{
|
||||
TRACE;
|
||||
}
|
||||
|
||||
|
||||
TimerThread::~TimerThread()
|
||||
{
|
||||
TRACE;
|
||||
}
|
||||
|
||||
|
||||
void TimerThread::addTimerUser(TimerUser* user,
|
||||
const time_t expiration,
|
||||
const time_t periodTime)
|
||||
{
|
||||
TRACE;
|
||||
ScopedLock sl( m_mutex );
|
||||
UserEntry userEntry = { periodTime, user };
|
||||
m_users.insert( std::pair<time_t, UserEntry>( expiration, userEntry ) );
|
||||
m_condVar.signal();
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool TimerThread::removeTimerUser ( UserEntry userEntry )
|
||||
{
|
||||
TRACE;
|
||||
ScopedLock sl( m_mutex );
|
||||
std::multimap<time_t, UserEntry>::iterator it;
|
||||
bool found(false);
|
||||
for ( it = m_users.begin(); it != m_users.end(); it++ ) {
|
||||
if ( it->second.user == userEntry.user ) {
|
||||
m_users.erase(it);
|
||||
m_condVar.signal();
|
||||
found = true; // one usercan be registered multiple times
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
// override to signal as well
|
||||
void TimerThread::stop()
|
||||
{
|
||||
TRACE;
|
||||
ScopedLock sl( m_mutex );
|
||||
m_isRunning = false;
|
||||
m_condVar.signal();
|
||||
}
|
||||
|
||||
|
||||
void* TimerThread::run( void )
|
||||
{
|
||||
TRACE;
|
||||
time_t nextExpiration;
|
||||
|
||||
std::multimap<time_t, UserEntry> tmp;
|
||||
std::multimap<time_t, UserEntry>::iterator it;
|
||||
std::pair<std::multimap<time_t, UserEntry>::iterator,
|
||||
std::multimap<time_t, UserEntry>::iterator> ret;
|
||||
|
||||
while( m_isRunning ) {
|
||||
|
||||
while ( m_users.empty() and m_isRunning ) {
|
||||
m_condVar.wait();
|
||||
}
|
||||
|
||||
if ( not m_isRunning) {
|
||||
LOG( Logger::FINEST, "return empty handed");
|
||||
return 0;
|
||||
}
|
||||
|
||||
nextExpiration = m_users.begin()->first;
|
||||
|
||||
// timer deleted / added, get nextExpiration again
|
||||
if ( m_condVar.wait( nextExpiration ) != ETIMEDOUT ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// notify & remove
|
||||
/// @todo lock here?
|
||||
// m_mutex.lock();
|
||||
ret = m_users.equal_range( nextExpiration );
|
||||
|
||||
/// @todo modify key values in multimap, must be a better way
|
||||
tmp.clear();
|
||||
for ( it = ret.first; it != ret.second; it++ ) {
|
||||
it->second.user->timerExpired();
|
||||
if ( it->second.periodTime ) tmp.insert(std::pair<time_t, UserEntry>(
|
||||
it->second.periodTime, it->second ) );
|
||||
}
|
||||
m_users.erase( nextExpiration );
|
||||
m_users.insert( tmp.begin(), tmp.end() );
|
||||
// m_mutex.unlock();
|
||||
|
||||
}
|
||||
|
||||
if ( not m_users.empty() ) {
|
||||
LOG( Logger::FINEST, "return full handed");
|
||||
for ( it = m_users.begin(); it != m_users.end(); it++ ) {
|
||||
it->second.user->timerDestroyed();
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
#ifndef TEST_COMMON_HPP
|
||||
#define TEST_COMMON_HPP
|
||||
|
||||
#include <cxxtest/TestSuite.h>
|
||||
#include <cxxtest/GlobalFixture.h>
|
||||
|
||||
#include "Common.hpp"
|
||||
#include <iostream>
|
||||
|
||||
class TestCommon : public CxxTest::GlobalFixture
|
||||
{
|
||||
bool setUpWorld()
|
||||
{
|
||||
Logger::createInstance();
|
||||
Logger::init(std::cout);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool tearDownWorld()
|
||||
{
|
||||
Logger::destroy();
|
||||
return true;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
static TestCommon testCommon;
|
||||
|
||||
#define TEST_HEADER \
|
||||
MSG( std::string("\n+++ ").append(__PRETTY_FUNCTION__).append(" +++\n").c_str());
|
||||
|
||||
|
||||
#endif // TEST_COMMON_HPP
|
@ -0,0 +1,91 @@
|
||||
#include <cxxtest/TestSuite.h>
|
||||
|
||||
#include "test_Common.hpp"
|
||||
|
||||
#include "TimerThread.hpp"
|
||||
|
||||
|
||||
class TestTimerThread : public CxxTest::TestSuite
|
||||
{
|
||||
|
||||
private:
|
||||
|
||||
class DummyTimerUser : public TimerUser
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
DummyTimerUser( void ) : m_counter(0) { TRACE; }
|
||||
~DummyTimerUser( void ) { TRACE; }
|
||||
void timerExpired( void ) { TRACE; m_counter++; }
|
||||
void timerDestroyed( void ) { TRACE; m_counter += 100; }
|
||||
|
||||
int m_counter;
|
||||
|
||||
}; // class DummyTimerUser
|
||||
|
||||
|
||||
|
||||
public:
|
||||
|
||||
void testBasic( void )
|
||||
{
|
||||
TEST_HEADER;
|
||||
TimerThread* tt = new TimerThread();
|
||||
tt->start();
|
||||
sleep(1);
|
||||
|
||||
DummyTimerUser *user = new DummyTimerUser();
|
||||
tt->addTimerUser( user, 2 );
|
||||
|
||||
sleep(4);
|
||||
tt->stop();
|
||||
sleep(1);
|
||||
|
||||
TS_ASSERT_EQUALS( user->m_counter, 1 );
|
||||
|
||||
delete tt;
|
||||
delete user;
|
||||
}
|
||||
|
||||
void testPeriodic( void )
|
||||
{
|
||||
TEST_HEADER;
|
||||
TimerThread* tt = new TimerThread();
|
||||
tt->start();
|
||||
sleep(1);
|
||||
|
||||
DummyTimerUser *user = new DummyTimerUser();
|
||||
tt->addTimerUser( user, 2, 1 );
|
||||
|
||||
sleep(6);
|
||||
tt->stop();
|
||||
sleep(1);
|
||||
|
||||
TS_ASSERT_EQUALS( user->m_counter, 104 );
|
||||
|
||||
delete tt;
|
||||
delete user;
|
||||
}
|
||||
|
||||
void testDestroyed( void )
|
||||
{
|
||||
TEST_HEADER;
|
||||
TimerThread* tt = new TimerThread();
|
||||
tt->start();
|
||||
sleep(1);
|
||||
|
||||
DummyTimerUser *user = new DummyTimerUser();
|
||||
tt->addTimerUser( user, 10 );
|
||||
|
||||
sleep(2);
|
||||
tt->stop();
|
||||
sleep(1);
|
||||
|
||||
TS_ASSERT_EQUALS( user->m_counter, 100 );
|
||||
|
||||
delete tt;
|
||||
delete user;
|
||||
}
|
||||
|
||||
};
|
Loading…
Reference in new issue