开发者

Detect that exception is not caught by user without rethrowing

I have a certain exception class (used as a thread cancellation exception, where I make cancellation points throw it).

I want to be able to make it call abort() if the user catches it but doesn't rethrow it. In order to prevent the user from trying to cancel a cancellation or inadvertently use catch (...).

I tried calling abort() in the destructor unless a private flag in the exception class is set. My thread's run function is a friend of this exception class and modifies the internal flag. The problem is that in Visual C++ the exception is destroyed twice (seemingly because it is copied when thrown).

I thought to use reference counting so that when the copy occurs (which appears to be during execution of the throw statement), the counter is incremented and the destructor won't throw abort while there are other copies.

Unfortunately, the copy constructor does not actually get called--I tried by trying to cout from it. Same for assignment operators, and move constructor and assignment--they are not called. It seems the exception is magically duplicated without any of these being called, as the destructor gets thrown twice.

So what workaround can I use here?

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ EDIT ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Added example code in order to explain:

C++0x std::thread constructor takes a functor or a function and 0..n movable arguments. Since MSVC doesn't support variadic templates, I used overloaded constructors for different number of arguments. For example, for two arguments:

template<typename C, typename A0, typename A1>
inline thread::thread(C const &func, A0 &&arg0, A1 &&arg1)
{
    Start(make_shared<function<void (void)>>(function<void (void)>(std::bind(forward<C&开发者_如何转开发gt;(func), forward<A0>(arg0), forward<A1>(arg1)))));
}

Next, Start() packages the std::function with an intrusive smart self-pointer in order to make sure the std::function is destroyed correctly when it's out of scope in both Start() and the thread Run() function started by _beginthreadex. Within the run function, the try/catch that actually runs the user supplied function/arguments would normally be:

try
{
    (*pack->runpack)();
}
catch (...)
{
    terminate();
}

However, I wanted to have the ability, in addition to the usual std::thread functionality, to do something like what happens when on Linux you call pthread_cancel(), and moreover be able to do either of the cancellation type analogues of PTHREAD_CANCEL_DEFERRED and PTHREAD_CANCEL_ASYNCHRONOUS. This is important as I'm writing for a kiosk application where the main application needs to be able to recover (most of the time) from threads hung when running third-party created modules.

I added the following above the catch (...) above:

catch (Cancellation const &c) // TODO: Make const & if removing _e flag from Cancellation; TODO: Catch by const & if not modifying internal state
{
    c.Exec();
}

where Cancellation::Exec() uses a pointer to the current std::thread (that I store in a thread-local) and calls detach(). Then I added two functions, the first one being:

bool CancelThreadSync(std::thread &t, unsigned int ms)
{
    if (!QueueUserAPC(APCProc, t.native_handle(), 0)) THROW_LASTWINERR(runtime_error, "Could not cancel thread")
    if (ms) Wait(ms);
    if (t.joinable()) return false;
    return true;

} APCProc sets some flag so that I can add a TestCancel(), similar to a pthread_testcancel() cancellation point function. TestCancel() throws Cancellation if the flag is set (pthreads on Linux cause the stack to be unwound and destructors called in a different way, but it does do that, so it's much better than TerminateThread()). However, I also changed all places I use waiting, such as WaitForSingleObject and Sleep, to the alertable versions of those functions, and so WaitForSingleObjectEx and SleepEx. For example:

inline void mutex::lock(void)
{
    while (Load(_owned) || _interlockedbittestandset(reinterpret_cast<long *>(&_waiters), 0))
    {
        unsigned long waiters(Load(_waiters) | 1);
        if (AtomicCAS(&_waiters, waiters, waiters + 512) == waiters) // Indicate sleeping
        {
            long const ret(NtWaitForKeyedEvent(_handle, reinterpret_cast<void **>(this), 1, nullptr)); // Sleep
            #pragma warning(disable : 4146) // Negating an unsigned
            AtomicAdd(&_waiters, -256); // Indicate finished waking
            #pragma warning(default : 4146)
            if (ret)
            {
                if (ret != STATUS_USER_APC) throw std::runtime_error("Failed to wait for keyed event");
                TestCancel();
            }
        }
    }
}

(original algorithm from http://www.locklessinc.com/articles/)

Note the check for whether an APC alerted the wait. Now I can cancel a running thread even when it's deadlocked. TestCancel() throws a Cancellation exception so the stack is unwound with destructors called before the thread terminates--this is exactly what I want. I also added asyncrhonous cancellation for cases where the block is not in a system call, but perhaps an infinite loop or really slow computation or a spinlock deadlock. I did this by suspending the thread, setting Eip (or Rip on x64) to point to a function that throws Cancellation, and resuming it. It's not perfectly robust but works in most cases and that's good enough for me.

I have tested things and they work great. My problem is how to prevent the user from catching a Cancellation without rethrowing it. I want it to always be allowed to propagate to the thread run function. So I thought I'd call an abort() in ~Cancellation() unless an internal flag is set (where the thread run function is a friend of Cancellation and the only that can do this). The problem is the multiple calling of the destructor that occurs, and my inability to get around this with reference counting since the duplication of the exception appears to happen without any of the copy/move constructor/assignment being called, from what I can tell (I tested by adding print statements to all of those).


Look at this question to see if that helps you. As explained there, when you call throw you pass a data object (a class in your case) to throw: this is copied and passed to all the catch blocks which handle that type. (If your catch uses the type directly there is another copy, if it uses a reference to the type then this is omitted.)

So you would expect the exception object to be deleted at least twice: once when the instance you use to initialise the throw is deleted, once when the last exception handler has been called and the object passed to the catch blocks is deleted.


abort is C runtime function, and doesn't goes well with C++ and Windows SEH exceptions. Avoid it. It wont be caught in either catch or __try/__except. Secondly, I suggest you to enable flag /EHs (C++ -> Code Generation -> Enable C++ Exception). This would facilitate you to catch both C++ and Windows SEH exceptions in try-catch block. It would also allow any pending destructors (however nested in callstack) to be called. Remember _try/_except will NOT allow destructors to be called.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜