C++: PTHREAD_CANCEL_ASYNCHRONOUS and random crash

  • Follow


I am trying to terminate threads in a C++ program using POSIX threads.
I use pthread_cancel(), and the thread is configured with
PTHREAD_CANCEL_ENABLE and PTHREAD_CANCEL_ASYNCHRONOUS.

However, when I run this test program, it crashes randomly, saying
"terminate called without an active exception".

How do I fix it so that it does not crash?
I want to use PTHREAD_CANCEL_ASYNCHRONOUS, because the thread will
be doing a loop of cpu-intensive stuff and I don't want to insert
pthread_testcancel() in the loop.

If I replace the std::vector<> with a custom-built class that has
constructors and destructors, it will still crash. If I use a plain
old C array, it does not crash. However, in the actual program where
I need this solution, there are C++ objects and it cannot be easily
avoided.

#include <vector>

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

void* Runner(void*p)
{
    pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, 0);
    pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, 0);
    
    const unsigned n=40000;
    
    std::vector<int> temp(n);
    
    for(unsigned c=0; c<5; ++c)
        for(unsigned a=0; a<n; ++a)
            temp[a]=n-a;
    fprintf(stderr, "- Got %u\n", n);
    return NULL;
}
 
int main(void)
{
    const unsigned n = 5;
    pthread_t threads[n];
    for(;;)
    {
        fprintf(stderr, "Creating %u threads\n", n);
        for(unsigned a=0; a<n; ++a)
            pthread_create(&threads[a], NULL, Runner, NULL);
        
        unsigned c = 2;
        fprintf(stderr, "Cancelling thread %u\n", c);
        pthread_cancel(threads[c]);
        
        fprintf(stderr, "Joining %u threads\n", n);
        for(unsigned a=0; a<n; ++a)
            pthread_join(threads[a], NULL);
    
        fprintf(stderr, "** Everything ok\n");
    }
}

-- 
Joel Yliluoma - http://bisqwit.iki.fi/
: comprehension = 1 / (2 ^ precision)
0
Reply Joel 7/28/2007 10:55:40 AM

Joel Yliluoma wrote:
> I am trying to terminate threads in a C++ program using POSIX threads.
> I use pthread_cancel(), and the thread is configured with
> PTHREAD_CANCEL_ENABLE and PTHREAD_CANCEL_ASYNCHRONOUS.
> 
> However, when I run this test program, it crashes randomly, saying
> "terminate called without an active exception".
> 
On what platform?


-- 
Ian Collins.
0
Reply Ian 7/28/2007 11:42:11 AM


On Sat, 28 Jul 2007 23:42:11 +1200, Ian Collins wrote:
> Joel Yliluoma wrote:
>> I am trying to terminate threads in a C++ program using POSIX threads.
>> I use pthread_cancel(), and the thread is configured with
>> PTHREAD_CANCEL_ENABLE and PTHREAD_CANCEL_ASYNCHRONOUS.
>> 
>> However, when I run this test program, it crashes randomly, saying
>> "terminate called without an active exception".
>> 
> On what platform?

Linux, both 64-bit and 32-bit are affected.
Compiler is g++-4.1, tested also 3.3; crashes but more tersely ("Abort").

-- 
Joel Yliluoma - http://bisqwit.iki.fi/
: comprehension = 1 / (2 ^ precision)
0
Reply Joel 7/28/2007 11:52:58 AM

On Jul 28, 3:55 am, Joel Yliluoma <bisq...@iki.fi> wrote:

> I am trying to terminate threads in a C++ program using POSIX threads.
> I use pthread_cancel(), and the thread is configured with
> PTHREAD_CANCEL_ENABLE and PTHREAD_CANCEL_ASYNCHRONOUS.
>
> However, when I run this test program, it crashes randomly, saying
> "terminate called without an active exception".

Well, what did you expect? What do you think should happen if you
cancel a thread while it's in the middle of a memory allocation?

> How do I fix it so that it does not crash?

Don't leave asynchronous cancellation enabled when your thread is in a
state where asynchronous cancellation is not known to be safe.

> I want to use PTHREAD_CANCEL_ASYNCHRONOUS, because the thread will
> be doing a loop of cpu-intensive stuff and I don't want to insert
> pthread_testcancel() in the loop.

Then you must make sure asynchronous cancellation is only enabled when
the thread is in a cancellation-safe state.

> If I replace the std::vector<> with a custom-built class that has
> constructors and destructors, it will still crash.

Because the constructors and destructors are not asynchronous
cancellation safe, no surprise there.

> If I use a plain
> old C array, it does not crash.

Because you have no asynchronous cancellation unsafe states.

> However, in the actual program where
> I need this solution, there are C++ objects and it cannot be easily
> avoided.

You have three choices:

1) Don't use asynchronous cancellation.

2) Make sure any thread you plan to cancel asynchronously never enters
a state where asynchronous cancellation is unsafe.

3) Disable asynchronous cancellation when you enter states where
asynchronous cancellation is unsafe.

DS

0
Reply David 7/28/2007 7:22:06 PM

On Sat, 28 Jul 2007 12:22:06 -0700, David Schwartz wrote:
> Well, what did you expect? What do you think should happen if you
> cancel a thread while it's in the middle of a memory allocation?

It is not a problem pertinent to memory allocation.

In fact, I think it has more to do with C++ standard forbidding
the throwing of exceptions while in execution of a destructor.

Since the cancel is an exception, and it may be thrown at _any_
time, it may also be thrown during the execution of a destructor,
leading into the shown behavior.

-- 
Joel Yliluoma - http://bisqwit.iki.fi/
: comprehension = 1 / (2 ^ precision)
0
Reply Joel 8/1/2007 7:31:12 AM

Joel Yliluoma wrote:

> On Sat, 28 Jul 2007 12:22:06 -0700, David Schwartz wrote:
> 
>>Well, what did you expect? What do you think should happen if you
>>cancel a thread while it's in the middle of a memory allocation?
> 
> 
> It is not a problem pertinent to memory allocation.
> 
> In fact, I think it has more to do with C++ standard forbidding
> the throwing of exceptions while in execution of a destructor.

That is simply not true. You can throw an exception from a destructor 
but it is very seldom a good idea. std::unexpected() will be called if 
an exception will be thrown while exception handling is in progress.

> Since the cancel is an exception, and it may be thrown at _any_
> time, it may also be thrown during the execution of a destructor,
> leading into the shown behavior.

It might be implemented as an exception. To write robust, reliable C++ 
code one need some functions that have the guaranty to never ever throw 
an exception (std::vector<>::swap() is such a function). So cancelling a 
thread that is executing C++ code, that is not written do be cancelled 
asynchron, leads to weak and unreliable programs.

If you really have to use asynchron cancellation, you should enable 
cancellation just for the parts of the program, you have written and 
that are save to be cancelled asynchron. So a call to 
std::vector<>::push() wouldn't be save to be cancelled.

regards,
Torsten

0
Reply Torsten 8/1/2007 8:35:50 AM

Joel Yliluoma wrote:
> On Sat, 28 Jul 2007 12:22:06 -0700, David Schwartz wrote:
>> Well, what did you expect? What do you think should happen if you
>> cancel a thread while it's in the middle of a memory allocation?
> 
> It is not a problem pertinent to memory allocation.
> 
It might be, all sorts of nasties might happen if a vector is being
allocated.  There's a more than fair chance of a lockup if either the
vector code or IO has a mutex locked when the thread is canceled.

> In fact, I think it has more to do with C++ standard forbidding
> the throwing of exceptions while in execution of a destructor.
> 
There is no such prohibition, just good style.

> Since the cancel is an exception, and it may be thrown at _any_
> time, it may also be thrown during the execution of a destructor,
> leading into the shown behavior.
> 
Who says the cancel is an exception?  If it was and the exception was
unhandled, why the "terminate called without an active exception" message?

-- 
Ian Collins.
0
Reply Ian 8/1/2007 8:46:39 AM

On Wed, 01 Aug 2007 20:46:39 +1200, Ian Collins wrote:
> > It is not a problem pertinent to memory allocation.
> > 
> It might be, all sorts of nasties might happen if a vector is being
> allocated.  There's a more than fair chance of a lockup if either the
> vector code or IO has a mutex locked when the thread is canceled.

Yes, it might be, but it is not the common denominator. The same error
happens regardless of whether there is code involved that allocates
memory dynamically or not.
It has more to do with constructors and destructors.
For example, the code attached below does the same error, even though
there is no dynamic memory allocation involved.


> > In fact, I think it has more to do with C++ standard forbidding
> > the throwing of exceptions while in execution of a destructor.
>
> There is no such prohibition, just good style.

Ah, I see.


> > Since the cancel is an exception, and it may be thrown at _any_
> > time, it may also be thrown during the execution of a destructor,
> > leading into the shown behavior.
>
> Who says the cancel is an exception?  If it was and the exception was
> unhandled, why the "terminate called without an active exception" message?

Well, that is to say GCC seems to do it that way...
With the frame unwinding and all.

Example code below. No dynamic memory allocation.

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

const unsigned n=40000;

struct tempobj
{
public:
    tempobj();
    ~tempobj();
    
    int& ref() { return a; }
    
    int a;
};
tempobj::tempobj() { }
tempobj::~tempobj() { }

void* Runner(void*p)
{
    pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, 0);
    pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, 0);

    tempobj temp;
    for(unsigned c=0; c<5; ++c)
        for(unsigned a=0; a<n; ++a)
            temp.ref() = n-a;

    putchar('.');
    return NULL;

} 
volatile unsigned v;
int main(void)
{
    const unsigned n = 5;
    pthread_t threads[n];
    for(;;)
    {
        for(unsigned a=0; a<n; ++a)
            pthread_create(&threads[a], NULL, Runner, NULL);
        
        for(unsigned b=0; b<400000; ++b) v=b;
        
        unsigned c = 2;
        pthread_cancel(threads[c]);
        for(unsigned a=0; a<n; ++a)
            pthread_join(threads[a], NULL);
    }
}

-- 
Joel Yliluoma - http://bisqwit.iki.fi/
: comprehension = 1 / (2 ^ precision)
0
Reply Joel 8/1/2007 9:09:29 AM

On 01 Aug 2007 09:09:29 GMT, Joel Yliluoma wrote:
> Example code below. No dynamic memory allocation.

Compile with inlining disabled to invoke the error.
I didn't bother going to lengths to ensure compiler won't inline stuff.

-- 
Joel Yliluoma - http://bisqwit.iki.fi/
: comprehension = 1 / (2 ^ precision)
0
Reply Joel 8/1/2007 9:13:54 AM

Joel Yliluoma wrote:
> On Wed, 01 Aug 2007 20:46:39 +1200, Ian Collins wrote:
>>> It is not a problem pertinent to memory allocation.
>>>
>> It might be, all sorts of nasties might happen if a vector is being
>> allocated.  There's a more than fair chance of a lockup if either the
>> vector code or IO has a mutex locked when the thread is canceled.
> 
> Yes, it might be, but it is not the common denominator. The same error
> happens regardless of whether there is code involved that allocates
> memory dynamically or not.
> It has more to do with constructors and destructors.
> For example, the code attached below does the same error, even though
> there is no dynamic memory allocation involved.
> 
Not on my system (Solaris), it just runs forever.  Constructors and
destructors are just function calls.

The point wasn't so much the exact reason why the problem occurred, but
that allowing asynchronous cancellation during library calls where it
isn't guaranteed to be safe is a bad idea.
> 
>>> Since the cancel is an exception, and it may be thrown at _any_
>>> time, it may also be thrown during the execution of a destructor,
>>> leading into the shown behavior.
>> Who says the cancel is an exception?  If it was and the exception was
>> unhandled, why the "terminate called without an active exception" message?
> 
> Well, that is to say GCC seems to do it that way...
> With the frame unwinding and all.
> 
Um, then I'd have expected there to be an active exception when
terminate was called.

-- 
Ian Collins.
0
Reply Ian 8/1/2007 9:31:06 AM

On Aug 1, 12:31 am, Joel Yliluoma <bisq...@iki.fi> wrote:

> Since the cancel is an exception, and it may be thrown at _any_
> time, it may also be thrown during the execution of a destructor,
> leading into the shown behavior.

Pthread cancellation is most definitely *not* an exception. It is
neither thrown, caught, nor handled.

DS

0
Reply David 8/2/2007 4:54:36 AM

David Schwartz wrote:
> On Aug 1, 12:31 am, Joel Yliluoma <bisq...@iki.fi> wrote:
> 
>> Since the cancel is an exception, and it may be thrown at _any_
>> time, it may also be thrown during the execution of a destructor,
>> leading into the shown behavior.
> 
> Pthread cancellation is most definitely *not* an exception. It is
> neither thrown, caught, nor handled.

Mostly true. ;-)

Pthread cancellation is, in fact, designed and intended to be an 
exception, and the standard was carefully written to not only ALLOW but 
(to the small extent feasible) ENCOURAGE implementation of cancellation 
using exceptions where available. POSIX cleanup handlers are nothing but 
detached exception finalization (with no continuation option simply 
because of the limitations of POSIX semantics).

Although based in truth on Ada 'finally' semantics, in C++ terms the 
intent would probably best map into pthread_cleanup_push() constructing 
a local "cancel guard" object that would properly manage calling the 
cleanup handler.

But of course as neither ISO C nor POSIX had any such concept as 
"exception", there was no way to formally require anything truly useful.

So, to correct your statement, "POSIX most definitely does not require 
that Pthread cancellation be an exception."

On any platform with any interest at all in C++ and threading, the 
platform's C runtime & compiler WILL support exceptions and cancellation 
WILL BE an exception, and the C/C++/threads runtime code will 
interoperate to make this all work correctly; but as this spans several 
independent standards there's no way to specify that it "shall work". We 
must instead simply rely on people being smart enough to figure out that 
it SHOULD work. ;-)
0
Reply Dave 8/2/2007 1:55:54 PM

11 Replies
304 Views

(page loaded in 0.204 seconds)

Similiar Articles:


















7/27/2012 5:05:14 PM


Reply: