From acad886cb4119e4077783b063d85e74cbe266106 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Mon, 13 Jul 2009 11:09:26 +0000 Subject: [PATCH] Add support for thread-specific log targets. A worker thread can now have its own log target which will be used directly by the log functions instead of buffering log output in the main thread; the GUI thread in the thread sample shows how it works. git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@61422 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- docs/changes.txt | 6 ++ include/wx/log.h | 43 +++++++------ include/wx/private/threadinfo.h | 43 +++++++++++++ interface/wx/log.h | 52 ++++++++++++++-- samples/thread/thread.cpp | 26 +++++++- src/common/log.cpp | 103 ++++++++++++++++++++++++++------ 6 files changed, 228 insertions(+), 45 deletions(-) create mode 100644 include/wx/private/threadinfo.h diff --git a/docs/changes.txt b/docs/changes.txt index 3a45abc5f4..75dbb047df 100644 --- a/docs/changes.txt +++ b/docs/changes.txt @@ -358,6 +358,12 @@ All: - Added wxTempFile::Flush(). - Added support for wxLongLong and wxULongLong in wxVariant. - Added wxVector::swap(). +- Many wxLog improvements: + * wxLogXXX() functions are now thread-safe. + * Log levels can now be set independently for different log components. + * wxLog::DoLogRecord() has access to the location of the log message + (file, line and function name) and id of the thread which generated it. + * SetThreadActiveTarget() allows to set up thread-specific log targets. All (GUI): diff --git a/include/wx/log.h b/include/wx/log.h index b7b28a6fc5..522c789f1c 100644 --- a/include/wx/log.h +++ b/include/wx/log.h @@ -385,24 +385,22 @@ public: // 17 modal dialogs one after another) virtual void Flush(); - // flush the active target if any - static void FlushActive() - { - if ( !ms_suspendCount ) - { - wxLog *log = GetActiveTarget(); - if ( log ) - log->Flush(); - } - } + // flush the active target if any and also output any pending messages from + // background threads + static void FlushActive(); - // only one sink is active at each moment - // get current log target, will call wxApp::CreateLogTarget() to - // create one if none exists + // only one sink is active at each moment get current log target, will call + // wxAppTraits::CreateLogTarget() to create one if none exists static wxLog *GetActiveTarget(); - // change log target, pLogger may be NULL - static wxLog *SetActiveTarget(wxLog *pLogger); + // change log target, logger may be NULL + static wxLog *SetActiveTarget(wxLog *logger); + +#if wxUSE_THREADS + // change log target for the current thread only, shouldn't be called from + // the main thread as it doesn't use thread-specific log target + static wxLog *SetThreadActiveTarget(wxLog *logger); +#endif // wxUSE_THREADS // suspend the message flushing of the main target until the next call // to Resume() - this is mainly for internal use (to prevent wxYield() @@ -580,11 +578,18 @@ protected: unsigned LogLastRepeatIfNeeded(); private: - // called from OnLog() if it's called from the main thread and from Flush() +#if wxUSE_THREADS + // called from FlushActive() to really log any buffered messages logged + // from the other threads + void FlushThreadMessages(); +#endif // wxUSE_THREADS + + // called from OnLog() if it's called from the main thread or if we have a + // (presumably MT-safe) thread-specific logger and by FlushThreadMessages() // when it plays back the buffered messages logged from the other threads - void OnLogInMainThread(wxLogLevel level, - const wxString& msg, - const wxLogRecordInfo& info); + void CallDoLogNow(wxLogLevel level, + const wxString& msg, + const wxLogRecordInfo& info); // static variables diff --git a/include/wx/private/threadinfo.h b/include/wx/private/threadinfo.h new file mode 100644 index 0000000000..87eee51cc6 --- /dev/null +++ b/include/wx/private/threadinfo.h @@ -0,0 +1,43 @@ +/////////////////////////////////////////////////////////////////////////////// +// Name: wx/private/threadinfo.h +// Purpose: declaration of wxThreadSpecificInfo: thread-specific information +// Author: Vadim Zeitlin +// Created: 2009-07-13 +// RCS-ID: $Id: wxhead.h,v 1.11 2009-06-29 10:23:04 zeitlin Exp $ +// Copyright: (c) 2009 Vadim Zeitlin +// Licence: wxWindows licence +/////////////////////////////////////////////////////////////////////////////// + +#ifndef _WX_PRIVATE_THREADINFO_H_ +#define _WX_PRIVATE_THREADINFO_H_ + +#if wxUSE_THREADS + +#include "wx/tls.h" + +class WXDLLIMPEXP_FWD_BASE wxLog; + +// ---------------------------------------------------------------------------- +// wxThreadSpecificInfo: contains all thread-specific information used by wx +// ---------------------------------------------------------------------------- + +// currently the only thread-specific information we use is the active wxLog +// target but more could be added in the future (e.g. current wxLocale would be +// a likely candidate) and we will group all of them in this struct to avoid +// consuming more TLS slots than necessary as there is only a limited number of +// them + +// NB: this must be a POD to be stored in TLS +struct wxThreadSpecificInfo +{ + wxLog *logger; +}; + +// currently this is defined in src/common/log.cpp +extern wxTLS_TYPE(wxThreadSpecificInfo) wxThreadInfoVar; +#define wxThreadInfo wxTLS_VALUE(wxThreadInfoVar) + +#endif // wxUSE_THREADS + +#endif // _WX_PRIVATE_THREADINFO_H_ + diff --git a/interface/wx/log.h b/interface/wx/log.h index 428f9fb660..d23af95b51 100644 --- a/interface/wx/log.h +++ b/interface/wx/log.h @@ -759,24 +759,36 @@ public: If the buffer is already empty, nothing happens. - It should only be called from the main application thread. - If you override this method in a derived class, call the base class - version first, before doing anything else, to ensure that any buffered - messages from the other threads are logged. + version first, before doing anything else. */ virtual void Flush(); /** Flushes the current log target if any, does nothing if there is none. - As Flush() itself, this method should only be called from the main - application thread. + When this method is called from the main thread context, it also + flushes any previously buffered messages logged by the other threads. + When it is called from the other threads it simply calls Flush() on the + currently active log target, so it mostly makes sense to do this if a + thread has its own logger set with SetThreadActiveTarget(). */ static void FlushActive(); /** Returns the pointer to the active log target (may be @NULL). + + Notice that if SetActiveTarget() hadn't been previously explicitly + called, this function will by default try to create a log target by + calling wxAppTraits::CreateLogTarget() which may be overridden in a + user-defined traits class to change the default behaviour. You may also + call DontCreateOnDemand() to disable this behaviour. + + When this function is called from threads other than main one, + auto-creation doesn't happen. But if the thread has a thread-specific + log target previously set by SetThreadActiveTarget(), it is returned + instead of the global one. Otherwise, the global log target is + returned. */ static wxLog* GetActiveTarget(); @@ -866,6 +878,8 @@ public: To suppress logging use a new instance of wxLogNull not @NULL. If the active log target is set to @NULL a new default log target will be created when logging occurs. + + @see SetThreadActiveTarget() */ static wxLog* SetActiveTarget(wxLog* logtarget); @@ -906,6 +920,32 @@ public: */ static void SetRepetitionCounting(bool repetCounting = true); + /** + Sets a thread-specific log target. + + The log target passed to this function will be used for all messages + logged by the current thread using the usual wxLog functions. This + shouldn't be called from the main thread which never uses a thread- + specific log target but can be used for the other threads to handle + thread logging completely separately; instead of buffering thread log + messages in the main thread logger. + + Notice that unlike for SetActiveTarget(), wxWidgets does not destroy + the thread-specific log targets when the thread terminates so doing + this is your responsibility. + + This method is only available if @c wxUSE_THREADS is 1, i.e. wxWidgets + was compiled with threads support. + + @param logger + The new thread-specific log target, possibly @NULL. + @return + The previous thread-specific log target, initially @NULL. + + @since 2.9.1 + */ + static wxLog *SetThreadActiveTarget(wxLog *logger); + /** Sets the timestamp format prepended by the default log targets to all messages. The string may contain any normal characters as well as % diff --git a/samples/thread/thread.cpp b/samples/thread/thread.cpp index 74afcec5a6..06232403a4 100644 --- a/samples/thread/thread.cpp +++ b/samples/thread/thread.cpp @@ -5,7 +5,7 @@ // Modified by: // Created: 06/16/98 // RCS-ID: $Id$ -// Copyright: (c) 1998-2002 wxWidgets team +// Copyright: (c) 1998-2009 wxWidgets team // Licence: wxWindows license ///////////////////////////////////////////////////////////////////////////// @@ -720,8 +720,8 @@ void MyFrame::OnAbout(wxCommandEvent& WXUNUSED(event) ) wxMessageDialog dialog(this, _T("wxWidgets multithreaded application sample\n") _T("(c) 1998 Julian Smart, Guilhem Lavaux\n") - _T("(c) 1999 Vadim Zeitlin\n") - _T("(c) 2000 Robert Roebling"), + _T("(c) 2000 Robert Roebling\n") + _T("(c) 1999,2009 Vadim Zeitlin"), _T("About wxThread sample"), wxOK | wxICON_INFORMATION); @@ -1003,6 +1003,14 @@ wxThread::ExitCode MyWorkerThread::Entry() wxThread::ExitCode MyGUIThread::Entry() { + // this goes to the main window + wxLogMessage("GUI thread starting"); + + // use a thread-specific log target for this thread to show that its + // messages don't appear in the main window while it runs + wxLogBuffer logBuf; + wxLog::SetThreadActiveTarget(&logBuf); + for (int i=0; iOnLogInMainThread(level, msg, info); + logger->CallDoLogNow(level, msg, info); } void -wxLog::OnLogInMainThread(wxLogLevel level, - const wxString& msg, - const wxLogRecordInfo& info) +wxLog::CallDoLogNow(wxLogLevel level, + const wxString& msg, + const wxLogRecordInfo& info) { if ( GetRepetitionCounting() ) { @@ -429,6 +449,19 @@ void wxLog::DoLog(wxLogLevel WXUNUSED(level), const wchar_t *wzString, time_t t) wxLog *wxLog::GetActiveTarget() { +#if wxUSE_THREADS + if ( !wxThread::IsMain() ) + { + // check if we have a thread-specific log target + wxLog * const logger = wxThreadInfo.logger; + + // the code below should be only executed for the main thread as + // CreateLogTarget() is not meant for auto-creating log targets for + // worker threads so skip it in any case + return logger ? logger : ms_pLogger; + } +#endif // wxUSE_THREADS + if ( ms_bAutoCreate && ms_pLogger == NULL ) { // prevent infinite recursion if someone calls wxLogXXX() from // wxApp::CreateLogTarget() @@ -465,6 +498,22 @@ wxLog *wxLog::SetActiveTarget(wxLog *pLogger) return pOldLogger; } +#if wxUSE_THREADS +/* static */ +wxLog *wxLog::SetThreadActiveTarget(wxLog *logger) +{ + wxASSERT_MSG( !wxThread::IsMain(), "use SetActiveTarget() for main thread" ); + + wxLog * const oldLogger = wxThreadInfo.logger; + if ( oldLogger ) + oldLogger->Flush(); + + wxThreadInfo.logger = logger; + + return oldLogger; +} +#endif // wxUSE_THREADS + void wxLog::DontCreateOnDemand() { ms_bAutoCreate = false; @@ -582,12 +631,10 @@ void wxLog::TimeStamp(wxString *str) #endif // wxUSE_DATETIME } -void wxLog::Flush() -{ #if wxUSE_THREADS - wxASSERT_MSG( wxThread::IsMain(), - "should be called from the main thread only" ); +void wxLog::FlushThreadMessages() +{ // check if we have queued messages from other threads wxLogRecords bufferedLogRecords; @@ -605,14 +652,36 @@ void wxLog::Flush() it != bufferedLogRecords.end(); ++it ) { - OnLogInMainThread(it->level, it->msg, it->info); + CallDoLogNow(it->level, it->msg, it->info); } } +} + #endif // wxUSE_THREADS +void wxLog::Flush() +{ LogLastRepeatIfNeeded(); } +/* static */ +void wxLog::FlushActive() +{ + if ( ms_suspendCount ) + return; + + wxLog * const log = GetActiveTarget(); + if ( log ) + { +#if wxUSE_THREADS + if ( wxThread::IsMain() ) + log->FlushThreadMessages(); +#endif // wxUSE_THREADS + + log->Flush(); + } +} + // ---------------------------------------------------------------------------- // wxLogBuffer implementation // ---------------------------------------------------------------------------- -- 2.45.2