From 2d8e0096cd998ef0c3c282fec593d4ccd12e3eb4 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Wed, 9 Dec 2009 14:59:08 +0000 Subject: [PATCH] Better handling for asserts in non-main threads. Don't call wxTrap() when an assert in a non-main thread fails. As asserts are now always enabled by default, this is not a good idea. Instead just show the full details about the assert failure using wxMessageOutputDebug under the platforms without MT-safe message box function (i.e. everything but MSW currently). Add a possibility to test an assert happening in non-main thread to the except sample. git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@62842 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- samples/except/except.cpp | 67 ++++++++++++++++++++++++++---- src/common/appbase.cpp | 26 +++--------- src/common/appcmn.cpp | 70 ++++++++++++++++--------------- src/gtk/utilsgtk.cpp | 87 +++++++++++++++++++++------------------ 4 files changed, 146 insertions(+), 104 deletions(-) diff --git a/samples/except/except.cpp b/samples/except/except.cpp index f225369234..7ea7b7b159 100644 --- a/samples/except/except.cpp +++ b/samples/except/except.cpp @@ -44,6 +44,8 @@ #include "wx/utils.h" #include "wx/msgdlg.h" #include "wx/icon.h" + + #include "wx/thread.h" #endif // ---------------------------------------------------------------------------- @@ -128,8 +130,13 @@ protected: // catch exceptions which occur in MyFrame methods here virtual bool ProcessEvent(wxEvent& event); - // show how an assert failure message box looks like + // provoke assert in main or worker thread + // + // this is used to show how an assert failure message box looks like void OnShowAssert(wxCommandEvent& event); +#if wxUSE_THREADS + void OnShowAssertInThread(wxCommandEvent& event); +#endif // wxUSE_THREADS private: // any class wishing to process wxWidgets events must use this macro @@ -185,6 +192,9 @@ enum Except_HandleCrash, #endif // wxUSE_ON_FATAL_EXCEPTION Except_ShowAssert, +#if wxUSE_THREADS + Except_ShowAssertInThread, +#endif // wxUSE_THREADS Except_Dialog, Except_Quit = wxID_EXIT, @@ -211,6 +221,9 @@ BEGIN_EVENT_TABLE(MyFrame, wxFrame) EVT_MENU(Except_HandleCrash, MyFrame::OnHandleCrash) #endif // wxUSE_ON_FATAL_EXCEPTION EVT_MENU(Except_ShowAssert, MyFrame::OnShowAssert) +#if wxUSE_THREADS + EVT_MENU(Except_ShowAssertInThread, MyFrame::OnShowAssertInThread) +#endif // wxUSE_THREADS END_EVENT_TABLE() BEGIN_EVENT_TABLE(MyDialog, wxDialog) @@ -301,14 +314,17 @@ void MyApp::OnAssertFailure(const wxChar *file, const wxChar *cond, const wxChar *msg) { - if ( wxMessageBox - ( - wxString::Format("An assert failed in %s().", func) + - "\n" - "Do you want to call the default assert handler?", - "wxExcept Sample", - wxYES_NO | wxICON_QUESTION - ) == wxYES ) + // take care to not show the message box from a worker thread, this doesn't + // work as it doesn't have any event loop + if ( !wxIsMainThread() || + wxMessageBox + ( + wxString::Format("An assert failed in %s().", func) + + "\n" + "Do you want to call the default assert handler?", + "wxExcept Sample", + wxYES_NO | wxICON_QUESTION + ) == wxYES ) { wxApp::OnAssertFailure(file, line, func, cond, msg); } @@ -343,6 +359,10 @@ MyFrame::MyFrame() menuFile->AppendSeparator(); #endif // wxUSE_ON_FATAL_EXCEPTION menuFile->Append(Except_ShowAssert, wxT("Provoke &assert failure\tCtrl-A")); +#if wxUSE_THREADS + menuFile->Append(Except_ShowAssertInThread, + wxT("Assert failure in &thread\tShift-Ctrl-A")); +#endif // wxUSE_THREADS menuFile->AppendSeparator(); menuFile->Append(Except_Quit, wxT("E&xit\tCtrl-Q"), wxT("Quit this program")); @@ -443,6 +463,35 @@ void MyFrame::OnShowAssert(wxCommandEvent& WXUNUSED(event)) arr[0]; } +#if wxUSE_THREADS + +void MyFrame::OnShowAssertInThread(wxCommandEvent& WXUNUSED(event)) +{ + class AssertThread : public wxThread + { + public: + AssertThread() + : wxThread(wxTHREAD_JOINABLE) + { + } + + protected: + virtual void *Entry() + { + wxFAIL_MSG("Test assert in another thread."); + + return 0; + } + }; + + AssertThread thread; + thread.Create(); + thread.Run(); + thread.Wait(); +} + +#endif // wxUSE_THREADS + void MyFrame::OnAbout(wxCommandEvent& WXUNUSED(event)) { wxString msg; diff --git a/src/common/appbase.cpp b/src/common/appbase.cpp index f2bed9aca8..fbc1cf78ba 100644 --- a/src/common/appbase.cpp +++ b/src/common/appbase.cpp @@ -1186,11 +1186,9 @@ bool DoShowAssertDialog(const wxString& msg) //case IDNO: nothing to do } #else // !__WXMSW__ - wxFprintf(stderr, wxT("%s\n"), msg.c_str()); - fflush(stderr); + wxMessageOutputDebug().Output(msg); - // TODO: ask the user to enter "Y" or "N" on the console? - wxTrap(); + // TODO: ask the user whether to trap on the console? #endif // __WXMSW__/!__WXMSW__ // continue with the asserts @@ -1236,27 +1234,15 @@ void ShowAssertDialog(const wxString& file, // since dialogs cannot be displayed if ( !wxThread::IsMain() ) { - msg += wxT(" [in child thread]"); - -#if defined(__WXMSW__) && !defined(__WXMICROWIN__) - msg << wxT("\r\n"); - OutputDebugString(msg.wx_str()); -#else - // send to stderr - wxFprintf(stderr, wxT("%s\n"), msg.c_str()); - fflush(stderr); -#endif - // He-e-e-e-elp!! we're asserting in a child thread - wxTrap(); + msg += wxString::Format(" [in thread %lx]", wxThread::GetCurrentId()); } - else #endif // wxUSE_THREADS + // log the assert in any case + wxMessageOutputDebug().Output(msg); + if ( !s_bNoAsserts ) { - // send it to the normal log destination - wxLogDebug(wxT("%s"), msg.c_str()); - if ( traits ) { // delegate showing assert dialog (if possible) to that class diff --git a/src/common/appcmn.cpp b/src/common/appcmn.cpp index ea3a17c7c9..ca3c6fdc75 100644 --- a/src/common/appcmn.cpp +++ b/src/common/appcmn.cpp @@ -447,6 +447,7 @@ wxRendererNative *wxGUIAppTraitsBase::CreateRenderer() bool wxGUIAppTraitsBase::ShowAssertDialog(const wxString& msg) { +#if wxDEBUG_LEVEL // under MSW we prefer to use the base class version using ::MessageBox() // even if wxMessageBox() is available because it has less chances to // double fault our app than our wxMessageBox() @@ -455,50 +456,51 @@ bool wxGUIAppTraitsBase::ShowAssertDialog(const wxString& msg) // // and finally we can't use wxMessageBox() if it wasn't compiled in, of // course -#if defined(__WXMSW__) || defined(__WXDFB__) || !wxUSE_MSGDLG - return wxAppTraitsBase::ShowAssertDialog(msg); -#else // wxUSE_MSGDLG -#if wxDEBUG_LEVEL - wxString msgDlg = msg; +#if !defined(__WXMSW__) && !defined(__WXDFB__) && wxUSE_MSGDLG + + // we can't (safely) show the GUI dialog from another thread, only do it + // for the asserts in the main thread + if ( wxIsMainThread() ) + { + wxString msgDlg = msg; #if wxUSE_STACKWALKER - // on Unix stack frame generation may take some time, depending on the - // size of the executable mainly... warn the user that we are working - wxFprintf(stderr, wxT("[Debug] Generating a stack trace... please wait")); - fflush(stderr); + // on Unix stack frame generation may take some time, depending on the + // size of the executable mainly... warn the user that we are working + wxFprintf(stderr, wxT("[Debug] Generating a stack trace... please wait")); + fflush(stderr); - const wxString stackTrace = GetAssertStackTrace(); - if ( !stackTrace.empty() ) - msgDlg << wxT("\n\nCall stack:\n") << stackTrace; + const wxString stackTrace = GetAssertStackTrace(); + if ( !stackTrace.empty() ) + msgDlg << wxT("\n\nCall stack:\n") << stackTrace; #endif // wxUSE_STACKWALKER - // this message is intentionally not translated -- it is for - // developpers only - msgDlg += wxT("\nDo you want to stop the program?\n") - wxT("You can also choose [Cancel] to suppress ") - wxT("further warnings."); + // this message is intentionally not translated -- it is for + // developpers only + msgDlg += wxT("\nDo you want to stop the program?\n") + wxT("You can also choose [Cancel] to suppress ") + wxT("further warnings."); - switch ( wxMessageBox(msgDlg, wxT("wxWidgets Debug Alert"), - wxYES_NO | wxCANCEL | wxICON_STOP ) ) - { - case wxYES: - wxTrap(); - break; + switch ( wxMessageBox(msgDlg, wxT("wxWidgets Debug Alert"), + wxYES_NO | wxCANCEL | wxICON_STOP ) ) + { + case wxYES: + wxTrap(); + break; - case wxCANCEL: - // no more asserts - return true; + case wxCANCEL: + // no more asserts + return true; - //case wxNO: nothing to do + //case wxNO: nothing to do + } + + return false; } -#else // !wxDEBUG_LEVEL - // this function always exists (for ABI compatibility) but is never called - // if debug level is 0 and so can simply do nothing then - wxUnusedVar(msg); -#endif // wxDEBUG_LEVEL/!wxDEBUG_LEVEL +#endif // wxUSE_MSGDLG +#endif // wxDEBUG_LEVEL - return false; -#endif // !wxUSE_MSGDLG/wxUSE_MSGDLG + return wxAppTraitsBase::ShowAssertDialog(msg); } bool wxGUIAppTraitsBase::HasStderr() diff --git a/src/gtk/utilsgtk.cpp b/src/gtk/utilsgtk.cpp index bfcbbc8ab2..a8d0888c7c 100644 --- a/src/gtk/utilsgtk.cpp +++ b/src/gtk/utilsgtk.cpp @@ -354,55 +354,60 @@ extern "C" bool wxGUIAppTraits::ShowAssertDialog(const wxString& msg) { #if wxDEBUG_LEVEL - // under GTK2 we prefer to use a dialog widget written using directly in - // GTK+ as use a dialog written using wxWidgets would need the wxWidgets - // idle processing to work correctly which might not be the case when - // assert happens - GtkWidget *dialog = gtk_assert_dialog_new(); - gtk_assert_dialog_set_message(GTK_ASSERT_DIALOG(dialog), msg.mb_str()); + // we can't show the dialog from another thread + if ( wxIsMainThread() ) + { + // under GTK2 we prefer to use a dialog widget written using directly + // in GTK+ as use a dialog written using wxWidgets would need the + // wxWidgets idle processing to work correctly which might not be the + // case when assert happens + GtkWidget *dialog = gtk_assert_dialog_new(); + gtk_assert_dialog_set_message(GTK_ASSERT_DIALOG(dialog), msg.mb_str()); #if wxUSE_STACKWALKER - // don't show more than maxLines or we could get a dialog too tall to be - // shown on screen: 20 should be ok everywhere as even with 15 pixel high - // characters it is still only 300 pixels... - static const int maxLines = 20; + // don't show more than maxLines or we could get a dialog too tall to + // be shown on screen: 20 should be ok everywhere as even with 15 pixel + // high characters it is still only 300 pixels... + static const int maxLines = 20; - // save current stack frame... - StackDump dump(GTK_ASSERT_DIALOG(dialog)); - dump.SaveStack(maxLines); + // save current stack frame... + StackDump dump(GTK_ASSERT_DIALOG(dialog)); + dump.SaveStack(maxLines); - // ...but process it only if the user needs it - gtk_assert_dialog_set_backtrace_callback(GTK_ASSERT_DIALOG(dialog), - (GtkAssertDialogStackFrameCallback)get_stackframe_callback, - &dump); -#endif // wxUSE_STACKWALKER + // ...but process it only if the user needs it + gtk_assert_dialog_set_backtrace_callback + ( + GTK_ASSERT_DIALOG(dialog), + (GtkAssertDialogStackFrameCallback)get_stackframe_callback, + &dump + ); +#endif // wxUSE_STACKWALKER - gint result = gtk_dialog_run(GTK_DIALOG (dialog)); - bool returnCode = false; - switch (result) - { - case GTK_ASSERT_DIALOG_STOP: - wxTrap(); - break; - case GTK_ASSERT_DIALOG_CONTINUE: - // nothing to do - break; - case GTK_ASSERT_DIALOG_CONTINUE_SUPPRESSING: - // no more asserts - returnCode = true; - break; + gint result = gtk_dialog_run(GTK_DIALOG (dialog)); + bool returnCode = false; + switch (result) + { + case GTK_ASSERT_DIALOG_STOP: + wxTrap(); + break; + case GTK_ASSERT_DIALOG_CONTINUE: + // nothing to do + break; + case GTK_ASSERT_DIALOG_CONTINUE_SUPPRESSING: + // no more asserts + returnCode = true; + break; - default: - wxFAIL_MSG( wxT("unexpected return code from GtkAssertDialog") ); + default: + wxFAIL_MSG( wxT("unexpected return code from GtkAssertDialog") ); + } + + gtk_widget_destroy(dialog); + return returnCode; } +#endif // wxDEBUG_LEVEL - gtk_widget_destroy(dialog); - return returnCode; -#else // !wxDEBUG_LEVEL - // this function is never called in this case - wxUnusedVar(msg); - return false; -#endif // wxDEBUG_LEVEL/!wxDEBUG_LEVEL + return wxAppTraitsBase::ShowAssertDialog(msg); } wxString wxGUIAppTraits::GetDesktopEnvironment() const