c++ - Thread safe operator<< -
i have quite complicated problem logger class. singleton pattern logging class. thread created taking out items queue , logging them. works fine , error occurs segmentation fault. happening more before decided put mutex on whole method chain. mutex dont understand why segmentation fault occuring. class got quite complicated because of operator<<
usage. problem operator template run many times, many items passed using <<
. because of other threads can cut in between templete calls.
the general usage looks this: 1. instance method called (creating or pointing instance pointer (singleton). mutex locked @ moment. 2. called methods called, example operator<< template. 3. finishing method called, placing log in queue , unlocking mutex.
i have edited code , tried take fata gathering out of singleton class proxy class.
main.c:
#include <iostream> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include "clogger.h" #include "ctestclass.h" using namespace std; int main() { clogger::instance().log.startlog("/home/lukasz/pulpit/test.txt", true); clogger::instance().log.setloglevel(clogger::eloglevel::edebug); clogger::instance().printf("print test"); clogger::instance().printf("print test"); clogger::instance().printf("print test"); clogger::instance() << "stream test" ; clogger::instance() << "stream test" ; clogger::instance() << "stream test" ; //ctestclass test1(1); //ctestclass test2(2); //ctestclass test3(3); sleep(3); clogger::instance().log.stoplog(); return 0; }
clogger.h:
#ifndef clogger_h_ #define clogger_h_ #include <iostream> #include <deque> #include <string> #include <mutex> #include <condition_variable> #include <pthread.h> #include <ostream> #include <fstream> #include <sstream> #include <ctime> #include <iomanip> #include <sys/time.h> #include <stdarg.h> #include <assert.h> #include "cteebuf.h" using namespace std; class cloggerproxy; /*! * \brief singleton class used logging */ class clogger { public: /*! * \brief describes log level of called \ref clogger object. */ enum class eloglevel { enone = 0, eerror, ewarning, einfo, edebug }; /*! * structure describing single log item: */ struct logline_t { string logstring; /*!< string line saved file (and printed cout). */ eloglevel loglevel; /*!< \ref eloglevel of line. */ timeval currenttime; /*!< time stamp of current log line */ }; static clogger* internalinstance(eloglevel llevel = eloglevel::edebug); static cloggerproxy instance(eloglevel llevel = eloglevel::edebug); bool startlog(string filename, bool verbose); void setloglevel(eloglevel ll); void stoplog(); void finaliseline(logline_t* log); protected: virtual void threadloop(); private: clogger() {}; // private can not called clogger(clogger const&) {}; // copy constructor private clogger& operator= (clogger const&) {}; // assignment operator private /*!< global static pointer used ensure single instance of class */ static clogger* mp_instance; bool m_logstarted; eloglevel m_userdefinedloglevel; ofstream m_logfilestream; bool m_verbose; bool m_finishlog; timeval m_initialtime; static void * threadhelper(void* handler) { ((clogger*)handler)->threadloop(); return null; } deque<logline_t*> m_data; mutex m_mutex2; condition_variable m_cv; pthread_t m_thread; logline_t pop_front(); void push_back(logline_t* s); }; /*! * raii class used destructor, add log item queue */ class cloggerproxy { public: clogger &log; cloggerproxy(clogger &logger) : log(logger) { mp_logline = new clogger::logline_t; gettimeofday(&mp_logline->currenttime, null); } ~cloggerproxy() { log.finaliseline(mp_logline); } void printf(const char* text, ...); /*! * takes data stream , adds current string. * @param t stream item * @return \ref object address */ template <typename t> cloggerproxy& operator<< (const t &t) { ostringstream stream; stream << t; mp_logline->logstring = (stream.str() + " "); return *this; } private: clogger::logline_t* mp_logline; }; #endif /* clogger_h_ */
clogger.cpp:
#include "clogger.h" using namespace std; clogger* clogger::mp_instance = null; /*! * function called create instance of class. * calling constructor publicly not allowed. constructor * private , called instance function. * @param llevel log level current object */ clogger* clogger::internalinstance(eloglevel llevel) { // allow 1 instance of class generated. if (!mp_instance) { mp_instance = new clogger; assert(mp_instance); } return mp_instance; } /*! * method called in order use methods * within objects. * @param llevel log level current object */ cloggerproxy clogger::instance(eloglevel llevel) { return cloggerproxy(*internalinstance(llevel)); } /*! * \brief starts logging system. * * method creates , opens log file, * opens , creates threadloop messages deque. * @param filename desired log file path, * @param verbose when set true, logging printed standard output. */ bool clogger::startlog(string filename, bool verbose) { if(remove(filename.c_str()) != 0) perror( "error deleting file" ); m_logfilestream.open(filename.c_str(), ios::out | ios::app); if (!m_logfilestream.is_open()) { cout << "could not open log file " << filename << endl; return false; } m_finishlog = false; m_verbose = verbose; m_logstarted = true; gettimeofday(&m_initialtime, null); return (pthread_create(&(m_thread), null, threadhelper, this) == 0); } /*! * \brief puts \ref logline_t object @ end of queue * @param s object added queue */ void clogger::push_back(logline_t* s) { unique_lock<mutex> ul(m_mutex2); m_data.emplace_back(move(s)); m_cv.notify_all(); } /*! * \brief takes \ref logline_t object beggining of queue * @return first \ref logline_t object */ clogger::logline_t clogger::pop_front() { unique_lock<mutex> ul(m_mutex2); m_cv.wait(ul, [this]() { return !m_data.empty(); }); logline_t retval = move(*m_data.front()); assert(m_data.front()); delete m_data.front(); m_data.front() = null; m_data.pop_front(); return retval; } /*! * \brief sets log level whole \ref clogger object. * if \ref m_logline equal or higher set level, log * going printed. * @param llevel desired user define log level. */ void clogger::setloglevel(eloglevel llevel) { m_userdefinedloglevel = llevel; } /*! * \brief stops logging system. * last final logline being added , logging thread * being closed. */ void clogger::stoplog() { m_finishlog = true; //instance(eloglevel::enone).log << "clogger stop"; //pthread_join(m_thread, null); } /*! * function should run in \ref cloggerproxy destructor. * pushes gathered stream queue. */ void clogger::finaliseline(logline_t* log) { if (log->logstring.size() > 0) push_back(log); else delete log; } /*! * \brief adds text log string in printf c way. * works faster operator<< , more atomic. * @param text pointer character string. * @param ... argptr parameters */ void cloggerproxy::printf(const char* text, ...) { va_list argptr; va_start(argptr, text); char* output = null; vasprintf(&output, text, argptr); mp_logline->logstring = output; va_end(argptr); } /*! * loop running in separate thread. take items of * log deque object (if there any) , saves them file. */ void clogger::threadloop() { logline_t logline; const string loglevelsstrings[] = {"enone", "eerror", "ewarning", "einfo", "edebug" }; coteestream tee; tee.add(m_logfilestream); if (m_verbose) tee.add(cout); struct sched_param param; param.__sched_priority = 0; if(!sched_setscheduler(0, sched_idle, ¶m)) instance().printf("clogger scheduler policy set %d", sched_getscheduler(0)); int secs = 0; int h = 0; int m = 0; int s = 0; { logline = pop_front(); // waits here new lines secs = logline.currenttime.tv_sec - m_initialtime.tv_sec; h = secs / 3600; m = ( secs % 3600 ) / 60; s = ( secs % 3600 ) % 60; tee << "[" << setw(2) << setfill('0') << h << ":" << setw(2) << setfill('0') << m << ":" << setw(2) << setfill('0') << s << "." << setw(6) << setfill('0') << logline.currenttime.tv_usec << "]" << "[" << setw(2) << setfill('0') << m_data.size() << "]" << "[" << loglevelsstrings[(int)logline.loglevel] << "] " << logline.logstring << "\n" << flush; } //while(!(m_finishlog && m_data.empty())); while(1); m_logfilestream.close(); }
there several problems code.
singleton
// allow 1 instance of class generated. if (!mp_instance) { mp_instance = new clogger; assert(mp_instance); }
this classic problem. may called different threads @ same time, , not thread safe. may end several instances of singleton.
the queue (m_data)
clients of logger put messages queue (apparently secured m_mutext
).
m_data.emplace_back(move(s)); m_cv.notify_all();
your logger thread removes messages in own thread (secured m_mutex2
).
unique_lock<mutex> ul(m_mutex2); m_cv.wait(ul, [this]() { return !m_data.empty(); }); logline_t retval = move(*m_data.front()); assert(m_data.front()); delete m_data.front(); m_data.front() = null; m_data.pop_front(); return retval;
the problem here is, use 2 different mutexes synchronize access same object. cannot work.
in addition access m_data
in thread without locking @ all:
<< setw(2) << setfill('0') << m_data.size()
or
while(!(m_finishlog && m_data.empty()));
the log message (mp_logline)
you try lock data. pointer log message meant used single thread @ time. store in main logger class accessed threads. have proxy logger private thread using it. store message there until it's finished, , add queue.
generally said, minimize amount of data locked. if rework code , object needing locking queue, on right way.
Comments
Post a Comment