Multithreaded exception handling

Hi,

I am experimenting with catching the exceptions that are thrown inside threads. The approach that I am trying involves:

  • Adding an exception handling callback, I chose to put it in G4UserWorkerInitialization::Catch(const std::exception& e) const.

  • Adding a try/catch block in G4MTRunManagerKernel::StartThread, ie

      G4MTRunManagerKernel::StartThread(...) {
          ...
          try {
              ...
          }
          catch (const std::exception& e) {
              masterRM->GetUserWorkerInitialization()->CatchException(e);
              ??? Terminate thread ???
          }
          ...
      }
    

Of course, I am not getting this to work, or otherwise I wouldn’t be here :smiley:

The main problem is that no matter what I do the program does not terminate, because some worker threads get stuck at a barrier waiting for the now dead thread to end. So the question is, what should the throwing thread do to clean after itself (which I guess is the same as whatever would solve the issue in Handling Ctrl+C/Sigint in multithreaded run).

I have tried several things: ending the catch block before “Step-6: Terminate worker thread”, calling ThisWorkerEndEventLoop, and I don’t know what else. Unfortunately nothing seems to work.

Some working code can be found here: https://gitlab.com/loximann/nain2/tree/thread_exceptions. A patch for Geant4 necessary to build the code is also provided (it is called catch.patch, I cannot provide more than two links as I am a new user…).

Any ideas? Any possibility that this feature would be added to Geant4, in this or other form?

Essentially, you catch the exception on the worker thread, assign it to a global exception pointer, and then rethrow it on the master thread.

To my knowledge, Geant4 does not throw std::exception anywhere (G4Exception just calls something like exit(EXIT_FAILURE) I believe) so that is probably why we don’t have the exception handling across threads. I am guessing your code is throwing an exception somewhere and that is why you are looking into this. It probably wouldn’t hurt to code this up and get it into the next release or patch but it might hurt because exception handling can have some performance implications w.r.t. maintaining stack unwinding.

In general, the handling would look like this:

static std::exception_ptr& G4GetGlobalException()
{
    static std::exception_ptr _instance = nullptr;
    return _instance;
}

// somewhere on the worker thread
void G4WorkerThread::SomeFunction(...) 
{
    try { ... }
    catch (const std::exception& e) 
    {
        G4GetGlobalException() = std::current_exception();
    }
}

// on the main thread
void G4MasterThread::SomeFunction()
{
    // ...
    if(G4GetGlobalException())
    {
        std::rethrow_exception(G4GetGlobalException());
    }
}

** Class names G4WorkerThread and G4MasterThread have no relevance to real G4 classes.

Thanks for the reply!

Would that work though? I think that I am doing pretty much the same thing. The problem is that the thread where the exception is thrown is terminated, and all other threads get stuck waiting for that thread to to signal it’s done.

The thing is, I have a custom exception handler that throws std::exception’s when G4Exception() is called.

Also, I guess the performance hit is not huge if the whole work done by the worker is inside one try/catch block?

@jrmadsen I am realizing I did not consider your solution properly: I need to change where the master thread looks for exceptions. At the moment, I was looking at them after the whole run had been finished (ie after calling BeamOn), so I guess I should move it elsewhere. The question is where. Any ideas?

Custom exception handling that throws exceptions when G4Exception is called? Are you implementing this with a signal handler and trying to turn a fatal G4Exception into a recoverable error?

There is no signal handling, but yes, I would like the program to fail gracefully, logging the exception information. This works fine when G4Exception() is called from the master thread, and I would like the same to happen when calling from a worker thread.