Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. Exceptions thrown in slots are crashing the application only on macOS
Forum Updated to NodeBB v4.3 + New Features

Exceptions thrown in slots are crashing the application only on macOS

Scheduled Pinned Locked Moved Solved General and Desktop
3 Posts 2 Posters 552 Views 3 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • rkhaotixR Offline
    rkhaotixR Offline
    rkhaotix
    wrote on last edited by
    #1

    Hi everyone!

    I'm facing a problem that is making me pull my hair! Some days ago, my desktop application stopped working, crashing constantly on macOS after a recent update of the OS as well as Qt itself. On Linux and Windows, the software works normally.

    But before I start to detail, let me give some background. For years, I've been working with exceptions in my application and never had problems throwing/catching them in event handlers or slots. I have a custom class derived from QApplication which overloads the notify() method to treat exceptions:

    bool PgModelerApp::notify(QObject *receiver, QEvent *event)
    {
    	try
    	{
    		return QApplication::notify(receiver,event);
    	}
    	catch(Exception &e)
    	{
    		Messagebox::error(e);
    		return false;
    	}
    	catch(...)
    	{
    		Messagebox::error(tr("Unknown exception caught!"));
    		return false;
    	}
    }
    

    This worked like a charm for years, actually, since the start of the project 17 years ago! I discovered recently, while porting the codebase to Qt 6, that Qt has a problem with exceptions in slots, this problem never happened to me.

    Now, after the mentioned updates, exceptions thrown from slots cause the application to crash. Exceptions thrown from event handlers are captured by PgModelerApp::notify as expected without crashing. When I look into the application's output after a crash I see the following message:

    libc++abi: terminating due to uncaught exception of type Exception

    Additionally, the crash handler of my application shows the following call stack (I included only the point where the exception is thrown [19] and where the application quits [29] with signal 6 (SIGABRT):

    [29] 0   pgmodeler  0x0000000100433ce8 _Z17startCrashHandleri + 60
    [28] 1   libsystem_platform.dylib 0x000000018560fa24 _sigtramp + 56
    [27] 2   libsystem_pthread.dylib  0x00000001855e0cc0 pthread_kill + 288
    [26] 3   libsystem_c.dylib  0x00000001854f0a40 abort + 180
    [25] 4   libc++abi.dylib  0x00000001855986d8 _ZN10__cxxabiv130__aligned_malloc_with_fallbackEm + 0
    [24] 5   libc++abi.dylib                     0x00000001855887c8 _ZL28demangling_terminate_handlerv + 348
    [23] 6   libobjc.A.dylib 0x00000001852338a4 _ZL15_objc_terminatev + 160
    [22] 7   libc++abi.dylib 0x0000000185597a9c _ZSt11__terminatePFvvE + 16
    [21] 8   libc++abi.dylib 0x000000018559aa48 __cxa_get_exception_ptr + 0
    [20] 9   libc++abi.dylib 0x000000018559a9f4 _ZN10__cxxabiv1L22exception_cleanup_funcE19_Unwind_Reason_CodeP17_Unwind_Exception + 0
    [19] 10  libgui.1.0.0.dylib 0x0000000101a42d3c _ZN12ColumnWidget18applyConfigurationEv + 3044
    

    For me the problem is quite clear, the exceptions are not being captured in PgModelerApp::notify anymore, and I don't have a slight idea of why! :(

    Having said that, I'm starting to construct a workaround while trying to discover what was going on just to keep to software working. After studying a lot, I came up with a not-so-elegant solution which consists of a couple of macros that expand to signal/slot connections using lambdas wrapped in try/catch statements.

    For example, given the macro call:

    __connect_l(sender, &SenderClass::signal, context, [this](){...});
    

    The result given by the preprocessor will be:

    { 
       auto __try_catch_lmbd = [&]() { 
         try
         {
    	auto __slot_lmbd = [this](){...} ;
    	__slot_lmbd();
          }
          catch(Exception &e)
          {
             Messagebox::error(e);
          }
       };
    
      connect(sender, signal, context, __try_catch_lmbd); 
    }
    

    That approach "solves" the crash problem but it's not even close to being the ideal solution for the problem. Honestly, I am not happy with that, but it's what I could elaborate on. I know that the best solution would stop using exceptions but this is out of the question since I don't have enough time and energy to refactor a 17-year-old codebase that uses exceptions all over the place. Also, It would lead to the introduction of more bugs which is not desirable.

    So, what I'm looking for is:

    1. Is there a way in Qt that can help me to handle exceptions in slots?
    2. If (1) is not possible, is there a more elegant way to create a macro, or even better, a function/method that behaves like the macro I've exemplified above?

    Thank you in advance!
    Cheers from Brazil.

    P.S.: I tried to create my own connect method mimicking QObject::connect that includes a try/catch block but I failed because I have a lot of difficulty understanding complex template declarations! :(

    1 Reply Last reply
    0
    • Chris KawaC Offline
      Chris KawaC Offline
      Chris Kawa
      Lifetime Qt Champion
      wrote on last edited by
      #2

      Signals and slots are fundamentally incompatible with exceptions.
      Not only is throwing from a slot undefined behavior, but it also makes no sense conceptually. Setting aside that you're completely messing with the internal mechanisms of signal dispatching, imagine a signal connected to 10 slots. Say two slots execute normally and third one throws. What should happen? Should the signal dispatch stop and let the exception through? What about the rest of the slots? How and when would they execute if you bubbled the exception to the very top of the app? Should Qt just skip them? What if they're important? What if some of them used a blocking connection on another thread? How should that be handled? Should Qt catch your exception and rethrow it up after it dispatches all the slots? What if more than one slot throws? Which thread should handle the exceptions? Signal or one of the slots?

      That your app "worked" for 17 years and suddenly stopped on an update is pretty much what undefined behavior means. It was a ticking bomb that you ignored and it finally went off.
      Btw. Windows and Linux versions are not "working normally". They are just still ticking.

      Is there a way in Qt that can help me to handle exceptions in slots?

      No. Qt explicitly documents exceptions thrown out of slots as undefined behavior. You're alone behind enemy lines.

      For me the problem is quite clear, the exceptions are not being captured in PgModelerApp::notify anymore

      That's not the real problem. The problem I see here is that you're not really handling your exceptions at all. Showing a message box is hardly handling it. What is user supposed to do with a message like "Some super internal object threw an exception of nerdy type at address 0x12345"? How can you reason about the state of the app after something like that? You let exceptions bubble all the way up to the top of your app. What is a QApplication object supposed to do with exception it knows nothing about, thrown 10 levels down in some random function? If you're throwing exceptions you should catch them as soon as possible, at a level that still has some context for it and handle it accordingly (showing a message is very rarely "handling it").

      I tried to create my own connect method mimicking QObject::connect that includes a try/catch block

      That's not going to work. It's not connect that throws. The code that throws is invoked in the signal call when it's a direct connection or on the thread the slot is in when it's a queued connection. As I mentioned above there's no sane way to handle that. Just don't let exceptions escape slots.

      catch(Exception &e)
      {
         Messagebox::error(e);
      }
      

      If Messagebox is based on Qt UI (e.g. widgets) this shouldn't be called from non-UI thread, so you can't call it as a generic exception handler, since you don't know what thread might throw.

      Honestly, if you really can't do the right thing then your wrapper is the least worst thing to do, just don't show a message box in it.

      rkhaotixR 1 Reply Last reply
      4
      • Chris KawaC Chris Kawa

        Signals and slots are fundamentally incompatible with exceptions.
        Not only is throwing from a slot undefined behavior, but it also makes no sense conceptually. Setting aside that you're completely messing with the internal mechanisms of signal dispatching, imagine a signal connected to 10 slots. Say two slots execute normally and third one throws. What should happen? Should the signal dispatch stop and let the exception through? What about the rest of the slots? How and when would they execute if you bubbled the exception to the very top of the app? Should Qt just skip them? What if they're important? What if some of them used a blocking connection on another thread? How should that be handled? Should Qt catch your exception and rethrow it up after it dispatches all the slots? What if more than one slot throws? Which thread should handle the exceptions? Signal or one of the slots?

        That your app "worked" for 17 years and suddenly stopped on an update is pretty much what undefined behavior means. It was a ticking bomb that you ignored and it finally went off.
        Btw. Windows and Linux versions are not "working normally". They are just still ticking.

        Is there a way in Qt that can help me to handle exceptions in slots?

        No. Qt explicitly documents exceptions thrown out of slots as undefined behavior. You're alone behind enemy lines.

        For me the problem is quite clear, the exceptions are not being captured in PgModelerApp::notify anymore

        That's not the real problem. The problem I see here is that you're not really handling your exceptions at all. Showing a message box is hardly handling it. What is user supposed to do with a message like "Some super internal object threw an exception of nerdy type at address 0x12345"? How can you reason about the state of the app after something like that? You let exceptions bubble all the way up to the top of your app. What is a QApplication object supposed to do with exception it knows nothing about, thrown 10 levels down in some random function? If you're throwing exceptions you should catch them as soon as possible, at a level that still has some context for it and handle it accordingly (showing a message is very rarely "handling it").

        I tried to create my own connect method mimicking QObject::connect that includes a try/catch block

        That's not going to work. It's not connect that throws. The code that throws is invoked in the signal call when it's a direct connection or on the thread the slot is in when it's a queued connection. As I mentioned above there's no sane way to handle that. Just don't let exceptions escape slots.

        catch(Exception &e)
        {
           Messagebox::error(e);
        }
        

        If Messagebox is based on Qt UI (e.g. widgets) this shouldn't be called from non-UI thread, so you can't call it as a generic exception handler, since you don't know what thread might throw.

        Honestly, if you really can't do the right thing then your wrapper is the least worst thing to do, just don't show a message box in it.

        rkhaotixR Offline
        rkhaotixR Offline
        rkhaotix
        wrote on last edited by rkhaotix
        #3

        @Chris-Kawa Hi Chris! Thanks for your response.

        Just to be clear, the message box display is the end of the route of an exception. There I have a stack trace which helps me a lot to debug and fix problems. Also, the wrapper I'm doing is just a workaround while I track down all the slots that are not capturing the exception inside them and letting the exceptions go out.

        That's not going to work. It's not connect that throws. The code that throws is invoked in the signal call when it's a direct connection or on the thread the slot is in when it's a queued connection.

        I understand that now. Thanks.

        Just don't let exceptions escape slots.

        There are tons of slots that already do that. But, unfortunately, the ones that are letting exceptions escape are in great amounts too. So, I have a lot of refactoring to do. :)

        If Messagebox is based on Qt UI (e.g. widgets) this shouldn't be called from non-UI thread, so you can't call it as a generic exception handler, since you don't know what thread might throw.

        Yes, it is. The problem I mentioned is only happening on UI threads.

        Anyway, thanks again for your time explaining that. It helped a lot to understand what's wrong, and how I can fix that and avoid falling on that pitfall again.

        Have a nice day!

        1 Reply Last reply
        0
        • rkhaotixR rkhaotix has marked this topic as solved on

        • Login

        • Login or register to search.
        • First post
          Last post
        0
        • Categories
        • Recent
        • Tags
        • Popular
        • Users
        • Groups
        • Search
        • Get Qt Extensions
        • Unsolved