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
This commit is contained in:
Vadim Zeitlin
2009-07-13 11:09:26 +00:00
parent 409aa9e1ef
commit acad886cb4
6 changed files with 228 additions and 45 deletions

View File

@@ -358,6 +358,12 @@ All:
- Added wxTempFile::Flush(). - Added wxTempFile::Flush().
- Added support for wxLongLong and wxULongLong in wxVariant. - Added support for wxLongLong and wxULongLong in wxVariant.
- Added wxVector::swap(). - 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): All (GUI):

View File

@@ -385,24 +385,22 @@ public:
// 17 modal dialogs one after another) // 17 modal dialogs one after another)
virtual void Flush(); virtual void Flush();
// flush the active target if any // flush the active target if any and also output any pending messages from
static void FlushActive() // background threads
{ static void FlushActive();
if ( !ms_suspendCount )
{
wxLog *log = GetActiveTarget();
if ( log )
log->Flush();
}
}
// only one sink is active at each moment // only one sink is active at each moment get current log target, will call
// get current log target, will call wxApp::CreateLogTarget() to // wxAppTraits::CreateLogTarget() to create one if none exists
// create one if none exists
static wxLog *GetActiveTarget(); static wxLog *GetActiveTarget();
// change log target, pLogger may be NULL // change log target, logger may be NULL
static wxLog *SetActiveTarget(wxLog *pLogger); 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 // suspend the message flushing of the main target until the next call
// to Resume() - this is mainly for internal use (to prevent wxYield() // to Resume() - this is mainly for internal use (to prevent wxYield()
@@ -580,9 +578,16 @@ protected:
unsigned LogLastRepeatIfNeeded(); unsigned LogLastRepeatIfNeeded();
private: 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 // when it plays back the buffered messages logged from the other threads
void OnLogInMainThread(wxLogLevel level, void CallDoLogNow(wxLogLevel level,
const wxString& msg, const wxString& msg,
const wxLogRecordInfo& info); const wxLogRecordInfo& info);

View File

@@ -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 <vadim@wxwidgets.org>
// 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_

View File

@@ -759,24 +759,36 @@ public:
If the buffer is already empty, nothing happens. 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 If you override this method in a derived class, call the base class
version first, before doing anything else, to ensure that any buffered version first, before doing anything else.
messages from the other threads are logged.
*/ */
virtual void Flush(); virtual void Flush();
/** /**
Flushes the current log target if any, does nothing if there is none. 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 When this method is called from the main thread context, it also
application thread. 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(); static void FlushActive();
/** /**
Returns the pointer to the active log target (may be @NULL). 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(); static wxLog* GetActiveTarget();
@@ -866,6 +878,8 @@ public:
To suppress logging use a new instance of wxLogNull not @NULL. If the 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 active log target is set to @NULL a new default log target will be
created when logging occurs. created when logging occurs.
@see SetThreadActiveTarget()
*/ */
static wxLog* SetActiveTarget(wxLog* logtarget); static wxLog* SetActiveTarget(wxLog* logtarget);
@@ -906,6 +920,32 @@ public:
*/ */
static void SetRepetitionCounting(bool repetCounting = true); 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 Sets the timestamp format prepended by the default log targets to all
messages. The string may contain any normal characters as well as % messages. The string may contain any normal characters as well as %

View File

@@ -5,7 +5,7 @@
// Modified by: // Modified by:
// Created: 06/16/98 // Created: 06/16/98
// RCS-ID: $Id$ // RCS-ID: $Id$
// Copyright: (c) 1998-2002 wxWidgets team // Copyright: (c) 1998-2009 wxWidgets team
// Licence: wxWindows license // Licence: wxWindows license
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
@@ -720,8 +720,8 @@ void MyFrame::OnAbout(wxCommandEvent& WXUNUSED(event) )
wxMessageDialog dialog(this, wxMessageDialog dialog(this,
_T("wxWidgets multithreaded application sample\n") _T("wxWidgets multithreaded application sample\n")
_T("(c) 1998 Julian Smart, Guilhem Lavaux\n") _T("(c) 1998 Julian Smart, Guilhem Lavaux\n")
_T("(c) 1999 Vadim Zeitlin\n") _T("(c) 2000 Robert Roebling\n")
_T("(c) 2000 Robert Roebling"), _T("(c) 1999,2009 Vadim Zeitlin"),
_T("About wxThread sample"), _T("About wxThread sample"),
wxOK | wxICON_INFORMATION); wxOK | wxICON_INFORMATION);
@@ -1003,6 +1003,14 @@ wxThread::ExitCode MyWorkerThread::Entry()
wxThread::ExitCode MyGUIThread::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; i<GUITHREAD_NUM_UPDATES && !TestDestroy(); i++) for (int i=0; i<GUITHREAD_NUM_UPDATES && !TestDestroy(); i++)
{ {
// inform the GUI toolkit that we're going to use GUI functions // inform the GUI toolkit that we're going to use GUI functions
@@ -1029,10 +1037,22 @@ wxThread::ExitCode MyGUIThread::Entry()
event.SetInt(i+1); event.SetInt(i+1);
wxQueueEvent( m_dlg, event.Clone() ); wxQueueEvent( m_dlg, event.Clone() );
if ( !((i + 1) % 10) )
{
// this message will go to the buffer
wxLogMessage("Step #%d.", i + 1);
}
// give the main thread the time to refresh before we lock the GUI mutex again // give the main thread the time to refresh before we lock the GUI mutex again
// FIXME: find a better way to do this! // FIXME: find a better way to do this!
wxMilliSleep(100); wxMilliSleep(100);
} }
// now remove the thread-specific thread target
wxLog::SetThreadActiveTarget(NULL);
// so that this goes to the main window again
wxLogMessage("GUI thread finished.");
return (ExitCode)0; return (ExitCode)0;
} }

View File

@@ -42,6 +42,7 @@
#include "wx/msgout.h" #include "wx/msgout.h"
#include "wx/textfile.h" #include "wx/textfile.h"
#include "wx/thread.h" #include "wx/thread.h"
#include "wx/private/threadinfo.h"
#include "wx/crt.h" #include "wx/crt.h"
#include "wx/vector.h" #include "wx/vector.h"
@@ -69,6 +70,8 @@ const char *wxLOG_COMPONENT = "";
#if wxUSE_THREADS #if wxUSE_THREADS
wxTLS_TYPE(wxThreadSpecificInfo) wxThreadInfoVar;
namespace namespace
{ {
@@ -259,29 +262,46 @@ wxLog::OnLog(wxLogLevel level,
#endif #endif
} }
wxLog *pLogger = GetActiveTarget(); wxLog *logger;
if ( !pLogger )
return;
#if wxUSE_THREADS #if wxUSE_THREADS
if ( !wxThread::IsMain() ) if ( !wxThread::IsMain() )
{ {
logger = wxThreadInfo.logger;
if ( !logger )
{
if ( ms_pLogger )
{
// buffer the messages until they can be shown from the main
// thread
wxCriticalSectionLocker lock(GetBackgroundLogCS()); wxCriticalSectionLocker lock(GetBackgroundLogCS());
gs_bufferedLogRecords.push_back(wxLogRecord(level, msg, info)); gs_bufferedLogRecords.push_back(wxLogRecord(level, msg, info));
// ensure that our Flush() will be called soon // ensure that our Flush() will be called soon
wxWakeUpIdle(); wxWakeUpIdle();
}
//else: we don't have any logger at all, there is no need to log
// anything
return; return;
} }
//else: we have a thread-specific logger, we can send messages to it
// directly
}
else
#endif // wxUSE_THREADS #endif // wxUSE_THREADS
{
logger = ms_pLogger;
if ( !logger )
return;
}
pLogger->OnLogInMainThread(level, msg, info); logger->CallDoLogNow(level, msg, info);
} }
void void
wxLog::OnLogInMainThread(wxLogLevel level, wxLog::CallDoLogNow(wxLogLevel level,
const wxString& msg, const wxString& msg,
const wxLogRecordInfo& info) const wxLogRecordInfo& info)
{ {
@@ -429,6 +449,19 @@ void wxLog::DoLog(wxLogLevel WXUNUSED(level), const wchar_t *wzString, time_t t)
wxLog *wxLog::GetActiveTarget() 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 ) { if ( ms_bAutoCreate && ms_pLogger == NULL ) {
// prevent infinite recursion if someone calls wxLogXXX() from // prevent infinite recursion if someone calls wxLogXXX() from
// wxApp::CreateLogTarget() // wxApp::CreateLogTarget()
@@ -465,6 +498,22 @@ wxLog *wxLog::SetActiveTarget(wxLog *pLogger)
return pOldLogger; 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() void wxLog::DontCreateOnDemand()
{ {
ms_bAutoCreate = false; ms_bAutoCreate = false;
@@ -582,12 +631,10 @@ void wxLog::TimeStamp(wxString *str)
#endif // wxUSE_DATETIME #endif // wxUSE_DATETIME
} }
void wxLog::Flush()
{
#if wxUSE_THREADS #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 // check if we have queued messages from other threads
wxLogRecords bufferedLogRecords; wxLogRecords bufferedLogRecords;
@@ -605,14 +652,36 @@ void wxLog::Flush()
it != bufferedLogRecords.end(); it != bufferedLogRecords.end();
++it ) ++it )
{ {
OnLogInMainThread(it->level, it->msg, it->info); CallDoLogNow(it->level, it->msg, it->info);
} }
} }
}
#endif // wxUSE_THREADS #endif // wxUSE_THREADS
void wxLog::Flush()
{
LogLastRepeatIfNeeded(); 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 // wxLogBuffer implementation
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------