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:
@@ -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):
|
||||||
|
|
||||||
|
@@ -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,11 +578,18 @@ 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);
|
||||||
|
|
||||||
|
|
||||||
// static variables
|
// static variables
|
||||||
|
43
include/wx/private/threadinfo.h
Normal file
43
include/wx/private/threadinfo.h
Normal 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_
|
||||||
|
|
@@ -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 %
|
||||||
|
@@ -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;
|
||||||
}
|
}
|
||||||
|
@@ -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,31 +262,48 @@ 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() )
|
||||||
{
|
{
|
||||||
wxCriticalSectionLocker lock(GetBackgroundLogCS());
|
logger = wxThreadInfo.logger;
|
||||||
|
if ( !logger )
|
||||||
|
{
|
||||||
|
if ( ms_pLogger )
|
||||||
|
{
|
||||||
|
// buffer the messages until they can be shown from the main
|
||||||
|
// thread
|
||||||
|
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)
|
||||||
{
|
{
|
||||||
if ( GetRepetitionCounting() )
|
if ( GetRepetitionCounting() )
|
||||||
{
|
{
|
||||||
@@ -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
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
Reference in New Issue
Block a user