Forcing exception in another thread

Started by
14 comments, last by Prune 13 years, 3 months ago
The MSVC example of using exception_ptr/rethrow_exception()/current_exception() shows only how to send exception data to another thread, but the user has to explicitly call rethrow_exception(), and so the exception is not rethrown automatically. I need a method to overcome this, so that as soon as I save an exception in a thread, the exception would be immediately rethrown in the handler thread. I assume this would involve using signals and signal handlers, but I'm not well familiar with these on neither Windows nor Linux. I'd like to try in Windows first. Any pointers as to the best way to do this? From what I've read about signal handlers, I'm not sure how to cause the exception to have the effect as if it were raised in a particular scope of the thread that does the handling...maybe longjmp?
"But who prays for Satan? Who, in eighteen centuries, has had the common humanity to pray for the one sinner that needed it most?" --Mark Twain

~~~~~~~~~~~~~~~Looking for a high-performance, easy to use, and lightweight math library? http://www.cmldev.net/ (note: I'm not associated with that project; just a user)
Advertisement
Anyone?

[Edit] I have found this solution for Windows: http://www.codeproject.com/KB/exception/ExcInject.aspx but I don't know how to accomplish this under Linux as well...

[Edited by - Prune on December 10, 2010 2:40:50 PM]
"But who prays for Satan? Who, in eighteen centuries, has had the common humanity to pray for the one sinner that needed it most?" --Mark Twain

~~~~~~~~~~~~~~~Looking for a high-performance, easy to use, and lightweight math library? http://www.cmldev.net/ (note: I'm not associated with that project; just a user)
I am not sure if this is possible at all in any other way but to write your own mini-debugger (using the debug API, you will get an event for every unhandled exception). Other than this, exceptions normally stay in the same thread, unless you do something explicit and special.

Why would you want to do this, anyway? It usually does not make much sense to catch exceptions in a different thread, unless the handler simply terminates the process. The actual purpose of exceptions is to handle some unexpected failure in a graceful way, i.e. backing off as much as needed so the program can still continue if possible at all.
I don't see how a handler thread knows enough about what is going on in some random other thread to be able of doing this.
Quote:Original post by samoth
I am not sure if this is possible at all in any other way but to write your own mini-debugger (using the debug API, you will get an event for every unhandled exception). Other than this, exceptions normally stay in the same thread, unless you do something explicit and special.

Well, given the above link, it's possible in Windows at least. That would make me guess it should somehow be possible in Linux as well.

Quote:Why would you want to do this, anyway? It usually does not make much sense to catch exceptions in a different thread, unless the handler simply terminates the process. The actual purpose of exceptions is to handle some unexpected failure in a graceful way, i.e. backing off as much as needed so the program can still continue if possible at all.
I don't see how a handler thread knows enough about what is going on in some random other thread to be able of doing this.

The exception is thrown in the current thread so "backing off" is already accomplished. But the other thread in my case is able to do further recovery, by loading some DLLs and doing a bunch of other stuff including restarting certain threads. It's hard to explain because the structure of the system I'm working on is nontrivial.
"But who prays for Satan? Who, in eighteen centuries, has had the common humanity to pray for the one sinner that needed it most?" --Mark Twain

~~~~~~~~~~~~~~~Looking for a high-performance, easy to use, and lightweight math library? http://www.cmldev.net/ (note: I'm not associated with that project; just a user)
Quote:Original post by Prune
The MSVC example of using exception_ptr/rethrow_exception()/current_exception() shows only how to send exception data to another thread, but the user has to explicitly call rethrow_exception(), and so the exception is not rethrown automatically. I need a method to overcome this, so that as soon as I save an exception in a thread, the exception would be immediately rethrown in the handler thread.


Bad idea. What if the other thread is doing this:

mutex.Lock();do_something();mutex.Unlock()


(Of course, the user probably should have used RAII for the lock, but in this case they got lazy for whatever reason).

If your exception is "forced" in to this thread before the call to mutex.Unlock() but after mutex.Lock(), your program is now completely screwed.

I chose a mutex because it's a particularly devastating example, but there are countless other kinds of resource that could be leaked in a similar way with various terrible consequences.

The boost threads library has an interruption-points feature which allows you to do this kind of thing, but the exception is only checked at certain "safe" points. This is probably the closest thing to what you want (though I'd suggest a redesign to avoid the issue altogether might be a better course of action).
Exceptions are stack unwinding mechanism. Stack is unwound either until it reaches the top, or until exception is handled.

Each thread has its own stack.

This makes threads and exceptions mutually exclusive. At least conceptually.


In practice it is sometimes beneficial to signal exceptions across threads. Perhaps to a logger or some coordinating mechanism. But at that point the original exception no longer serves its purpose. It is merely a log or a message, not an exception - the handling thread did not have an exception and cannot unwind a stack.

And if exception records a stack trace, the handling thread has no use for it - that stack no longer exists.


There are usually two separate issues here:
- Detecting abnormal condition in one of worker threads, possibly fatal, with the need to restart them.
- Having a centralized logging or notification system.

The solution to these is same. The exception must first be serialized in some way, maybe just a .what() will be enough. This message is then passed to handler thread using usual inter-thread communication. Handler thread does not deal with exceptions but simple user-defined objects.
the_edd, your example applies just as much to the case where the exception is thrown in the same thread by do_something() :D

In fact, I use RAII for pretty much everything.

Checkpoints is a good idea, but I really need both, because a few things need to be handled immediately.

Antheus, the handler thread needs happens to be my main thread which does various other things and needs to be interrupted; it would be very difficult to refactor so I have a separate handler thread that simply does WaitForMultipleObjects() (or select()) waiting for error events. Things would work out much better if I can have an exception generated because then RAII would take care of a lot of things, including some of the recovery mechanisms. This way, the same exception both unwinds the stack and calls destructor/cleanup in the originating thread, and takes care of the serialization issue you mentioned in the main thread.
"But who prays for Satan? Who, in eighteen centuries, has had the common humanity to pray for the one sinner that needed it most?" --Mark Twain

~~~~~~~~~~~~~~~Looking for a high-performance, easy to use, and lightweight math library? http://www.cmldev.net/ (note: I'm not associated with that project; just a user)
Quote:Original post by Prune
the_edd, your example applies just as much to the case where the exception is thrown in the same thread by do_something() :D


Not quite. If I know that do_something() does not throw an exception, that code should be perfectly safe.

If some other thread injects an exception without my say-so, it's not safe.

In fact even if RAII was used in my example (some kind of scoped-locking mechanism), there's still no guarantee of correctness. If the exception is injected just after the constructor of the scoped lock calls mutex.Lock() but before the constructor returns, same problem (because unwinding triggered in a constructor does not call the destructor).

EDIT: and you could take it a step further; what if the exception is injected somewhere in the middle of the call to mutex.Lock()?
Quote:Original post by Prune

the handler thread needs happens to be my main thread which does various other things and needs to be interrupted;
Thread interruption has been effectively abandoned. It's simply too problematic, even if well designed.

The boost's interruption is effectively just that - periodic checking for external events.

Quote:Things would work out much better if I can have an exception generated because then RAII would take care of a lot of things, including some of the recovery mechanisms.
But this doesn't make sense. If thread A throws an exception, B is completely independent. There is nothing to unwind in B since exception didn't happen there.

B may choose to throw a new exception to clean up its own stack upon learning that A has shutdown. In code, this looks something like this:
void thread_func() {  try {    // do the real work  } catch (std::exception & e) {    // unhandled exception    worker_exceptions.push.back(this.id(), e, ...);  }  // exit this thread normally}void main() {  while (running) {    // do something    if (!worker_exceptions.empty()) {      throw some_exception(foo, bar, baz);    }    // keep running  }}


No matter how it's implemented, this introduces asynchronous messaging. It also needs to handle all the associated cases, such as main not responding.

If worker thread attempted to interrupt main thread immediately - what would happen if during that time another thread interrupted main thread while it's cleaning up and exiting.

Quote:need to be handled immediately
Threads cannot do that. Unless running on a hard real-time OS, there is no guarantee main thread will ever wake up - it can remain suspended/unscheduled for arbitrary amount of time.

Quote:signal handlers
This is how interrupts work. But due to many issues they are also notoriously difficult to program, especially in user space. Making a tiny mistake inside a handler can bring down the system. Today's userspaces are highly isolated for this very reason.

The method presented above avoids the design issues present in interrupts while retaining the desired functionality. Anything beyond, such as any attempts to directly interact with code flow is simply not adequately supported by C or C++, especially in the automagic parts (destructors, RAII), which means results are unpredictable.
That code over at CodeProject is scary, if you ask me. First it uses SuspendThread, which is nasty stuff that should really never be used if you can help it at all (causes deadlocks in combination with wait functions), and then it tampers with the other thread's context. Which, meh... sure, will probably work, but... bleh. It's nasty.

If you do need to signal an exception, you can always just post a thread message. This is not the fastest thing in the world, but seeing how exceptions occur exceptionally, it will be fast enough. No tampering with half-documented internals that might change next week if you're unlucky or might not run on a different system.

But again, for most exceptions, handling them in another thread does not make an awful lot of sense. Logging or killing the process can be done easier and faster, without exceptions.
And pretty much everything else requires "knowledge" (this is what Antheus referred to with stack unwinding) that is not available to the other thread. Plus, the stack unwinding will be done anyway.
And lastly, in the (admittedly rare) cases where an exception handler can actually do something useful (something different than killing the process) only make sense in the same thread. For example, if you catch bad_alloc, you might want to try to free some memory that you can purge and try allocating again. The program could then (in the lucky case) continue as before, and nobody would ever know.
This wouldn't make sense when done in another thread.

This topic is closed to new replies.

Advertisement