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

TSD key reusing issue

5 views
Skip to first unread message

Maxim Konovalov

unread,
Feb 20, 2002, 3:26:22 PM2/20/02
to

Hello,

I wrote a simple testcase which creates a TSD key, sets a TSD value,
deletes the key, re-creates the same key and checks the TSD value. I
got the different results on the different platforms:

Compaq Tru64 Unix 5.1 doesn't clear TSD value when re-creates the key
FreeBSD 4.5-STABLE ditto

RedHat 7.2 clears TSD value when re-creates the key
AIX 4.3.2.0 ditto
IRIX 6.5 ditto

Solaris 8 does not allow to reuse the key at all
HP/UX 10.20, libdce has no pthread_key_delete nor pthread_keydelete

David R. Butenhof, "Programming with POSIX Threads", p.166:

"When an existing thread that had set a value for the old key requests
the value of the new key, it will receive the old value".

SUSv3, http://www.opengroup.org/onlinepubs/007904975/functions/pthread_key_creat
e.html:

"Upon key creation, the value NULL shall be associated with the new
key in all active threads. Upon thread creation, the value NULL shall
be associated with all defined keys in the new thread."

SUSv3 says about new key, but it is not clear is reused key new or
not. So the questions are:

Which behaviour is more correct from standards point of view?
Or is it just an implementation details?

Thanks in advance,

- -maxim

Kaz Kylheku

unread,
Feb 20, 2002, 4:27:40 PM2/20/02
to
In article <20020220231643...@news1.macomnet.ru>, Maxim

Konovalov wrote:
>
>Hello,
>
>I wrote a simple testcase which creates a TSD key, sets a TSD value,
>deletes the key, re-creates the same key and checks the TSD value. I
>got the different results on the different platforms:

Strictly speaking, you cannot ``recreate the same key''. The
pthread_key_create function is an abstraction that allocates a new key. Of
course, implementations reuse the space liberated by keys that were freed,
so that they don't leak resources.

>Compaq Tru64 Unix 5.1 doesn't clear TSD value when re-creates the key
>FreeBSD 4.5-STABLE ditto

That is broken. When you create a key, it should have a NULL value in
all threads.

>RedHat 7.2 clears TSD value when re-creates the key

Ha, but the pthread_key_delete function is horribly broken. I recently fixed
it, but I think that was after Red Hat 7.2 shipped. Perhaps they have
a library upgrade RPM that has picked up the fix.

In order to clear out the slots of the deleted key, the function
was wrongfully walking over data structures which are private to the
LinuxThreads manager thread, and are not protected. This gave rise to
a race condition that could lead to a segmentation violation.

If you are shipping code for RedHat 7.2 and older, I would advise against
calling pthread_key_delete.

--
Meta-CVS: version control with directory structure versioning over top of CVS.
http://users.footprints.net/~kaz/mcvs.html

Maxim Konovalov

unread,
Feb 21, 2002, 6:21:42 AM2/21/02
to

Hello Kaz,

On 21:27-0000, Feb 20, 2002, Kaz Kylheku wrote:

> In article <20020220231643...@news1.macomnet.ru>, Maxim
> Konovalov wrote:
> >
> >Hello,
> >
> >I wrote a simple testcase which creates a TSD key, sets a TSD value,
> >deletes the key, re-creates the same key and checks the TSD value. I
> >got the different results on the different platforms:
>
> Strictly speaking, you cannot ``recreate the same key''. The
> pthread_key_create function is an abstraction that allocates a new key. Of
> course, implementations reuse the space liberated by keys that were freed,
> so that they don't leak resources.
>
> >Compaq Tru64 Unix 5.1 doesn't clear TSD value when re-creates the key
> >FreeBSD 4.5-STABLE ditto
>
> That is broken. When you create a key, it should have a NULL value in
> all threads.

Does it mean David Butenhof was wrong? David, could you please comment?

[...]

--
Maxim Konovalov, MAcomnet, Internet-Intranet Dept., system engineer
phone: +7 (095) 796-9079, mailto:ma...@macomnet.ru

Alexander Terekhov

unread,
Feb 21, 2002, 7:39:32 AM2/21/02
to

Maxim Konovalov wrote:
[...]

> > >Compaq Tru64 Unix 5.1 doesn't clear TSD value when re-creates the key
> > >FreeBSD 4.5-STABLE ditto
> >
> > That is broken. When you create a key, it should have a NULL value in
> > all threads.
>
> Does it mean David Butenhof was wrong?

Or perhaps it is just your test program wrong?

http://www.testdrive.compaq.com

Compaq Tru64 Unix 5.1 DS20 2@500MHz (ev6) telnet to <...>

----

spe156.testdrive.compaq.com> ./tsd
th.cre 0
create 0
set 0
get 1400003c0
get[1] 0
set[1] 0
get[1] 20000f15ac0
delete 0
create 0
get 0
get[1] 0
set[1] 0
get[1] 20000f15ac0
delete 0

spe156.testdrive.compaq.com> cat tsd.c

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

pthread_key_t key;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t condvar = PTHREAD_COND_INITIALIZER;
int flag = 0;

void* another_thread( void* p )
{
pthread_mutex_lock( &mutex );

for ( ;; ) {

while ( 0 == flag )
pthread_cond_wait( &condvar,&mutex );

printf( "get[1] %p\n",p = pthread_getspecific( key ) );

if ( NULL == p )
printf( "set[1] %d\n",pthread_setspecific( key,&p ) );

printf( "get[1] %p\n",p = pthread_getspecific( key ) );

flag = 0;

pthread_cond_signal( &condvar );

}

}

void test_another_thread()
{
flag = 1;
pthread_cond_signal( &condvar );
while ( 1 == flag )
pthread_cond_wait( &condvar,&mutex );
}

int main ()
{
pthread_t tid;
pthread_mutex_lock( &mutex );
printf( "th.cre %d\n",pthread_create( &tid,NULL,&another_thread,NULL )
);
printf( "create %d\n",pthread_key_create( &key,NULL ) );
printf( "set %d\n",pthread_setspecific( key,&key ) );
printf( "get %p\n",pthread_getspecific( key ) );
test_another_thread();
printf( "delete %d\n",pthread_key_delete( key ) );
printf( "create %d\n",pthread_key_create( &key,NULL ) );
printf( "get %p\n",pthread_getspecific( key ) );
test_another_thread();
printf( "delete %d\n",pthread_key_delete( key ) );
pthread_mutex_unlock( &mutex );

return 0;
}

regards,
alexander.

Maxim Konovalov

unread,
Feb 21, 2002, 8:13:50 AM2/21/02
to
On 13:39+0100, Feb 21, 2002, Alexander Terekhov wrote:

>
> Maxim Konovalov wrote:
> [...]
> > > >Compaq Tru64 Unix 5.1 doesn't clear TSD value when re-creates the key
> > > >FreeBSD 4.5-STABLE ditto
> > >
> > > That is broken. When you create a key, it should have a NULL value in
> > > all threads.
> >
> > Does it mean David Butenhof was wrong?
>
> Or perhaps it is just your test program wrong?

Please take a look at my code:
http://news1.macomnet.ru/~maxim/stuff/specific.c

But from my previous letter:

David R. Butenhof, "Programming with POSIX Threads", p.166, about key
reusing:

"When an existing thread that had set a value for the old key requests
the value of the new key, it will receive the old value".

Do I miss something?

--

David Butenhof

unread,
Feb 21, 2002, 9:06:17 AM2/21/02
to
Maxim Konovalov wrote:

> I wrote a simple testcase which creates a TSD key, sets a TSD value,
> deletes the key, re-creates the same key and checks the TSD value. I
> got the different results on the different platforms:
>
> Compaq Tru64 Unix 5.1 doesn't clear TSD value when re-creates the key
> FreeBSD 4.5-STABLE ditto

Ignores a clearly invalid requirement of the standard in favor of
fulfulling the implied promise of pthread_key_delete without an
implicit/asynchronous memory leak. A compromise (deliberate at least in my
case) to do something meaningful with a flawed standard. Wrong? Maybe. I
wouldn't do it this way again, anyway.

> RedHat 7.2 clears TSD value when re-creates the key
> AIX 4.3.2.0 ditto
> IRIX 6.5 ditto

SERIOUS BUG. That is, this is either "a conforming memory leak" or "a
standard violation", depending on whether they illegally run destructors
before asynchronously clearing the threads' TSD values. Either way, bad
medicine.

> Solaris 8 does not allow to reuse the key at all

Good for them. If I was to "do it again", that's what I'd do, too.

> HP/UX 10.20, libdce has no pthread_key_delete nor pthread_keydelete

That's because HP-UX 10.20 doesn't have POSIX threads. It's completely
irrelevant in this context.

> David R. Butenhof, "Programming with POSIX Threads", p.166:
>
> "When an existing thread that had set a value for the old key requests
> the value of the new key, it will receive the old value".

I never intended to restate the standard. I'm explaining and giving
practical advice. Obviously that advice is based on MY experience and
interpretations. I'm not infalliable, but I've got a pretty good record so
far. ;-)

> SUSv3,
> http://www.opengroup.org/onlinepubs/007904975/functions/pthread_key_creat
> e.html:
>
> "Upon key creation, the value NULL shall be associated with the new
> key in all active threads. Upon thread creation, the value NULL shall
> be associated with all defined keys in the new thread."

This is the wording of the standard. The standard "says what it says", and
conforming implementations are bound to implement what it says. However
ridiculous and unsupportable that might be. That doesn't mean it's RIGHT.
Conformance is a good goal. Blind conformance to errors is silly.

> SUSv3 says about new key, but it is not clear is reused key new or
> not. So the questions are:
>
> Which behaviour is more correct from standards point of view?
> Or is it just an implementation details?

The basic truth here is that pthread_key_delete() was added late, and
wasn't thoroughly integrated into the standard. The function was well
intended, but poorly conceived and incompletely defined.

It doesn't run destructors in all threads, because the synchronization
would be a ridiculous mess. Having failed to run destructors, though, it
would be dangerous (at the very least, a memory leak in all threads with
active values) to "throw out" the existing TSD values.

The phrase about key values being null on key creation predates
pthread_key_delete() and wasn't reconsidered. It makes no sense unless
deletion were to run destructors, and it doesn't. (You can't even legally
"extend" delete to do so, since the standard clearly requires that they NOT
be run.)

So, clearing pointers without running destructors is a major error. The
standard does not intend to say this, and if it did it'd be wrong. On the
other hand, running destructors on pthread_key_delete() violates the
standard. So one can say that that the standard is broken, and nobody's
ever bothered to try to resolve the contradiction. (I admit to having
thought about this once or twice and not doing anything about it.)

The standard does say that cleanup is the application's responsibility. I
believe (or at least, I chose to accept when I wrote my code!) that the
most correct/portable/useful interpretation is that the application has
responsibility for implementing the clause suggesting that a new key
created over a previously deleted key have the value NULL; it's the only
party capable of doing that. That is, it must ensure that all threads
holding values for the TSD key have freed applicable resources and set the
key value to NULL before deleting the key.

In fact, there's no wording to even IMPLY that a deleted key MIGHT be
available for re-creation. We simply choose to infer that this must be the
case since there's otherwise no purpose to having pthread_key_delete.
(Well, OK, *I* happen to know for sure that this was the INTENT, but that
doesn't count.) In fact, in combination, "doesn't run destructors" and "the
initial value is NULL" clearly require that a deleted key CANNOT be
re-used, and pthread_key_delete() must do nothing other than mark the key
"invalid".

Like pthread_atfork(), pthread_key_delete() is an implied promise on which
the standard as it currently exists cannot deliver. It's at best an
inconsistency, but there are tough issues to be dealt with in fixing it,
and I don't think that's likely to happen any time soon. In summary, I
guess I'd have to say that my current opinion is that Solaris is the only
one that got this right. I actually started out with a smiley face at the
end... but after rereading a few time, I removed it. There's really no
other supportable interpretation of the current contradiction in the
standard. (Which leaves me with the question of whether to fix my
implementation and risk new failures in applications, if any, that delete
keys, aren't affected by retaining the old values, and would run out of
keys otherwise. Honestly, I doubt that pthread_key_delete is widely used...
but I have no way to verify that assumption.)

/------------------[ David.B...@compaq.com ]------------------\
| Compaq Computer Corporation POSIX Thread Architect |
| My book: http://www.awl.com/cseng/titles/0-201-63392-2/ |
\-----[ http://home.earthlink.net/~anneart/family/dave.html ]-----/

Maxim Konovalov

unread,
Feb 21, 2002, 10:11:20 AM2/21/02
to

Thank you very much David!

Alexander Terekhov

unread,
Feb 21, 2002, 10:36:58 AM2/21/02
to

David Butenhof wrote:
[...]

> The standard does say that cleanup is the application's responsibility.

spe156.testdrive.compaq.com> ./tsd3
Cleanup *DONE*...key deleted!
Assertion failed: NULL == pthread_getspecific(*pkey), file tsd3.c, line
23
Abort process (core dumped)
spe156.testdrive.compaq.com> cat tsd3.c
#include <assert.h>
#include <pthread.h>
#include <stdio.h>

int
main(void)
{
pthread_key_t key, *pkey;
void* p = (void*)malloc( 1 );

assert(pthread_key_create(&key, NULL) == 0);
assert(pthread_setspecific(key,p) == 0);
assert( NULL != (p = pthread_getspecific(key)) );
free( p );
assert(pthread_key_delete(key) == 0);
printf("Cleanup *DONE*...key deleted!\n");

for ( ;; ) {

printf(".");
assert( NULL != (pkey =
(pthread_key_t*)malloc(sizeof(pthread_key_t))) );
assert(pthread_key_create(pkey, NULL) == 0);
assert( NULL == pthread_getspecific(*pkey) );

}

return 0;
}

regards,
alexander.

Kaz Kylheku

unread,
Feb 21, 2002, 12:49:11 PM2/21/02
to
In article <u97d8.29$fL6...@news.cpqcorp.net>, David Butenhof wrote:
>Maxim Konovalov wrote:
>
>> I wrote a simple testcase which creates a TSD key, sets a TSD value,
>> deletes the key, re-creates the same key and checks the TSD value. I
>> got the different results on the different platforms:
>>
>> Compaq Tru64 Unix 5.1 doesn't clear TSD value when re-creates the key
>> FreeBSD 4.5-STABLE ditto
>
>Ignores a clearly invalid requirement of the standard in favor of
>fulfulling the implied promise of pthread_key_delete without an
>implicit/asynchronous memory leak. A compromise (deliberate at least in my
>case) to do something meaningful with a flawed standard. Wrong? Maybe. I
>wouldn't do it this way again, anyway.
>
>> RedHat 7.2 clears TSD value when re-creates the key
>> AIX 4.3.2.0 ditto
>> IRIX 6.5 ditto
>
>SERIOUS BUG. That is, this is either "a conforming memory leak" or "a
>standard violation", depending on whether they illegally run destructors
>before asynchronously clearing the threads' TSD values. Either way, bad
>medicine.
>
>> Solaris 8 does not allow to reuse the key at all
>
>Good for them. If I was to "do it again", that's what I'd do, too.

That is impractical if you are loading an unloading program components
dynamically. Say you load some multi-threaded gizmo packaged as a shared
library. When that thing is initialized, it may want some thread-specific
keys. Then when it's to be unloaded, it will liberate those keys. If
you don't allow reuse, the keys will be exhausted.

Destroying a key is not any different from destroying any other shared object.
Of course you have to ensure that no thread is using that key any longer.
Since threads can no longer use that key, there is nothing wrong with
clearing out the slots.

Of course, a program which wants to destroy a key has to not only ensure
that no threads will ever try to use that key again, but also that
any resources associated with those keys are cleaned up too, since
the pthread_key_t registered destructors won't be run.

>> SUSv3,
>> http://www.opengroup.org/onlinepubs/007904975/functions/pthread_key_creat
>> e.html:
>>
>> "Upon key creation, the value NULL shall be associated with the new
>> key in all active threads. Upon thread creation, the value NULL shall
>> be associated with all defined keys in the new thread."
>
>This is the wording of the standard. The standard "says what it says", and
>conforming implementations are bound to implement what it says. However
>ridiculous and unsupportable that might be. That doesn't mean it's RIGHT.
>Conformance is a good goal. Blind conformance to errors is silly.

This behavior supports what I believe is a common programming pattern. If
the slot is not null, then a thread has no way to know whether it contains
a value that it has previously stored there, or whether it is garbage.

Think about a program module which needs to associate context data with
foreign threads, i.e. threads whose creation and setup it does not control.
A thread somes in out of the blue; you want to know whether that thread
has been there before, to see whether you can reuse a cached context or
whether you have to allocate a new one. This is easy: do a thread specific
key lookup. If it is null, you don't have a context, so make one and
store it there. If thread specific slots can contain indeterminate
garbage, this logic is impossible.

>The basic truth here is that pthread_key_delete() was added late, and
>wasn't thoroughly integrated into the standard. The function was well
>intended, but poorly conceived and incompletely defined.

I agree that pthread_key_delete is completely useless in programs
that don't load and unload components.

Thread-specific storage is a lot like dynamically-scoped variables
(called ``special variables'') in Lisp. In fact in Lisp implementations
that have threads, special variables have thread-specific dynamic
bindings. The difference is that the lookups through the dynamic binding
is implicit in the variable reference, and that dynamic bindings are
automatically saved and restored, something that can be done with
wrappers around pthread_key_*(). It's not hard to imagine a special
variable implementation layered on top of these functions.

Now it rarely makes sense to delete a global variable. It also rarely
makes sense to dynamically generate anonymous global variables.

This is where the design of pthread_key_create falters. The problem
with it is that it manufactures a new key each time it is called, with
no possibility of interning. If pthread_key_create performed interning,
then pthread_key_delete would truly be useless, even in programs
that dynamically load and unload modules.

By interning, I mean that there should be some parameter by which
the caller of pthread_key_create specifies some symbol, such that if it is
called more than once with the same symbol, it will return the same key.

Thus if the same program component is loaded and unloaded repeatedly,
it will get the same key each time, essentially by asking for it by name.
The name could be some string, which programmers could choose in such a
way that a clash is vanishingly unlikely.

Then it will make sense to keep the slot data, since it is associated
with that unique name. You could unload the module, load it later, call
into it with the same old threads, who will find the correct slots,
with intact values. Of course, the module implementors could still choose
to clean out those slots before unloading, and then set them to null.

>It doesn't run destructors in all threads, because the synchronization
>would be a ridiculous mess.

It's impossible to do, because it would require cooperation from the app.
It's the programmer's responsibility to make sure that the attached
resources are cleaned up, before the key is destroyed. Those resources
can be in some global list maintained by the module that is being
cleaned up.

>Having failed to run destructors, though, it
>would be dangerous (at the very least, a memory leak in all threads with
>active values) to "throw out" the existing TSD values.

But threads have no way to get to those values, because the key is
destroyed. There may still be copies of that key, but it's wrong to use
it, because it's a dead object. Clearing out values that are not
legally accessible to the program is innocuous.

>The standard does say that cleanup is the application's responsibility. I

It also does not say that it's *not* the application's reponsibility. ;)

Readers of the standard cannot afford to assume that their
responsibilities are limited to that which is explicitly spelled out.

Alexander Terekhov

unread,
Feb 21, 2002, 1:48:06 PM2/21/02
to

Kaz Kylheku wrote:
[...]

> Destroying a key is not any different from destroying any other shared object.
> Of course you have to ensure that no thread is using that key any longer.
> Since threads can no longer use that key, there is nothing wrong with
> clearing out the slots.

Here's my opinion (I totally agree with Kaz):

http://groups.google.com/groups?as_umsgid=3B855B73.C2DE7A53%40web.de
http://groups.google.com/groups?as_umsgid=3B8654EB.9179FE4B%40web.de

regards,
alexander.

David Butenhof

unread,
Feb 22, 2002, 7:47:20 AM2/22/02
to
Alexander Terekhov wrote:

> Maxim Konovalov wrote:
> [...]
>> > >Compaq Tru64 Unix 5.1 doesn't clear TSD value when re-creates
>> > >the key
>> > >FreeBSD 4.5-STABLE ditto
>> >
>> > That is broken. When you create a key, it should have a NULL value in
>> > all threads.
>>
>> Does it mean David Butenhof was wrong?
>
> Or perhaps it is just your test program wrong?
>
> http://www.testdrive.compaq.com
>
> Compaq Tru64 Unix 5.1 DS20 2@500MHz (ev6) telnet to <...>

The difference in your results reveals a bit of how I implemented reuse of
keys. Your program simply creates a key, deletes it, and creates a new key.
What makes you think you got the same key value reused?

Maxim's program, on the other hand, imagines that it knows how to interpret
the pthread_key_t value (hey, it violates the standard but it's really just
an integer index so he's effectively correct), and LOOPS until a call to
pthread_key_create() returns the original key value once more.

So BOTH program results are correct; you just "didn't try hard enough".
What this means is that we don't scan for deleted key values until we've
gone through the entire range once. Which means that at least you won't run
into the problem of stale TSD values until you've exhausted all possible
keys.

David Butenhof

unread,
Feb 22, 2002, 9:00:07 AM2/22/02
to
Kaz Kylheku wrote:

Certainly, such support was indeed the motivation behind
pthread_key_delete(). So? That's not the same as saying that the promise
and intent were achieved. You can't actually do this because of the loose
ends. I don't believe I said anything that could reasonably be taken to
imply that I liked it that way. The standard simply provides no solution to
the difficulty.

> Destroying a key is not any different from destroying any other shared
> object. Of course you have to ensure that no thread is using that key any
> longer. Since threads can no longer use that key, there is nothing wrong
> with clearing out the slots.

Yes, and one solution would be to change the standard to require that the
application destroy all threads' values for a key before deleting the key.
This could be represented by an optionally detected EBUSY, for example.

If the thread calling pthread_key_delete (or pthread_key_create on reuse)
were to asynchronously clear all threads' key values, without
synchronization on every pthread_set/getspecific call, on an SMP some
threads might still see the "pre-cleared" TSD value on their next
pthread_getspecific() anyway.

> Of course, a program which wants to destroy a key has to not only ensure
> that no threads will ever try to use that key again, but also that
> any resources associated with those keys are cleaned up too, since
> the pthread_key_t registered destructors won't be run.

Right. And the implementation can't (certainly shouldn't) help, including
clearing TSD values, because that substantially increases the cost of ALL
TSD access (by adding synchronization where none is otherwise needed, and
where the standard definitely did not intend to require synchronization).

>>> SUSv3,
>>>
http://www.opengroup.org/onlinepubs/007904975/functions/pthread_key_creat
>>> e.html:
>>>
>>> "Upon key creation, the value NULL shall be associated with the new
>>> key in all active threads. Upon thread creation, the value NULL shall
>>> be associated with all defined keys in the new thread."
>>
>>This is the wording of the standard. The standard "says what it says", and
>>conforming implementations are bound to implement what it says. However
>>ridiculous and unsupportable that might be. That doesn't mean it's RIGHT.
>>Conformance is a good goal. Blind conformance to errors is silly.
>
> This behavior supports what I believe is a common programming pattern. If
> the slot is not null, then a thread has no way to know whether it contains
> a value that it has previously stored there, or whether it is garbage.

Absolutely. But the responsibility for setting the TSD value to NULL can
only belong with the application; because only it can know when the
resources it represents (if any) have been released.

Ha. The only way you can really do that is with a central registry.
(Replace "vanishingly" by "somewhat", and maybe you're OK... if that's
really good enough to satisfy you. ;-) )

> Then it will make sense to keep the slot data, since it is associated
> with that unique name. You could unload the module, load it later, call
> into it with the same old threads, who will find the correct slots,
> with intact values. Of course, the module implementors could still choose
> to clean out those slots before unloading, and then set them to null.

That is the "key"... the APPLICATION must be responsible for freeing
resources and setting the key value to NULL. Nobody else can do it.

>>It doesn't run destructors in all threads, because the synchronization
>>would be a ridiculous mess.
>
> It's impossible to do, because it would require cooperation from the app.
> It's the programmer's responsibility to make sure that the attached
> resources are cleaned up, before the key is destroyed. Those resources
> can be in some global list maintained by the module that is being
> cleaned up.

Yes, it "could be". But once you require each TSD-using facility to
maintain a global synchronized list of TSD data, you've superceded the
entire TSD mechanism. Because TSD access itself must also be synchronized,
you're getting no benefit at all from the standard functions.

I agree that there's no mechanism to clear the TSD value in threads that
weren't created by the facility that owns the TSD key being destroyed. But
that's not the point. Perhaps adding such a mechanism would be one possible
solution. (I've experimented with that. My thread library has a new type of
TSD key that supports constructor routines as well as destructor routines,
and allows a flag that causes the routines to be run on behalf of all
"live" threads in the context of the thread that's creating or deleting the
key. However there are serious synchronization issues here that have caused
us to keep this mechanism undocumented, and restricted to one particular
use: compiler "Thread Local Storage".)

>>Having failed to run destructors, though, it
>>would be dangerous (at the very least, a memory leak in all threads with
>>active values) to "throw out" the existing TSD values.
>
> But threads have no way to get to those values, because the key is
> destroyed. There may still be copies of that key, but it's wrong to use
> it, because it's a dead object. Clearing out values that are not
> legally accessible to the program is innocuous.

You still can't make it work without synchronizing every TSD access. The
standard did NOT intend to require synchronization. The performance impact
is NOT innocuous, and it was not the intent of the working group.

>>The standard does say that cleanup is the application's responsibility. I
>
> It also does not say that it's *not* the application's reponsibility. ;)
>
> Readers of the standard cannot afford to assume that their
> responsibilities are limited to that which is explicitly spelled out.

Depends on what's spelled out and how. In this case, it clearly SHOULD BE
(and MUST BE) the application's responsibility, but the standard
incorrectly requires behavior of the implementation that contradicts one of
the most basic architectural intents of TSD. It is, inherently, "thread
private" data that shouldn't require synchronization. The standard as
currently written requires synchronization, and that's an error.

Alexander Terekhov

unread,
Feb 22, 2002, 10:42:16 AM2/22/02
to

David Butenhof wrote:
[...]

> So BOTH program results are correct; you just "didn't try hard enough".

I wrote a THIRD[1] program that did it, I believe.

And, yes, I would have NO problems with a requirement to
"manually" set TSD values to NULL in ALL threads prior to
key destruction... BUT please spell this out *clearly*
in the STANDARD[2] then!

regards,
alexander.

[1] I think that it is FULLY conforming,
please correct me if I am wrong:

http://groups.google.com/groups?as_umsgid=3C75141A.942FD709%40web.de

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

int
main(void)
{
pthread_key_t key, *pkey;
void* p = (void*)malloc( 1 );

assert(pthread_key_create(&key, NULL) == 0);
assert(pthread_setspecific(key,p) == 0);
assert( NULL != (p = pthread_getspecific(key)) );
free( p );
assert(pthread_key_delete(key) == 0);
printf("Cleanup *DONE*...key deleted!\n");

for ( ;; ) {

printf(".");
assert( NULL != (pkey =
(pthread_key_t*)malloc(sizeof(pthread_key_t))) );
assert(pthread_key_create(pkey, NULL) == 0);
assert( NULL == pthread_getspecific(*pkey) );

}

return 0;
}

spe156.testdrive.compaq.com> ./tsd3


Cleanup *DONE*...key deleted!
Assertion failed: NULL == pthread_getspecific(*pkey), file tsd3.c, line
23
Abort process (core dumped)

[2] In the meantime, spelling it out in the *2nd* PWPT edition
would be *sufficient* for me! ;-)

Alexander Terekhov

unread,
Feb 22, 2002, 11:10:16 AM2/22/02
to

David Butenhof wrote:
[...]

> > But threads have no way to get to those values, because the key is
> > destroyed. There may still be copies of that key, but it's wrong to use
> > it, because it's a dead object. Clearing out values that are not
> > legally accessible to the program is innocuous.
>
> You still can't make it work without synchronizing every TSD access.

Only if you want/need to "detect EBUSY" (and you just can't
"detect" all error cases, BTW), *I think*. Personally, I do
not need that fancy error detection on key destruction. Oh,
Ah, and I also just hate PTHREAD_KEYS_MAX, and would even like
to see something along the lines of pthread_once_init_create/
pthread_once_init_delete for pthread_once (extra USER parm
and preserved init routine return status aside), but never
mind. ;-)

regards,
alexander.

Kaz Kylheku

unread,
Feb 22, 2002, 11:31:37 AM2/22/02
to
In article <3C7666D8...@web.de>, Alexander Terekhov wrote:
>
>David Butenhof wrote:
>[...]
>> So BOTH program results are correct; you just "didn't try hard enough".
>
>I wrote a THIRD[1] program that did it, I believe.
>
>And, yes, I would have NO problems with a requirement to
>"manually" set TSD values to NULL in ALL threads prior to
>key destruction...

This is an unreasonable requirement to implement in a complex application.
Every module doing TSD cleanup would have to have a way of notifying every
thread that ever went into that module, and then wait until each of
those threads calls in and sets the slot to null. This is a grossly
unreasonable obligation on the rest of the program with respect to the
module that is going to be a huge nightmare to program.

Alexander Terekhov

unread,
Feb 22, 2002, 1:10:05 PM2/22/02
to

Kaz Kylheku wrote:
[...]

> >And, yes, I would have NO problems with a requirement to
> >"manually" set TSD values to NULL in ALL threads prior to
> >key destruction...
>
> This is an unreasonable requirement to implement in a complex application.
> Every module doing TSD cleanup would have to have a way of notifying every
> thread that ever went into that module, and then wait until each of
> those threads calls in and sets the slot to null. This is a grossly
> unreasonable obligation on the rest of the program with respect to the
> module that is going to be a huge nightmare to program.

Uhmm, but somehow you would have keep track of a shared
key usage anyway, so that you could know/decide WHEN
it is safe to delete it. I think that basically, threads
should explicitly cleanup their associated TSD data and
"unref" a key once it goes out of their "scope" (in effect,
that is similar to thread termination -- when you simply
cancel all suspect threads and delete the key AFTER
join_all loop).

So, why not make BOTH of these operations -- cleanup
of tsd data (if any) and "write NULL value" just a
part of that "unref" operation -- pretty much the
same as implementations do on thread termination,
given PTHREAD_DESTRUCTOR_ITERATIONS, AFAICT.

Or am I missing something?

Could you please provide an example?

regards,
alexander.

0 new messages