Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

std::thread does not follow RAII principles

549 views
Skip to first unread message

Juha Nieminen

unread,
May 26, 2021, 7:51:42 AM5/26/21
to
I have noticed a rather odd, and somewhat annoying, design decision in
std::thread, which breaks RAII design.

Normally when you use a C++ class from the standard library which allocates
some resource, the class in question will make sure to properly freeing
that resource when the object is destroyed, which is par for the course in
good RAII design.

For example, rather obviously, if you create an object of type std::vector,
it will delete its allocated memory (and destroy the elements) when it's
itself destroyed.

A more noteworthy example of something that allocates a resource other than
memory is std::ofstream: When it's destroyed, if it has a file open, it will
properly close the file.

This adds safety because it minimizes the risk of mistakes, especially since
functions may be exited at surprising places thanks to exceptions. Like:

void foo()
{
std::ofstream os("somefile");

doSomething(); // this might throw!

// ...
}

No matter how that function is exited, even if an exception is thrown,
that std::ofstream object will close the file so that the file handle isn't
leaked.

But now we come to std::thread. Suppose you have something like:

void foo()
{
std::thread t(someFunction);

doSomething();

signalTheThreadToStop();
t.join();
}

The problem with this is that if doSomething() throws an exception, the
program will crash. It doesn't even matter that you may be prepared for
the exception, like:

try { foo(); }
catch(...) { std::cout << "Exception thrown!\n"; }

the program will still crash, because std::thread does not like being
destroyed without being joined or detached, so it just terminates if that's
the case.

This is quite inconvenient, and as far as I know the standard library doesn't
really offer any utility to make disposing of the thread properly more
automatic. You'll have to write your own "thread handler" that will terminate
the thread properly when the thread object is destroyed, so that the
program won't crash if the function is exited unexpectedly.

This feels very counter to RAII principles, where objects should take care
of the resources they have allocated, rather than demanding the calling code
to manually do so.

Bonita Montero

unread,
May 26, 2021, 8:05:00 AM5/26/21
to
> the program will still crash, because std::thread does not like being
> destroyed without being joined or detached, so it just terminates if that's
> the case.

Where's the problem ? Use a scrope-guard which waits for the end of the
thread or a jthread with C++.

That's my scope-guard class:

#pragma once
#include <cassert>
#include "debug_exceptions.h"

template<typename T>
struct invoke_on_destruct
{
private:
T m_t;
bool m_enabled;

public:
invoke_on_destruct( T t ) :
m_t( t ), m_enabled( true )
{
}
~invoke_on_destruct()
{
if( m_enabled )
{
try_debug
{
m_t();
}
catch_debug
{
assert(false);
}
}
}
void disable_and_invoke()
{
m_enabled = false;
m_t();
}
void disable()
{
m_enabled = false;
}
void enable()
{
m_enabled = true;
}
};

#if defined(IOD_SHORT)
template<typename C>
using iod = invoke_on_destruct<C>;
#define IOD(varName, lambdaName) \
iod<decltype(lambdaName)> varName( lambdaName );
#endif

Paavo Helde

unread,
May 26, 2021, 10:35:34 AM5/26/21
to
26.05.2021 14:51 Juha Nieminen kirjutas:
> I have noticed a rather odd, and somewhat annoying, design decision in
> std::thread, which breaks RAII design.

>
> the program will still crash, because std::thread does not like being
> destroyed without being joined or detached, so it just terminates if that's
> the case.

std::thread is based on boost::thread which automatically detaches in
the destructor. Alas, this would create a rampant thread which cannot be
joined any more.

I guess when it got standardized, they felt that such automatic detach
is no good, but did not dare to enforce automatic join either, by some
reason. So they chose to std::terminate() which is the worst of them all
IMO.

See https://isocpp.org/files/papers/p0206r0.html (Discussion about
std::thread and RAII).

Scott Lurndal

unread,
May 26, 2021, 11:20:19 AM5/26/21
to
Juha Nieminen <nos...@thanks.invalid> writes:
>I have noticed a rather odd, and somewhat annoying, design decision in
>std::thread, which breaks RAII design.

That's one of the reasons we use pthreads instead of C++ threads.

MrSpook_...@q0jfxa33c.biz

unread,
May 26, 2021, 11:25:05 AM5/26/21
to
On Wed, 26 May 2021 11:51:24 +0000 (UTC)
Juha Nieminen <nos...@thanks.invalid> wrote:
>the program will still crash, because std::thread does not like being
>destroyed without being joined or detached, so it just terminates if that's
>the case.

I'll have to add this to my list of why the C++ threading library is crap.
I would suggest if your code doesn't need to be portable to either use
posix threads on *nix or its Windows equivalent.

MrSpoo...@_4fk8arybb76.eu

unread,
May 26, 2021, 11:26:06 AM5/26/21
to
On Wed, 26 May 2021 14:04:42 +0200
Bonita Montero <Bonita....@gmail.com> wrote:
>> the program will still crash, because std::thread does not like being
>> destroyed without being joined or detached, so it just terminates if that's
>> the case.
>
>Where's the problem ? Use a scrope-guard which waits for the end of the
>thread or a jthread with C++.
>
>That's my scope-guard class:

This sort of home brew stuff shouldn't needed to control standard operations
on threads.

Manfred

unread,
May 26, 2021, 11:45:18 AM5/26/21
to
Having the thread to detach on destruction is a poor choice, IMO.
On the other hand defaulting to join would lead to buggy programs likely
to hang indefinitely instead of recovering or terminate. I can
understand the committee's concerns in this sense.

We see more and more commonly (admittedly in mediocre programs) that
unexpected default actions provided by "easy to use" languages bubble up
to the level of user's experience because of software bugs - this
reflects poorly on the perceived quality of the language itself.

However, I agree with the OP that this is inconsistent with RAII principles.

Thanks for the link, btw.

Bonita Montero

unread,
May 26, 2021, 11:50:45 AM5/26/21
to
>>> the program will still crash, because std::thread does not like being
>>> destroyed without being joined or detached, so it just terminates if that's
>>> the case.

>> Where's the problem ? Use a scrope-guard which waits for the end of the
>> thread or a jthread with C++.

>> That's my scope-guard class:

> This sort of home brew stuff shouldn't needed to control standard operations
> on threads.

Why not ? Before C++20 there's no jthread, so you could write:

#define(IOD_SHORT)
#include <invoke_on_destruct.h>


...
{
thread thr( xxx );
auto waitForTerminate = [&]()
{
thr.join();
};
IOD(iodWaitForTerminate, waitForTerminate);
}

There's nothing unprofessional with that. I use IOD often,
especially for closing for closing resources for which it
would be too much work to have a special RAII-class for.

Bonita Montero

unread,
May 26, 2021, 11:52:22 AM5/26/21
to
> std::thread is based on boost::thread which automatically detaches in
> the destructor. ...

No, if the thread doesn't terminate before destruction or you
detach it an exception is thrown and if that is done while un-
winding your application is terminate()d.

Bonita Montero

unread,
May 26, 2021, 11:54:21 AM5/26/21
to
> That's one of the reasons we use pthreads instead of C++ threads.

pthreads are really poor. F.e. having the opportunity to pass
arbitrary parameter lists which might hold resources as elegant
as a shared_ptr<> is much more powerful. And if you use pthreads
you could also use std::thread( xxx, params ... ).detach() instead.

Bonita Montero

unread,
May 26, 2021, 12:02:37 PM5/26/21
to
> I'll have to add this to my list of why the C++ threading library is crap.

Because of that ting ascpect ? Have you ever noticed the RAII-flexi-
bility of C++-locking ? Have syou ever noted how convenient it is
to pass arbitrary parameter-lists to your thread as it it would be
a directly called function; compare the work of defining a structure,
to define it, allocate it, fill it, pass it to the thread and deallo-
cate it at the end of the thread - that's all for free in C++ and
the performance is the same !

> I would suggest if your code doesn't need to be portable to
> either use posix threads on *nix or its Windows equivalent.

That's a lot of work more than with C++-threads.

MrSpook_...@9wp2.net

unread,
May 26, 2021, 12:28:23 PM5/26/21
to
On Wed, 26 May 2021 17:54:06 +0200
Bonita Montero <Bonita....@gmail.com> wrote:
>> That's one of the reasons we use pthreads instead of C++ threads.
>
>pthreads are really poor. F.e. having the opportunity to pass

Really? Want to have a guess what the C++ threading library on Linux uses?

Bonita Montero

unread,
May 26, 2021, 1:05:12 PM5/26/21
to
>> pthreads are really poor. F.e. having the opportunity to pass

> Really? Want to have a guess what the C++ threading library on Linux uses?

We don't discuss physical threading but how the language presents
threading; and C++11-threading is by far more convenient than pure
pthreads.

Paavo Helde

unread,
May 26, 2021, 2:41:54 PM5/26/21
to
Right, it seems boost has formally deprecated the earlier detach()
behavior in newer releases in favor of terminate(). This is controlled
by the BOOST_THREAD_VERSION macro. By default this appears still to be
defined to 2, which means detach(), and version 3 only appeared in 2012.
So I should have said:

"std::thread is based on boost::thread which automatically detached in
the destructor, at least at the time when std::thread was standardized."





MrSpo...@939_6htz773e0qeya.eu

unread,
May 27, 2021, 4:18:57 AM5/27/21
to
A Big Mac is convenient, doesn't make it the best meal. The pthreads library
is extremely powerful, perhaps the boiler plate setup code can be a bit long
winded but its not hard to use.

Bonita Montero

unread,
May 27, 2021, 5:02:25 AM5/27/21
to
> A Big Mac is convenient, doesn't make it the best meal. ...

Programming pthreads directly has no advantages
and makes a lot of workd more.

Juha Nieminen

unread,
May 27, 2021, 6:13:29 AM5/27/21
to
Why don't you just fuck off, asshole? You aren't contributing to the
dicussion. You are just being an asshole.

MrSpook...@9slt8vr2tlp1ljrvd8h.net

unread,
May 27, 2021, 6:21:16 AM5/27/21
to
On Thu, 27 May 2021 10:13:13 +0000 (UTC)
Juha Nieminen <nos...@thanks.invalid> wrote:
>MrSpook_...@q0jfxa33c.biz wrote:
>> On Wed, 26 May 2021 11:51:24 +0000 (UTC)
>> Juha Nieminen <nos...@thanks.invalid> wrote:
>>>the program will still crash, because std::thread does not like being
>>>destroyed without being joined or detached, so it just terminates if that's
>>>the case.
>>
>> I'll have to add this to my list of why the C++ threading library is crap.
>> I would suggest if your code doesn't need to be portable to either use
>> posix threads on *nix or its Windows equivalent.
>
>Why don't you just fuck off, asshole? You aren't contributing to the
>dicussion. You are just being an asshole.

Good morning Mr Happy, things going well in Finland today?

MrSpoo...@jxgz6zklebr1.tv

unread,
May 27, 2021, 6:24:42 AM5/27/21
to
On Thu, 27 May 2021 11:02:11 +0200
Bonita Montero <Bonita....@gmail.com> wrote:
>> A Big Mac is convenient, doesn't make it the best meal. ...
>
>Programming pthreads directly has no advantages

Presumably you've never had to use 3 level locking or fine grain threading
control. Also the lack of proper interoperability with signals makes C++
threading on unix a bit of a toy frankly.

>and makes a lot of workd more.

A bit, not a lot.

Bonita Montero

unread,
May 27, 2021, 6:33:45 AM5/27/21
to
> Presumably you've never had to use 3 level locking or fine grain threading
> control. Also the lack of proper interoperability with signals makes C++
> threading on unix a bit of a toy frankly.

Signals are a plague. You can't write a libary which does have a
Signal-handling which is coordinatet independently from the code
it is later embedded into. Both have to be made of a single piece.
Signals are even more worse since there can't be different handlers
for synchonous signals for different threads.
And signal-codd always have a reentrancy problem, that makes them
a even bigger plague. And the ABI has to be designed around them
(red zone). That's not clean coding.
Therefore: Outsource asynchronous signals to differnt threads.
Windows has a more powerful handling for something like synchronous
signals, Structured Excetion Handling. And for the few asynchronous
signals Windows knows, Windows spawns a diffrent thread if a signal
happens.

And which threading-contol is needed beyond that what C++ provides ?

Bonita Montero

unread,
May 27, 2021, 6:40:26 AM5/27/21
to
>> Presumably you've never had to use 3 level locking or fine grain
>> threading
>> control. Also the lack of proper interoperability with signals makes C++
>> threading on unix a bit of a toy frankly.

> Signals are a plague....
And even more: If I use C++-threading the places where signals could
occur and where I can't get the EAGAIN are only where I have locking
and / or waiting for a condition_variable. But the places where I
lock a mutex or wait for a CV with pthreads, Posix mandates you to
re-lock the mutex or re-wait for the CV - that's exactly what C++11
-synhroni-zation does also - so there's no difference here. So what
do you complain here ?

Bonita Montero

unread,
May 27, 2021, 6:55:22 AM5/27/21
to
> And even more: If I use C++-threading the places where signals could
> occur and where I can't get the EAGAIN are only where I have locking
> and / or waiting for a condition_variable. But the places where I
> lock a mutex or wait for a CV with pthreads, Posix mandates you to
> re-lock the mutex or re-wait for the CV - that's exactly what C++11
> -synhroni-zation does also - so there's no difference here. So what
> do you complain here ?

Oh, I'm partitially wrong here: pthread_mutex_wait behaves as described
_but_ pthread_cond_wait handles the signal-handler internally and con-
tinues waiting afterwards.
So there's still nothing different than with C+11-threads !

MrSpook_...@8dftupdzk09cop2.co.uk

unread,
May 27, 2021, 7:05:48 AM5/27/21
to
On Thu, 27 May 2021 12:33:28 +0200
Bonita Montero <Bonita....@gmail.com> wrote:
>> Presumably you've never had to use 3 level locking or fine grain threading
>> control. Also the lack of proper interoperability with signals makes C++
>> threading on unix a bit of a toy frankly.
>
>Signals are a plague. You can't write a libary which does have a

Says the windows programmer.

>Signal-handling which is coordinatet independently from the code

Good, libraries should not be handling signals.

>Therefore: Outsource asynchronous signals to differnt threads.

You simply have a signal handling thread that sits in sigwait() and block
signals to everything else using pthread_sigmask(SIG_BLOCK...) which -
surprise! - you can't do in C++ threads.

>Windows has a more powerful handling for something like synchronous
>signals, Structured Excetion Handling. And for the few asynchronous
>signals Windows knows, Windows spawns a diffrent thread if a signal
>happens.

Spawning a new thread for every signal isn't powerful , its moronic.

>And which threading-contol is needed beyond that what C++ provides ?

I'm not going to keep repeating myself, you appear to be wilfully deaf.

Bonita Montero

unread,
May 27, 2021, 7:14:52 AM5/27/21
to
>> Signals are a plague. You can't write a libary which does have a

> Says the windows programmer.

Signals are simply a bad concept. No one would invent them today.

>> Signal-handling which is coordinatet independently from the code

> Good, libraries should not be handling signals.

The problem is that they partitially need. And that's not possible
without coordination with the code the're embedded into.

>> Therefore: Outsource asynchronous signals to differnt threads.

> You simply have a signal handling thread that sits in sigwait() and
> block signals to everything else using pthread_sigmask(SIG_BLOCK...)
> which - surprise! - you can't do in C++ threads.

Of course you could call sigwait() from a C++11-thread.

>> Windows has a more powerful handling for something like synchronous
>> signals, Structured Excetion Handling. And for the few asynchronous
>> signals Windows knows, Windows spawns a diffrent thread if a signal
>> happens.

> Spawning a new thread for every signal isn't powerful , its moronic.

That absolutely doesn't matter since the signals Windows knows are
very infreqiernt.

>> And which threading-contol is needed beyond that what C++ provides ?

> I'm not going to keep repeating myself, you appear to be wilfully deaf.

You are ultimately stupid !

Bonita Montero

unread,
May 27, 2021, 7:18:41 AM5/27/21
to
> The problem is that they partitially need. And that's not possible
> without coordination with the code the're embedded into.

Imagine you've got two framworks with two configuration-files which
are refreshed through SIGUP - that's not possible without central
coordination.
Or imagine you've a application which does asynchronous I/O and a
include libary that does it as well. Handling SIGIO and passs the
results back to the originating threads can be a complex task. And
both must be coordinated centrally as there coudln't be any indi-
vidual signal-handlers.

Signals are simply a weak concept.

Juha Nieminen

unread,
May 27, 2021, 8:24:54 AM5/27/21
to
Fuck off, asshole.

MrSpoo...@bltmyc.gov.uk

unread,
May 27, 2021, 11:40:11 AM5/27/21
to
On Thu, 27 May 2021 12:24:36 +0000 (UTC)
Oh dear, another bad day? Have a lie down and cuddle the therapy teddy.

MrSpo...@ukpge.org

unread,
May 27, 2021, 11:54:46 AM5/27/21
to
On Thu, 27 May 2021 13:14:36 +0200
Bonita Montero <Bonita....@gmail.com> wrote:
>>> Signals are a plague. You can't write a libary which does have a
>
>> Says the windows programmer.
>
>Signals are simply a bad concept. No one would invent them today.

Behold! Our mighty sage has spoken - let it be known that interrupts are a bad
idea!

Whatever you say sweetie.


Bonita Montero

unread,
May 27, 2021, 1:33:16 PM5/27/21
to
>> Signals are simply a bad concept. No one would invent them today.

> Behold! Our mighty sage has spoken - let it be known that interrupts are a bad
> idea!

Interrupts in userland with reentrancy-constraints a very bad idea.
And interrupting OS-calls with signals makes the operating-system
design very complex if you really want to make it interruptible at
every time while processing a call. That's not necessary if you'd
design it not that stupid.

Chris Vine

unread,
May 27, 2021, 2:00:13 PM5/27/21
to
Interrupts were necessary with single-threaded programs. They are not
usually necessary with multi-threaded programs and POSIX provides
everything you need in consequence. You block the signals of interest
in all your threads and then have a single handler thread with blocks
on sigwait() and deals with the signals synchronously (synchronously
for the handler thread that is). Job done.

One other thing POSIX provides and C++11 doesn't is thread cancellation.
Some programmers who don't have relevant experience think thread
cancellation is a bad idea because it allows arbitrary cancellation
which cannot be adequately controlled (the "combing your hair with a
fork" jibe). Whilst that is true with windows it absolutely isn't with
POSIX. What you do is use deferred cancellation (the default in POSIX)
and block cancellation as normal policy, and only enable cancellation
at defined points in the code which are able to deal with it (normally
where a wait is to occur). Done properly, thread cancellation is far
easier to use than exceptions, which can jump out of anywhere if you
include std::bad_alloc in the mix. C++11 threads don't deal with
cancellation and will never be able to do so because they require OS
support - in this case POSIX support.

Öö Tiib

unread,
May 27, 2021, 8:52:47 PM5/27/21
to
With that thread cancellation I'm always in doubt. Especially if discussed
in context of RAII and destructor of thing like std::thread.

One of major reasons making a thread is to turn synchronous work into
asynchronous. For that we put it into thread so then synchronous work is
going on in thread doing its blocking operations (potentially checking if
the work has become obsolete and/or reporting progress between those)
and once complete (or failed) handing the results back over to caller thread.
That way the caller thread has all benefits of asynchronous works going on.
It is fully responsive, so can take care of other responsibilities, entertain
users, report progresses, mark ongoing works obsolete and/or put out the
results (or failures) once something becomes complete.

But what is that cancellation? It is like telling to worker thread to die in
middle of unknown state potentially in kernel with unknown amount of
locks taken, files open or mapped to virtual memory, that fragile external
hardware device produced by random morons in middle of something
and what not? std::terminate feels better and more honest than to cancel
the thread ... at least our users will see that we screwed all up in major
way. It all gets of course lost in examples because these talk about "foo()".

Bonita Montero

unread,
May 27, 2021, 9:55:46 PM5/27/21
to
> What you do is use deferred cancellation (the default in POSIX)
> and block cancellation as normal policy, and only enable cancellation
> at defined points in the code which are able to deal with it (normally
> where a wait is to occur). Done properly, thread cancellation is far
> easier to use than exceptions, ...

Handling cancelation via flags and exceptions is cleaner and as easy
to handle. It's cleaner as it does honor destructors to be called inside
the stack. So never use Posix cancellation in C++-programs !

Bonita Montero

unread,
May 27, 2021, 10:02:50 PM5/27/21
to
> ofstream's destructor has to ignore errors on closing the system handle ---
> a bad choice, data loss without detection.

See it that way: when closing a file-handle file data is usally
written back asynchronously, so you can't see if writing the
data was done properly if you don't flush before. So silently
closing the handle is o.k..

Ian Collins

unread,
May 27, 2021, 10:59:41 PM5/27/21
to
Cancellation is well defined in POSIX. Cancellation points are defined
by the standard and threads can manage their own cancellation type and
state,

If you want to manage resources in a thread that may be cancelled, you
can use cleanup functions. I haven't tried it in Linux, but certainly
in Solaris, the C++ runtime will destroy objects if the creating thread
is cancelled. This is one of those annoying behaviours not covered by
either the C++ or POSIX standards...

A quick check on Linux shows that yes, destructors are called when a
thread is cancelled.

--
Ian

Bonita Montero

unread,
May 27, 2021, 11:25:13 PM5/27/21
to
> If you want to manage resources in a thread that may be cancelled, you
> can use cleanup functions.  I haven't tried it in Linux, but certainly
> in Solaris, the C++ runtime will destroy objects if the creating thread
> is cancelled. ...

With Linux that doesn't work:

#include <iostream>
#include <limits>
#include <pthread.h>
#include <unistd.h>

using namespace std;

struct destr
{
~destr();
};

destr::~destr()
{
cout << "destr::~destr()" << endl;
}

int main()
{
auto thr = []( void * ) -> void *
{
cout << "thread is running" << endl;
int oldStat;
if( pthread_setcanceltype( PTHREAD_CANCEL_DEFERRED, &oldStat ) != 0
|| pthread_setcanceltype( PTHREAD_CANCEL_ENABLE, &oldStat ) != 0 )
{
cout << "can't enable cancelling" << endl;
return nullptr;
}
destr d;
sleep( numeric_limits<int>::max() );
return nullptr;
};
pthread_t pt;
if( pthread_create( &pt, nullptr, thr, nullptr ) != 0 )
{
cout << "can't create thread" << endl;
return -1;
}
if( pthread_cancel( pt ) != 0 )
{
cout << "can't cancel thread" << endl;
return -1;
}
if( pthread_join( pt, nullptr ) != 0 )
{
cout << "can't join thread" << endl;
return -1;
}

}

Remember that a cancellation-request remains queued even if cancellation
isn't enabled yet.

Bonita Montero

unread,
May 27, 2021, 11:27:34 PM5/27/21
to
>         if(    pthread_setcanceltype( PTHREAD_CANCEL_DEFERRED, &oldStat
> ) != 0
>             || pthread_setcanceltype( PTHREAD_CANCEL_ENABLE,   &oldStat
> ) != 0 )

Oh, a little bug:
if( pthread_setcanceltype( PTHREAD_CANCEL_DEFERRED, &oldStat ) != 0
|| pthread_setcancelstate( PTHREAD_CANCEL_ENABLE, &oldStat ) != 0 )
But doesn't change anything.

Öö Tiib

unread,
May 27, 2021, 11:37:42 PM5/27/21
to
Very interesting ... but magical solutions make me even more worried.
Is there some kind of secret exception thrown or some kind of alternative
stack unwinding used or what? If secret exception then does
catch(...) { mopup(); throw; } work or has it to be full RAII? If alternative
stack unwinding then what it costs and do noexcept(true) functions
in call stack compile still into that rainbow table because of it?

Ian Collins

unread,
May 28, 2021, 12:51:05 AM5/28/21
to
I believe (at lease on Solaris), the "magic" is the runtime using
pthread_cleanup_push/pthread_cleanup_pop to manage the destructor
calling, there's no need for exceptions. There's nothing to stop you
doing this by hand...

There a a number of behaviours which fall between two standards and we
have to rely on the quality of the implementation, this just happens to
be one.

A simple test case:

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

struct foo
{
foo() { std::cout << "constructor" << std::endl; }

~foo() { std::cout << "destructor" << std::endl; }
};

void*
thread(void*)
{
foo f;

while (true)
{
sleep(5);
}

return nullptr;
}


int
main()
{
pthread_t t;

pthread_create(&t, 0, thread, 0);

sleep(1);
pthread_cancel(t);

pthread_join(t, 0);
}

--
Ian.

Bonita Montero

unread,
May 28, 2021, 12:53:55 AM5/28/21
to
> A simple test case:
>
> #include <pthread.h>
> #include <iostream>
> #include <unistd.h>
>
> struct foo
> {
>   foo() { std::cout << "constructor" << std::endl; }
>
>   ~foo() { std::cout << "destructor" << std::endl; }
> };
>
> void*
> thread(void*)
> {
>   foo f;
>
>   while (true)
>   {
>     sleep(5);
>   }
>
>   return nullptr;
> }
>
>
> int
> main()
> {
>   pthread_t t;
>
>   pthread_create(&t, 0, thread, 0);
>
>   sleep(1);
>   pthread_cancel(t);
>
>   pthread_join(t, 0);
> }

You have to makje the thread cancelable.

Öö Tiib

unread,
May 28, 2021, 2:07:56 AM5/28/21
to
My threads typically do some sequential work that can be time consuming
and complex (or otherwise why thread?). So I am unsure how to unit or
automatic test canceling it cheaply enough as magic goes in the
supernatural realm.

When I do by hand, set some atomic flag (that thread checks if it
should stop) and then join then I can measure, can mock that flag
checking function. It is lot easier?

Bonita Montero

unread,
May 28, 2021, 2:27:09 AM5/28/21
to
> When I do by hand, set some atomic flag (that thread checks if it
> should stop) and then join then I can measure, can mock that flag
> checking function. It is lot easier?

And if you load that flag relaxed and it is shared in several
cachelines of different cores the check is usually predicted
as false by the branch-prediction so that checking that flag
almost takes no overhead.

Juha Nieminen

unread,
May 28, 2021, 2:27:18 AM5/28/21
to
Just fuck off already, fucking asshole.

Bonita Montero

unread,
May 28, 2021, 2:31:23 AM5/28/21
to
>> Oh dear, another bad day? Have a lie down and cuddle the therapy teddy.

> Just fuck off already, fucking asshole.

According to what he's telling he must be very frustrated.

Ian Collins

unread,
May 28, 2021, 5:01:27 AM5/28/21
to
That is certainly a very common paradigm, one which my current day job
application (being cross-platform) uses exclusively. It is also one
where C++ threads provide everything you need.

Cancellation is probably uncommon in current C++ applications (I had to
go back to some of my old C++98 code to find a use case!) but it does
have its usages and if needed, it is good to know that your C++ runtime
handles object destruction correctly.

--
Ian.

Chris Vine

unread,
May 28, 2021, 7:02:11 AM5/28/21
to
All the common open source OS's (linux[1], the BSDs), and the commercial
Unixes (AIX, HPUX, Solaris) will unwind the stack on thread
cancellation in a C++ program. With linux's NPTL as implemented by
glibc this occurs by the emission of a "pseudo-exception" which you can
catch with a catch-all but which (if you do catch it) you must rethrow
in your catch block - in other words, you cannot stop cancellation by
using a catch block once it has started but you can do clean-up.

The point about thread cancellation using deferred cancellation is that
nearly all the POSIX functions which can block in the kernel, including
blocking reads and condition variable waits, are specified as
cancellation points in the SUS, so any such blocking in the kernel will
be awoken by a cancellation request (assuming cancellation is not
blocked at that particular point in the code). You cannot emulate that
in POSIX with flags and exeptions except by having a signal generate
EINTR so as to force the kernel wait in question to end in order to
check the flag, which has a number of undesirable effects of its own for
multi-threaded programs. Furthermore some POSIX blocking functions
(including pthread_cond_wait) are specified as not interruptible by
EINTR.

On a unix-like OS, there is really no problem with thread cancellation
apart from a lack of familiarity arising from the fact it does not
feature in the C++ standard and is not usuably available on the windows
platform.

[1] The former linuxthreads implementation from around 15 to 20 years
ago did not unwind the stack. Any linux distributions over the last 10
years will use glibc's NPTL implementation, which does unwind the stack.

MrSpook_9...@082ssf7_0d.edu

unread,
May 28, 2021, 7:04:06 AM5/28/21
to
On Fri, 28 May 2021 06:27:01 +0000 (UTC)
Awww, poppet, wasn't Teddy available? Never mind, maybe sucking your thumb
will help until he comes back? :)

Bonita Montero

unread,
May 28, 2021, 7:04:39 AM5/28/21
to
> Cancellation is probably uncommon in current C++ applications (I had to
> go back to some of my old C++98 code to find a use case!) but it does
> have its usages and if needed, it is good to know that your C++ runtime
> handles object destruction correctly.

Have you ever checked how pthread_cleanup_push and pthead_cleanup_pop
works ? These are macros and generate something with do / while inter-
nally. What kind of bastard does design such ugly hacks ?

MrSpo...@zp238i0_j1.net

unread,
May 28, 2021, 7:11:28 AM5/28/21
to
Do stop talking out your arse just for once FFS.

Bonita Montero

unread,
May 28, 2021, 7:15:45 AM5/28/21
to
> All the common open source OS's (linux[1], the BSDs), ...

Linux doesn't:

#include <iostream>
#include <limits>
#include <pthread.h>
#include <unistd.h>

using namespace std;

struct destr
{
~destr();
};

destr::~destr()
{
cout << "destr::~destr()" << endl;
}

int main()
{
auto thr = []( void * ) -> void *
{
cout << "thread is running" << endl;
int oldStat;
if( pthread_setcanceltype( PTHREAD_CANCEL_DEFERRED, &oldStat
) != 0
|| pthread_setcanceltype( PTHREAD_CANCEL_ENABLE, &oldStat
) != 0 )
{
cout << "can't enable cancelling" << endl;
return nullptr;
}
destr d;
sleep( numeric_limits<int>::max() );
return nullptr;
};
pthread_t pt;
if( pthread_create( &pt, nullptr, thr, nullptr ) != 0 )
{
cout << "can't create thread" << endl;
return -1;
}
if( pthread_cancel( pt ) != 0 )
{
cout << "can't cancel thread" << endl;
return -1;
}
if( pthread_join( pt, nullptr ) != 0 )
{
cout << "can't join thread" << endl;
return -1;
}
}

And I hardly doubt that this works on most Unices.

Chris Vine

unread,
May 28, 2021, 7:40:40 AM5/28/21
to
Comment out 'cout << "thread is running" << endl;' and try again. I
doubt your doubt.

Bonita Montero

unread,
May 28, 2021, 7:55:58 AM5/28/21
to
> Comment out 'cout << "thread is running" << endl;' and try again. I
> doubt your doubt.

auto thr = []( void * ) -> void *
{
// cout << "thread is running" << endl;
int oldStat;
if( pthread_setcanceltype( PTHREAD_CANCEL_DEFERRED, &oldStat ) != 0
|| pthread_setcanceltype( PTHREAD_CANCEL_ENABLE, &oldStat ) != 0 )
{
cout << "can't enable cancelling" << endl;
return nullptr;
}
destr d;
sleep( numeric_limits<int>::max() );
cout << "after sleep" << endl;
return nullptr;
};

Ok, you're right - after sleep isn't called.
But is that relly guaranteed ?

Bonita Montero

unread,
May 28, 2021, 7:58:09 AM5/28/21
to
But if you consider that commenting out the cout above makes the whole
thing working it doesn't look reliable.

Chris Vine

unread,
May 28, 2021, 9:29:13 AM5/28/21
to
No, that's nothing to do with cancellation; it's to do with threads
and static objects. If you amend your first version to issue a print
statement in the constructor instead of the destructor, that won't print
either. It appears that std::cout isn't properly available to the
worker thread when it is first launched (that is, before the sleep).

I would need to look it up but it looks as if there is an optimization
bug in g++ concerning the std::cout static object as I believe
std::cout is required to be ready when main is entered and so when the
thread is entered. As it happens there is a get-out clause for the
optimizer because your code has undefined behaviour: your code can emit
"can't cancel thread" and "can't join thread" in the main thread without
synchronization. (As to the second of those, if pthread_join fails
there is no synchronization.)

If you test with a boolean flag rather than using std::cout in the
worker thread you will see that the cancellation is working in the
expected way.

Bonita Montero

unread,
May 28, 2021, 9:48:26 AM5/28/21
to
> No, that's nothing to do with cancellation; it's to do with threads
> and static objects. If you amend your first version to issue a print
> statement in the constructor instead of the destructor, that won't print
> either. It appears that std::cout isn't properly available to the
> worker thread when it is first launched (that is, before the sleep).

It's not about cout not being workig - it works always -,
it's about cancellation not to work if I activate the cout.

Chris Vine

unread,
May 28, 2021, 10:08:59 AM5/28/21
to
OK, I have looked further at your code and I can see your problem.
It's not static objects.

Immediately pthread_create() returns you call pthread_cancel().
However you write to std::cout with "thread is running" without
setting a cancel block. The write to std::cout will operate on POSIX
primitives such as write which are cancellation points, so the worker
thread is cancelled on its first write to cout, which is not your
intention.

Before first writing to std::cout you need to set cancellation to
blocked and then unblock cancellation before you sleep. Then it will
work.

Bonita Montero

unread,
May 28, 2021, 10:16:40 AM5/28/21
to
> Immediately pthread_create() returns you call pthread_cancel().
> However you write to std::cout with "thread is running" without
> setting a cancel block. ...

The thread doesn't need to enable cancelling for a cancel-request
to be hold in the queue; a cancel-request is also enqueued if the
thread hasn't enabled cancelling yet.

> The write to std::cout will operate on POSIX primitives such as
> write which are cancellation points, so the worker thread is
> cancelled on its first write to cout, which is not your intention.

I think the thread isn't cancelled at this point since I've not
enabled cancelling then.

Chris Vine

unread,
May 28, 2021, 10:45:59 AM5/28/21
to
You are making this quite hard work. For new threads (including the
main thread), cancellation is enabled by default, and also deferred by
default. Your code making cancellation deferred can be omitted. You
must also set cancellation state with pthread_setcancelstate, not with
pthread_setcanceltype as in your code.

So omit your calls to pthread_setcanceltype which are in the first
case otiose and in the second case both otiose and incorrect.

Then add this to your worker thread as the first thing it does:

pthread_setcancelstate( PTHREAD_CANCEL_DISABLE, &oldState ) ;

And add this immediately before your call to sleep:

pthread_setcancelstate( PTHREAD_CANCEL_ENABLE, &oldState ) ;

Bonita Montero

unread,
May 28, 2021, 11:31:10 AM5/28/21
to
> You are making this quite hard work. For new threads (including the
> main thread), cancellation is enabled by default, ...

Ok, that's what I didn't consider.

Branimir Maksimovic

unread,
May 28, 2021, 1:03:28 PM5/28/21
to
I think that using flags is more practical as it less error prone ;)
>


--
current job title: senior software engineer
skills: x86 aasembler,c++,c,rust,go,nim,haskell...

press any key to continue or any other to quit...

Branimir Maksimovic

unread,
May 28, 2021, 1:07:02 PM5/28/21
to
That is POSIX thing. Very old.

Branimir Maksimovic

unread,
May 28, 2021, 1:09:11 PM5/28/21
to
On 2021-05-28, Bonita Montero <Bonita....@gmail.com> wrote:
POSIX threads is for C, when they designed it no one thought
about exceptions adn destructors ;)
Hell I don't use exceptions because of that. More hassle
then use ;)

Scott Lurndal

unread,
May 28, 2021, 1:51:54 PM5/28/21
to
Branimir Maksimovic <branimir....@gmail.com> writes:
>On 2021-05-28, Bonita Montero <Bonita....@gmail.com> wrote:
>>> Cancellation is probably uncommon in current C++ applications (I had to
>>> go back to some of my old C++98 code to find a use case!) but it does
>>> have its usages and if needed, it is good to know that your C++ runtime
>>> handles object destruction correctly.
>>
>> Have you ever checked how pthread_cleanup_push and pthead_cleanup_pop
>> works ? These are macros and generate something with do / while inter-
>> nally. What kind of bastard does design such ugly hacks ?
>That is POSIX thing. Very old.

Please don't feed the troll.

Bonita Montero

unread,
May 28, 2021, 2:00:36 PM5/28/21
to
>>>> Cancellation is probably uncommon in current C++ applications (I had to
>>>> go back to some of my old C++98 code to find a use case!) but it does
>>>> have its usages and if needed, it is good to know that your C++ runtime
>>>> handles object destruction correctly.

>>> Have you ever checked how pthread_cleanup_push and pthead_cleanup_pop
>>> works ? These are macros and generate something with do / while inter-
>>> nally. What kind of bastard does design such ugly hacks ?
>> That is POSIX thing. Very old.

> Please don't feed the troll.

Have I taken away one of your toys ?

Chris Vine

unread,
May 28, 2021, 2:54:22 PM5/28/21
to
And in case any readers think there is a race between the parent
thread's call to pthread_cancel and the worker thread's call to
pthread_setcancelstate as its first action, I should mention that there
isn't: this is because pthread_setcancelstate is not a cancellation
point. POSIX's thread cancellation, as extended for C++, is pretty well
thought through in my opinion.

Branimir Maksimovic

unread,
May 28, 2021, 5:42:22 PM5/28/21
to
I beleive she pents significant time to show her code here, so she is not
really troll ;)

Bonita Montero

unread,
May 29, 2021, 2:02:50 AM5/29/21
to
>> when closing a file-handle file data is usally written back asynchronously

> That's wrong for Windows NT, ...

CloseHandle() doesn't flush.

Bonita Montero

unread,
May 29, 2021, 2:16:49 AM5/29/21
to
If I run this ...

#include <Windows.h>
#include <iostream>
#include <cstdint>

using namespace std;

int main( int argc, char **argv )
{
if( argc < 2 )
return -1;
HANDLE hFile = CreateFileA( argv[1], GENERIC_READ | GENERIC_WRITE, 0,
nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
if( hFile == INVALID_HANDLE_VALUE )
return -1;
char buf[0x10000];
DWORD dwWritten;
for( uint64_t n = (size_t)4 * 1024 * 1024 * 1024; n; n -= sizeof buf )
if( !WriteFile( hFile, buf, sizeof buf, &dwWritten, nullptr ) )
return -1;
if( argc >= 3 )
FlushFileBuffers( hFile );
CloseHandle( hFile );
}

... without a third parameter the time is ...

real 6671.72ms
user 0.00ms
sys 1890.62ms
cycles 7.367.367.816

... and with a third parameter it is ...

real 11051.36ms
user 0.00ms
sys 2078.12ms
cycles 7.361.105.580

Öö Tiib

unread,
May 29, 2021, 4:42:28 AM5/29/21
to
On Friday, 28 May 2021 at 14:02:11 UTC+3, Chris Vine wrote:
> With linux's NPTL as implemented by
> glibc this occurs by the emission of a "pseudo-exception" which you can
> catch with a catch-all but which (if you do catch it) you must rethrow
> in your catch block - in other words, you cannot stop cancellation by
> using a catch block once it has started but you can do clean-up.

Does that pseudo exception not cause std::terminate when thrown from
noexcept function? Does it mean that implementing noexcept functions
by catching and handling everything is impossible?

> The point about thread cancellation using deferred cancellation is that
> nearly all the POSIX functions which can block in the kernel, including
> blocking reads and condition variable waits, are specified as
> cancellation points in the SUS, so any such blocking in the kernel will
> be awoken by a cancellation request (assuming cancellation is not
> blocked at that particular point in the code). You cannot emulate that
> in POSIX with flags and exeptions except by having a signal generate
> EINTR so as to force the kernel wait in question to end in order to
> check the flag, which has a number of undesirable effects of its own for
> multi-threaded programs. Furthermore some POSIX blocking functions
> (including pthread_cond_wait) are specified as not interruptible by
> EINTR.

What are the scenarios where there are no opportunities to check
the flags? The blocking functions I've used on posix have timeouts
or versions with timeouts, nonblocking options (O_NONBLOCK) or
do wake up spuriously frequently enough. But it is sure possible
that I've missed scenario as there have been only dozen or so
posix projects.

> On a unix-like OS, there is really no problem with thread cancellation
> apart from a lack of familiarity arising from the fact it does not
> feature in the C++ standard and is not usuably available on the windows
> platform.

Testing of magical, non-mockable features is really no problem?
I can't find programmers that do no defects so we need to test.

Chris Vine

unread,
May 29, 2021, 8:06:02 AM5/29/21
to
On Sat, 29 May 2021 01:42:19 -0700 (PDT)
Öö Tiib <oot...@hot.ee> wrote:
> On Friday, 28 May 2021 at 14:02:11 UTC+3, Chris Vine wrote:
> > With linux's NPTL as implemented by
> > glibc this occurs by the emission of a "pseudo-exception" which you can
> > catch with a catch-all but which (if you do catch it) you must rethrow
> > in your catch block - in other words, you cannot stop cancellation by
> > using a catch block once it has started but you can do clean-up.
>
> Does that pseudo exception not cause std::terminate when thrown from
> noexcept function? Does it mean that implementing noexcept functions
> by catching and handling everything is impossible?

If a function is cancellable, then with the glibc implementation (NPTL)
it cannot be noexcept. I don't know about other cancellation
implementations which unwind the stack. It is up to the programmer to
determine whether the function is to be cancellable or is to be
noexcept: if the function contains a POSIX cancellation point (or
applies a function containing a POSIX cancellation point), the
programmer can either allow or disallow cancellation during the
execution of the function.

Of course, the same is true if you use flags and exceptions to emulate
cancellation.

> > The point about thread cancellation using deferred cancellation is that
> > nearly all the POSIX functions which can block in the kernel, including
> > blocking reads and condition variable waits, are specified as
> > cancellation points in the SUS, so any such blocking in the kernel will
> > be awoken by a cancellation request (assuming cancellation is not
> > blocked at that particular point in the code). You cannot emulate that
> > in POSIX with flags and exeptions except by having a signal generate
> > EINTR so as to force the kernel wait in question to end in order to
> > check the flag, which has a number of undesirable effects of its own for
> > multi-threaded programs. Furthermore some POSIX blocking functions
> > (including pthread_cond_wait) are specified as not interruptible by
> > EINTR.
>
> What are the scenarios where there are no opportunities to check
> the flags? The blocking functions I've used on posix have timeouts
> or versions with timeouts, nonblocking options (O_NONBLOCK) or
> do wake up spuriously frequently enough. But it is sure possible
> that I've missed scenario as there have been only dozen or so
> posix projects.

The base case is a thread waiting for something to happen which it
turns out can no longer happen in the manner required. There are
numerous variations on this of course.

If the blocking function in question has a timeout option then yes you
could use timeouts and loop on the timeout to check a flag and throw an
exception if the flag is set. If the function in question does not
have a timeout option but has a EAGAIN option (say you have a file
descriptor set O_NONBLOCK), polling a flag in a loop is possible but
usually sub-optimal because you have to mitigate an otherwise tight loop
by use of sched_yield(), pthread_yield(), usleep() or similar in the
loop - and using usleep() is problematic because if the waiting event
occurs, you introduce variable latency into its handling. (Obviously if
you have a non-blocking file descriptor and you are selecting on the
descriptor the issue is different, but then you are not looking at
thread cancellation: instead your business is to remove the descriptor
from the selection set.) For blocking functions which return on an
interrupt (not all do), using EINTR via pthread_kill is a possibility
but then the signal in question cannot be one of a set on which
sigwait() is waiting and you cannot set SA_RESTART for it. Relying on
asynchronous signals and EINTR, and throwing an exception if a quit
flag is set, is just a poor version of what cancellation does better in
my view.

Thread cancellation is not something you need often. But when you need
it, you need it, either by emulating it in some way or employing it
directly.

> > On a unix-like OS, there is really no problem with thread cancellation
> > apart from a lack of familiarity arising from the fact it does not
> > feature in the C++ standard and is not usuably available on the windows
> > platform.
>
> Testing of magical, non-mockable features is really no problem?
> I can't find programmers that do no defects so we need to test.

Testing anything to do with the interaction of different threads with
one another is difficult, including any scheme to emulate cancellation
of one thread by another in the way you have mentioned. Thread
cancellation, if properly done, turns out to be another synchronization
exercise.

Chris Vine

unread,
May 29, 2021, 8:44:26 AM5/29/21
to
On Sat, 29 May 2021 13:05:49 +0100
Chris Vine <chris@cvine--nospam--.freeserve.co.uk> wrote:
> Thread cancellation is not something you need often. But when you need
> it, you need it, either by emulating it in some way or employing it
> directly.

By the way, since you have mentioned O_NONBLOCK as an option, I would
not expect that doing a blocking read of a file descriptor would
normally require resorting to cancellation, or some cancellation
substitute involving a non-blocking read and polling on a flag. That
is because the closing of the remote end of a pipe or socket will cause
the blocking read to return anyway. If you are in control of the
remote end of the pipe or socket you can close the remote end; if you
are not in control you are probably happy to wait anyway until the
remote end is closed. And if you are writing to a pipe or socket which
has had its remote end closed you will get SIGPIPE or EPIPE.

Öö Tiib

unread,
May 29, 2021, 9:34:26 AM5/29/21
to
But on case the remote end did neither do its operation nor close
within reasonable time-frame? Is it reasonable to assume
that the remote software was programmed by god?
Are cases when code hanging somewhere or forgetting that
it is communicating and leaking the descriptor unusual?
When should my code resort to that cancellation?

For me it is most normal, everyday case that there is some proof of
concept or worse level garbage made by startups in hope to raise
funding or something that was good before but is now maintained
by some kind of least bidder. I prefer to close my end and
to blame accordingly without that cancellation if possible.

Chris Vine

unread,
May 29, 2021, 9:48:09 AM5/29/21
to
It is undefined behaviour for a thread to close a descriptor upon which
another thread is blocking. In fact, it appears linux continues to
block in that case:
http://lkml.iu.edu/hypermail/linux/kernel/0106.0/0768.html

If that is your scenario and you don't want to use cancellation I would
resort to asynchronous (non-blocking) i/o and use poll() or select()
even if you don't otherwise need it. You could of course put a timeout
on the poll() or select() as well.

Öö Tiib

unread,
May 29, 2021, 9:51:57 AM5/29/21
to
On Saturday, 29 May 2021 at 15:06:02 UTC+3, Chris Vine wrote:
> If the function in question does not
> have a timeout option but has a EAGAIN option (say you have a file
> descriptor set O_NONBLOCK), polling a flag in a loop is possible but
> usually sub-optimal because you have to mitigate an otherwise tight loop
> by use of sched_yield(), pthread_yield(), usleep() or similar in the
> loop - and using usleep() is problematic because if the waiting event
> occurs, you introduce variable latency into its handling.

Why simply not to ask about 2000-3000$ per hour in project where work
may not have few ms latency but where killing the whole thread doing
it without killing the process itself is allowable? There are lot of more
interesting and fruitful work to do.

Öö Tiib

unread,
May 29, 2021, 10:59:13 AM5/29/21
to
Yes, I repeat "The blocking functions I've used on posix have timeouts
or versions with timeouts, nonblocking options (O_NONBLOCK) or
do wake up spuriously frequently enough." I have met no reason to use
anything else and was asking what is the scenario that does not let my
thread to check frequently enough if the work it is doing or the whole
thread has became obsolete and so should wrap it up.

Also I can mock that flag checking function and just make it to tell that
now it is time to wrap up on 6987th check of flags. How to do same
with cancellation?


Chris Vine

unread,
May 29, 2021, 1:15:56 PM5/29/21
to
On Sat, 29 May 2021 07:59:02 -0700 (PDT)
I am not certain what you mean by "wrap up on", but you can instrument
a cancellation by including a catch-all in your checking function which
logs that cancellation has begun and such other state as is available
to it to record, and then rethrows. One happy outcome of using POSIX
functions is that the only exception-like thing they can emit is a
cancellation pseudo-exception. But perhaps you meant something else.

As to usage I have used cancellation with a thread blocking on
accept(). It would have been possible to make the socket descriptor
non-blocking and block on select() after adding the socket to the set of
read descriptors (when a non-blocking accept receives a connection
select() will signal it as ready for reading), and polled a flag on a
select timeout, but just cancelling it proved much easier and more
obvious. I can recall using it to kill a thread waiting on
pthread_join() but I cannot now remember the reasons why.

Öö Tiib

unread,
May 29, 2021, 10:03:18 PM5/29/21
to
Sorry for my bad English. I meant the thread to complete its work early or
to cancel it. The flags are for that purpose. I check those in known places
and so repeating same test the total count of such checks is same from
run to run.

> One happy outcome of using POSIX
> functions is that the only exception-like thing they can emit is a
> cancellation pseudo-exception. But perhaps you meant something else.

So you suggest I can mock the POSIX functions to work like always but
then throw sometimes something unusual for testing? It is plan but feels
like quite lot of work compared to mocking the flag checking.

> As to usage I have used cancellation with a thread blocking on
> accept(). It would have been possible to make the socket descriptor
> non-blocking and block on select() after adding the socket to the set of
> read descriptors (when a non-blocking accept receives a connection
> select() will signal it as ready for reading), and polled a flag on a
> select timeout, but just cancelling it proved much easier and more
> obvious.

OK, but how it is easier and more obvious? Indicating with flags and
letting the running work to decide itself where and how to complete
early feels most obvious split of responsibilities. Otherwise the
canceling thread has to know and monitor the work progress details
of threads that it can potentially cancel. That feels fragile and risky.

> I can recall using it to kill a thread waiting on
> pthread_join() but I cannot now remember the reasons why.

It can be the thread it was joining had gone insane and hung.
I prefer to abort whole process or power-cycle devices on such
cases if possible. That guarantees the programming errors are
fixed quickest and so insane processes and trashy devices do
least damage to customers. But I've met it more on Windows
where some closed source garbage I'm forced to use does
hang.

Chris Vine

unread,
May 30, 2021, 6:48:55 AM5/30/21
to
On Sat, 29 May 2021 19:03:08 -0700 (PDT)
Öö Tiib <oot...@hot.ee> wrote:
> On Saturday, 29 May 2021 at 20:15:56 UTC+3, Chris Vine wrote:
> > On Sat, 29 May 2021 07:59:02 -0700 (PDT)
> > Öö Tiib <oot...@hot.ee> wrote:
[snip]
> > > Also I can mock that flag checking function and just make it to tell that
> > > now it is time to wrap up on 6987th check of flags. How to do same
> > > with cancellation?
> >
> > I am not certain what you mean by "wrap up on", but you can instrument
> > a cancellation by including a catch-all in your checking function which
> > logs that cancellation has begun and such other state as is available
> > to it to record, and then rethrows.
>
> Sorry for my bad English. I meant the thread to complete its work early or
> to cancel it. The flags are for that purpose. I check those in known places
> and so repeating same test the total count of such checks is same from
> run to run.
>
> > One happy outcome of using POSIX
> > functions is that the only exception-like thing they can emit is a
> > cancellation pseudo-exception. But perhaps you meant something else.
>
> So you suggest I can mock the POSIX functions to work like always but
> then throw sometimes something unusual for testing? It is plan but feels
> like quite lot of work compared to mocking the flag checking.

You want to know when and how many times a cancellation "request" (ie
flag change) has been made in respect of a mocked version of a blocking
function (that is, a function blocking on some event and/or a quit flag
request), possibly without carrying out any cancellation/quitting? The
"possibly without carrying out any cancellation/quitting" makes such
mocking unfeasible with thread cancellation, since once cancellation
has started you can catch and rethrow it (and instrument and count that)
but you cannot stop it. For that you would have to mock the function
which applies pthread_cancel instead.

> > As to usage I have used cancellation with a thread blocking on
> > accept(). It would have been possible to make the socket descriptor
> > non-blocking and block on select() after adding the socket to the set of
> > read descriptors (when a non-blocking accept receives a connection
> > select() will signal it as ready for reading), and polled a flag on a
> > select timeout, but just cancelling it proved much easier and more
> > obvious.
>
> OK, but how it is easier and more obvious? Indicating with flags and
> letting the running work to decide itself where and how to complete
> early feels most obvious split of responsibilities. Otherwise the
> canceling thread has to know and monitor the work progress details
> of threads that it can potentially cancel. That feels fragile and risky.

The cancelling thread doesn't need to know any of the work details of
the thread which is accepting connections. With deferred cancellation
the accepting thread is master of its own cancellation and (in the case
I have in mind) allows cancellation only when applying accept(), so
disallowing cancellation whenever accept() returns with a new
connection. Once a new connection occurs it completes the
establishment of the connection and hands off the new connection socket
for another thread to deal with in the normal way, during which time it
is uncancellable. When it has completed the hand off unhindered it
loops back to accept and makes itself cancellable again, and so on. It
would work the same way as having the accepting thread checking a flag
via a timeout and killing itself with an exception or in some other
way, but with much less faff.

The simple scheme I have described works on the basis that you may want
to terminate the accepting thread but not any thread(s) still dealing
with previously established connections. That may not always be what
you want but since every thread is master of its own cancellation, you
can arrange for the termination of other threads to occur in any way you
want, and (at the points where cancellation is allowed) you can save
work, clean-up, log etc. in an appropriate catch-all block which
rethrows when it has done the saving and clean-up. If the thread(s)
handling previously established connections are doing so asynchronously
via an event loop, you probably wouldn't use cancellation at all: you
would bring the relevant event loop(s) maintained by those threads to
an end. (That wasn't the position in the case I dealt with, but I can
easily imagine it could be.)

Öö Tiib

unread,
May 30, 2021, 12:35:11 PM5/30/21
to
Maybe. Idiomatic case is that we want a work to end early as it has
become obsolete. Other idiomatic case is that we want thread to stop
early as whatever work it can possibly do has became obsolete.
With flags we can indicate that such or other case is now and then
have ending the work or stopping the thread early.
Also we want to test everything. By mocking flag checking
functions we can test it with granularity of one check. If only sole work
or whole thread is stopped early does not matter to the question as
we are testing outcome of such abrupt stop. If it didn't stop then we
have defect.

> The
> "possibly without carrying out any cancellation/quitting" makes such
> mocking unfeasible with thread cancellation, since once cancellation
> has started you can catch and rethrow it (and instrument and count that)
> but you cannot stop it. For that you would have to mock the function
> which applies pthread_cancel instead.

So we have to have both flags and cancellation in place when same
thread is reused for different works? Or maybe flags and then if flags
do not work then cancellation as wheelchair to defective program?
Sounds like some kind of master of itself suicide pattern. It is very
interesting as I've never needed that thing. What happens when it kills
itself? Does it signal someone to join it?

> The simple scheme I have described works on the basis that you may want
> to terminate the accepting thread but not any thread(s) still dealing
> with previously established connections. That may not always be what
> you want but since every thread is master of its own cancellation, you
> can arrange for the termination of other threads to occur in any way you
> want, and (at the points where cancellation is allowed) you can save
> work, clean-up, log etc. in an appropriate catch-all block which
> rethrows when it has done the saving and clean-up.

Ok so I can have the dead corpse of responsible removed. How
to try to figure what happened?

> If the thread(s)
> handling previously established connections are doing so asynchronously
> via an event loop, you probably wouldn't use cancellation at all: you
> would bring the relevant event loop(s) maintained by those threads to
> an end. (That wasn't the position in the case I dealt with, but I can
> easily imagine it could be.)

Threads are doing anything that can take time, anything that can take
time may become obsolete before it is completed. It can be let
to run to end and then obsolete product discarded or it may be stopped
early as performance optimization. Network communication is usually
behind bottle neck of available network cards and as our processors are
typically tremendously quicker just one thread can easily handle all
communication going through one network card.
0 new messages