]> git.saurik.com Git - wxWidgets.git/commitdiff
Add support for thread-specific log targets.
authorVadim Zeitlin <vadim@wxwidgets.org>
Mon, 13 Jul 2009 11:09:26 +0000 (11:09 +0000)
committerVadim Zeitlin <vadim@wxwidgets.org>
Mon, 13 Jul 2009 11:09:26 +0000 (11:09 +0000)
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
include/wx/log.h
include/wx/private/threadinfo.h [new file with mode: 0644]
interface/wx/log.h
samples/thread/thread.cpp
src/common/log.cpp

index 3a45abc5f4bda0e3371845830d2fbc0f30a2655e..75dbb047dfdc0964fba59f06d51c354c24194175 100644 (file)
@@ -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):
 
index b7b28a6fc5b1c289067882ea054702f3c74956f9..522c789f1cd240a7a078e8e26af5d272db5a1333 100644 (file)
@@ -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 (file)
index 0000000..87eee51
--- /dev/null
@@ -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_
+
index 428f9fb660162005412100e552256ca441db5814..d23af95b516425bdafa144cbb320d16bf4153d89 100644 (file)
@@ -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 %
index 74afcec5a65b1c1b35ad6b66f530b4ca8882b1dd..06232403a4af2dad6218081278a86bbd55bc3a7d 100644 (file)
@@ -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; i<GUITHREAD_NUM_UPDATES && !TestDestroy(); i++)
     {
         // inform the GUI toolkit that we're going to use GUI functions
@@ -1029,10 +1037,22 @@ wxThread::ExitCode MyGUIThread::Entry()
         event.SetInt(i+1);
         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
         // FIXME: find a better way to do this!
         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;
 }
index 99a424bfd3ba289c1da7f7a74f53d63cf88f68cf..293fcd1dc8a10a75d34121fb79f260af8a8d3142 100644 (file)
@@ -42,6 +42,7 @@
 #include "wx/msgout.h"
 #include "wx/textfile.h"
 #include "wx/thread.h"
+#include "wx/private/threadinfo.h"
 #include "wx/crt.h"
 #include "wx/vector.h"
 
@@ -69,6 +70,8 @@ const char *wxLOG_COMPONENT = "";
 
 #if wxUSE_THREADS
 
+wxTLS_TYPE(wxThreadSpecificInfo) wxThreadInfoVar;
+
 namespace
 {
 
@@ -259,31 +262,48 @@ wxLog::OnLog(wxLogLevel level,
 #endif
     }
 
-    wxLog *pLogger = GetActiveTarget();
-    if ( !pLogger )
-        return;
+    wxLog *logger;
 
 #if wxUSE_THREADS
     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
-        wxWakeUpIdle();
+                // ensure that our Flush() will be called soon
+                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
+    {
+        logger = ms_pLogger;
+        if ( !logger )
+            return;
+    }
 
-    pLogger->OnLogInMainThread(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
 // ----------------------------------------------------------------------------