3

I'm attempting to implement a scope guard class and am running into problems with cancellation points on Linux. From what I've found, Linux implements pthread cancellation in terms of exceptions, where the exception cannot be handled in a user's catch block (i.e. it must be rethrown). This causes problems for the following scope guard implementation that attempts to ignore any exceptions that may be thrown by its associated function:

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

#include <functional>

struct scope_guard {
    template <typename T>
    scope_guard(T func) : function_(func) {}

    ~scope_guard() {
        try {
            function_();
        }
        catch (...) {}
    }

    std::function<void()> function_;
};

extern "C" {
    void* thread_func(void*) {
        try {
            scope_guard guard( [](void) {
                // wait for a cancellation event
                while (true) {
                    pthread_testcancel();
                }
            });
            throw int();
        }
        catch (int&) {}

        return 0;
    }
}

int main(void) {

    pthread_t thread;
    int ret;
    if (0 != (ret = pthread_create(&thread, NULL, &thread_func, NULL))) {
        fprintf(stderr, "pthread_create failed: %d\n", ret);
        return 1;
    }

    sleep(1);

    if (0 != (ret = pthread_cancel(thread))) {
        fprintf(stderr, "pthread_cancel failed: %d\n", ret);
        return 1;
    }

    void* thread_ret;
    if (0 != (ret = pthread_join(thread, &thread_ret))) {
        fprintf(stderr, "pthread_join failed: %d\n", ret);
        return 1;
    }

    return 0;
}

~scope_guard() {
    try {
        function_();
    }
    catch (abi::__forced_unwind&) {
        throw;
    }
    catch (...) {}
}

This works fine on Solaris, HPUX and AIX, but on Linux it produces the following error and aborts:

FATAL: exception not rethrown

This is caused by the catch (...) block in ~scope_guard which catches but does not rethrow the pthread cancellation exception.

Based on this article, it appears that the recommended way to handle these cancellation exceptions is to either rethrow the exception from a catch (...) block, or to explicitly catch and rethrow the cancellation exception like so:

    ~scope_guard() {
        try {
            function_();
        }
        catch (abi::__forced_unwind&) {
            throw;
        }
        catch (...) {}
    }

Both of these approaches however cause a new exception to be thrown from this descructor, which causes the problem to terminate as an existing exception is already in the process of unwinding the stack. In my tests the following is reported:

terminate called without an active exception

Is there a way to handle this scenario on Linux?

DRH
  • 7,868
  • 35
  • 42

1 Answers1

0

The second version of ~scope_guard() is correct.

The problem here is, why do you throw int() in thread_func()? Delete it, your code will work fine.

And if you really want to throw int, you should need to write like below:

void* thread_func(void*) {
  scope_guard guard( [](void) {
    // wait for a cancellation event
    while (true) {
      pthread_testcancel();
      throw int(); // Throw either here
    }
    throw int(); // Or here
  });
  return 0;
}

Note you should put all the "possible throw code" in scope_guard's function.

Mine
  • 4,123
  • 1
  • 25
  • 46
  • This is a reduced test scenario to demonstrate what happens when an exception occurs (any exception, but in this case an int) which causes the stack to unwind, and where as a part of the stack unwinding, a pthread cancellation point is encountered. – DRH Dec 18 '13 at 07:10
  • I think the point is, the scope_guard should include all the codes that may throw. But actually I do not know what exactly happens in your demo. It looks like abi::__forced_unwind occurs first, the thread stack is unwinded, and then I don't know how `throw int()` makes things wrong... – Mine Dec 18 '13 at 08:36
  • The purpose of scope_guard is to ensure that some action is taken, regardless of whether it is due to normal execution or due to stack unwind from an uncaught exception. In the code above, the scope_guard instance is created first and placed on the stack. Nothing occurs in its constructor. Next the exception (int) is thrown, which causes the stack to unwind, as the stack unwinds, the scope_guard is destroyed, and the function associated with it is invoked. In that destructor, a pthread cancellation point is encountered, and as a result the __forced_unwind exception is thrown. – DRH Dec 18 '13 at 14:26
  • OK, that's very clear. I see what happens now. It could be related to this question: http://stackoverflow.com/questions/130117/throwing-exceptions-out-of-a-destructor – Mine Dec 19 '13 at 02:37