Issue #19787: PyThread_set_key_value() now always set the value

In Python 3.3, PyThread_set_key_value() did nothing if the key already exists
(if the current value is a non-NULL pointer).

When _PyGILState_NoteThreadState() is called twice on the same thread with a
different Python thread state, it still keeps the old Python thread state to
keep the old behaviour. Replacing the Python thread state with the new state
introduces new bugs: see issues #10915 and #15751.
This commit is contained in:
Victor Stinner 2013-12-13 11:08:56 +01:00
parent cb1c4c8c22
commit 590cebe391
6 changed files with 24 additions and 39 deletions

View file

@ -10,6 +10,10 @@ Release date: 2014-01-05
Core and Builtins Core and Builtins
----------------- -----------------
- Issue #19787: PyThread_set_key_value() now always set the value. In Python
3.3, the function did nothing if the key already exists (if the current value
is a non-NULL pointer).
- Issue #14432: Remove the thread state field from the frame structure. Fix a - Issue #14432: Remove the thread state field from the frame structure. Fix a
crash when a generator is created in a C thread that is destroyed while the crash when a generator is created in a C thread that is destroyed while the
generator is still used. The issue was that a generator contains a frame, and generator is still used. The issue was that a generator contains a frame, and

View file

@ -168,14 +168,11 @@ set_reentrant(int reentrant)
assert(reentrant == 0 || reentrant == 1); assert(reentrant == 0 || reentrant == 1);
if (reentrant) { if (reentrant) {
assert(PyThread_get_key_value(tracemalloc_reentrant_key) == NULL); assert(PyThread_get_key_value(tracemalloc_reentrant_key) == NULL);
PyThread_set_key_value(tracemalloc_reentrant_key, PyThread_set_key_value(tracemalloc_reentrant_key, REENTRANT);
REENTRANT);
} }
else { else {
/* FIXME: PyThread_set_key_value() cannot be used to set the flag assert(PyThread_get_key_value(tracemalloc_reentrant_key) == REENTRANT);
to zero, because it does nothing if the variable has already PyThread_set_key_value(tracemalloc_reentrant_key, NULL);
a value set. */
PyThread_delete_key_value(tracemalloc_reentrant_key);
} }
} }

View file

@ -723,18 +723,18 @@ _PyGILState_NoteThreadState(PyThreadState* tstate)
The only situation where you can legitimately have more than one The only situation where you can legitimately have more than one
thread state for an OS level thread is when there are multiple thread state for an OS level thread is when there are multiple
interpreters, when: interpreters.
a) You shouldn't really be using the PyGILState_ APIs anyway, You shouldn't really be using the PyGILState_ APIs anyway (see issues
and: #10915 and #15751).
b) The slightly odd way PyThread_set_key_value works (see The first thread state created for that given OS level thread will
comments by its implementation) means that the first thread "win", which seems reasonable behaviour.
state created for that given OS level thread will "win",
which seems reasonable behaviour.
*/ */
if (PyThread_get_key_value(autoTLSkey) == NULL) {
if (PyThread_set_key_value(autoTLSkey, (void *)tstate) < 0) if (PyThread_set_key_value(autoTLSkey, (void *)tstate) < 0)
Py_FatalError("Couldn't create autoTLSkey mapping"); Py_FatalError("Couldn't create autoTLSkey mapping");
}
/* PyGILState_Release must not try to delete this thread state. */ /* PyGILState_Release must not try to delete this thread state. */
tstate->gilstate_counter = 1; tstate->gilstate_counter = 1;

View file

@ -205,7 +205,7 @@ static int nkeys = 0; /* PyThread_create_key() hands out nkeys+1 next */
* segfaults. Now we lock the whole routine. * segfaults. Now we lock the whole routine.
*/ */
static struct key * static struct key *
find_key(int key, void *value) find_key(int set_value, int key, void *value)
{ {
struct key *p, *prev_p; struct key *p, *prev_p;
long id = PyThread_get_thread_ident(); long id = PyThread_get_thread_ident();
@ -215,8 +215,11 @@ find_key(int key, void *value)
PyThread_acquire_lock(keymutex, 1); PyThread_acquire_lock(keymutex, 1);
prev_p = NULL; prev_p = NULL;
for (p = keyhead; p != NULL; p = p->next) { for (p = keyhead; p != NULL; p = p->next) {
if (p->id == id && p->key == key) if (p->id == id && p->key == key) {
if (set_value)
p->value = value;
goto Done; goto Done;
}
/* Sanity check. These states should never happen but if /* Sanity check. These states should never happen but if
* they do we must abort. Otherwise we'll end up spinning in * they do we must abort. Otherwise we'll end up spinning in
* in a tight loop with the lock held. A similar check is done * in a tight loop with the lock held. A similar check is done
@ -227,7 +230,7 @@ find_key(int key, void *value)
if (p->next == keyhead) if (p->next == keyhead)
Py_FatalError("tls find_key: circular list(!)"); Py_FatalError("tls find_key: circular list(!)");
} }
if (value == NULL) { if (!set_value && value == NULL) {
assert(p == NULL); assert(p == NULL);
goto Done; goto Done;
} }
@ -279,19 +282,12 @@ PyThread_delete_key(int key)
PyThread_release_lock(keymutex); PyThread_release_lock(keymutex);
} }
/* Confusing: If the current thread has an association for key,
* value is ignored, and 0 is returned. Else an attempt is made to create
* an association of key to value for the current thread. 0 is returned
* if that succeeds, but -1 is returned if there's not enough memory
* to create the association. value must not be NULL.
*/
int int
PyThread_set_key_value(int key, void *value) PyThread_set_key_value(int key, void *value)
{ {
struct key *p; struct key *p;
assert(value != NULL); p = find_key(1, key, value);
p = find_key(key, value);
if (p == NULL) if (p == NULL)
return -1; return -1;
else else
@ -304,7 +300,7 @@ PyThread_set_key_value(int key, void *value)
void * void *
PyThread_get_key_value(int key) PyThread_get_key_value(int key)
{ {
struct key *p = find_key(key, NULL); struct key *p = find_key(0, key, NULL);
if (p == NULL) if (p == NULL)
return NULL; return NULL;

View file

@ -389,20 +389,11 @@ PyThread_delete_key(int key)
TlsFree(key); TlsFree(key);
} }
/* We must be careful to emulate the strange semantics implemented in thread.c,
* where the value is only set if it hasn't been set before.
*/
int int
PyThread_set_key_value(int key, void *value) PyThread_set_key_value(int key, void *value)
{ {
BOOL ok; BOOL ok;
void *oldvalue;
assert(value != NULL);
oldvalue = TlsGetValue(key);
if (oldvalue != NULL)
/* ignore value if already set */
return 0;
ok = TlsSetValue(key, value); ok = TlsSetValue(key, value);
if (!ok) if (!ok)
return -1; return -1;

View file

@ -627,9 +627,6 @@ int
PyThread_set_key_value(int key, void *value) PyThread_set_key_value(int key, void *value)
{ {
int fail; int fail;
void *oldValue = pthread_getspecific(key);
if (oldValue != NULL)
return 0;
fail = pthread_setspecific(key, value); fail = pthread_setspecific(key, value);
return fail ? -1 : 0; return fail ? -1 : 0;
} }