bpo-25658: Implement PEP 539 for Thread Specific Storage (TSS) API (GH-1362)

See PEP 539 for details.

Highlights of changes:

- Add Thread Specific Storage (TSS) API
- Document the Thread Local Storage (TLS) API as deprecated
- Update code that used TLS API to use TSS API
This commit is contained in:
Masayuki Yamamoto 2017-10-06 19:41:34 +09:00 committed by Nick Coghlan
parent b8ab9d3fc8
commit 731e189014
18 changed files with 651 additions and 108 deletions

View file

@ -46,7 +46,12 @@ _PyRuntimeState_Init(_PyRuntimeState *runtime)
_PyEval_Initialize(&runtime->ceval);
runtime->gilstate.check_enabled = 1;
runtime->gilstate.autoTLSkey = -1;
/* A TSS key must be initialized with Py_tss_NEEDS_INIT
in accordance with the specification. */
{
Py_tss_t initial = Py_tss_NEEDS_INIT;
runtime->gilstate.autoTSSkey = initial;
}
runtime->interpreters.mutex = PyThread_allocate_lock();
if (runtime->interpreters.mutex == NULL)
@ -485,9 +490,9 @@ PyThreadState_Delete(PyThreadState *tstate)
if (tstate == GET_TSTATE())
Py_FatalError("PyThreadState_Delete: tstate is still current");
if (_PyRuntime.gilstate.autoInterpreterState &&
PyThread_get_key_value(_PyRuntime.gilstate.autoTLSkey) == tstate)
PyThread_tss_get(&_PyRuntime.gilstate.autoTSSkey) == tstate)
{
PyThread_delete_key_value(_PyRuntime.gilstate.autoTLSkey);
PyThread_tss_set(&_PyRuntime.gilstate.autoTSSkey, NULL);
}
tstate_delete_common(tstate);
}
@ -502,9 +507,9 @@ PyThreadState_DeleteCurrent()
"PyThreadState_DeleteCurrent: no current tstate");
tstate_delete_common(tstate);
if (_PyRuntime.gilstate.autoInterpreterState &&
PyThread_get_key_value(_PyRuntime.gilstate.autoTLSkey) == tstate)
PyThread_tss_get(&_PyRuntime.gilstate.autoTSSkey) == tstate)
{
PyThread_delete_key_value(_PyRuntime.gilstate.autoTLSkey);
PyThread_tss_set(&_PyRuntime.gilstate.autoTSSkey, NULL);
}
SET_TSTATE(NULL);
PyEval_ReleaseLock();
@ -761,11 +766,11 @@ void
_PyGILState_Init(PyInterpreterState *i, PyThreadState *t)
{
assert(i && t); /* must init with valid states */
_PyRuntime.gilstate.autoTLSkey = PyThread_create_key();
if (_PyRuntime.gilstate.autoTLSkey == -1)
Py_FatalError("Could not allocate TLS entry");
if (PyThread_tss_create(&_PyRuntime.gilstate.autoTSSkey) != 0) {
Py_FatalError("Could not allocate TSS entry");
}
_PyRuntime.gilstate.autoInterpreterState = i;
assert(PyThread_get_key_value(_PyRuntime.gilstate.autoTLSkey) == NULL);
assert(PyThread_tss_get(&_PyRuntime.gilstate.autoTSSkey) == NULL);
assert(t->gilstate_counter == 0);
_PyGILState_NoteThreadState(t);
@ -780,14 +785,13 @@ _PyGILState_GetInterpreterStateUnsafe(void)
void
_PyGILState_Fini(void)
{
PyThread_delete_key(_PyRuntime.gilstate.autoTLSkey);
_PyRuntime.gilstate.autoTLSkey = -1;
PyThread_tss_delete(&_PyRuntime.gilstate.autoTSSkey);
_PyRuntime.gilstate.autoInterpreterState = NULL;
}
/* Reset the TLS key - called by PyOS_AfterFork_Child().
/* Reset the TSS key - called by PyOS_AfterFork_Child().
* This should not be necessary, but some - buggy - pthread implementations
* don't reset TLS upon fork(), see issue #10517.
* don't reset TSS upon fork(), see issue #10517.
*/
void
_PyGILState_Reinit(void)
@ -796,15 +800,18 @@ _PyGILState_Reinit(void)
if (_PyRuntime.interpreters.mutex == NULL)
Py_FatalError("Can't initialize threads for interpreter");
PyThreadState *tstate = PyGILState_GetThisThreadState();
PyThread_delete_key(_PyRuntime.gilstate.autoTLSkey);
if ((_PyRuntime.gilstate.autoTLSkey = PyThread_create_key()) == -1)
Py_FatalError("Could not allocate TLS entry");
PyThread_tss_delete(&_PyRuntime.gilstate.autoTSSkey);
if (PyThread_tss_create(&_PyRuntime.gilstate.autoTSSkey) != 0) {
Py_FatalError("Could not allocate TSS entry");
}
/* If the thread had an associated auto thread state, reassociate it with
* the new key. */
if (tstate && PyThread_set_key_value(_PyRuntime.gilstate.autoTLSkey,
(void *)tstate) < 0)
Py_FatalError("Couldn't create autoTLSkey mapping");
if (tstate &&
PyThread_tss_set(&_PyRuntime.gilstate.autoTSSkey, (void *)tstate) != 0)
{
Py_FatalError("Couldn't create autoTSSkey mapping");
}
}
/* When a thread state is created for a thread by some mechanism other than
@ -815,13 +822,13 @@ _PyGILState_Reinit(void)
static void
_PyGILState_NoteThreadState(PyThreadState* tstate)
{
/* If autoTLSkey isn't initialized, this must be the very first
/* If autoTSSkey isn't initialized, this must be the very first
threadstate created in Py_Initialize(). Don't do anything for now
(we'll be back here when _PyGILState_Init is called). */
if (!_PyRuntime.gilstate.autoInterpreterState)
return;
/* Stick the thread state for this thread in thread local storage.
/* Stick the thread state for this thread in thread specific storage.
The only situation where you can legitimately have more than one
thread state for an OS level thread is when there are multiple
@ -833,12 +840,11 @@ _PyGILState_NoteThreadState(PyThreadState* tstate)
The first thread state created for that given OS level thread will
"win", which seems reasonable behaviour.
*/
if (PyThread_get_key_value(_PyRuntime.gilstate.autoTLSkey) == NULL) {
if ((PyThread_set_key_value(_PyRuntime.gilstate.autoTLSkey,
(void *)tstate)
) < 0)
if (PyThread_tss_get(&_PyRuntime.gilstate.autoTSSkey) == NULL) {
if ((PyThread_tss_set(&_PyRuntime.gilstate.autoTSSkey, (void *)tstate)
) != 0)
{
Py_FatalError("Couldn't create autoTLSkey mapping");
Py_FatalError("Couldn't create autoTSSkey mapping");
}
}
@ -852,8 +858,7 @@ PyGILState_GetThisThreadState(void)
{
if (_PyRuntime.gilstate.autoInterpreterState == NULL)
return NULL;
return (PyThreadState *)PyThread_get_key_value(
_PyRuntime.gilstate.autoTLSkey);
return (PyThreadState *)PyThread_tss_get(&_PyRuntime.gilstate.autoTSSkey);
}
int
@ -864,8 +869,9 @@ PyGILState_Check(void)
if (!_PyGILState_check_enabled)
return 1;
if (_PyRuntime.gilstate.autoTLSkey == -1)
if (!PyThread_tss_is_created(&_PyRuntime.gilstate.autoTSSkey)) {
return 1;
}
tstate = GET_TSTATE();
if (tstate == NULL)
@ -886,8 +892,7 @@ PyGILState_Ensure(void)
*/
/* Py_Initialize() hasn't been called! */
assert(_PyRuntime.gilstate.autoInterpreterState);
tcur = (PyThreadState *)PyThread_get_key_value(
_PyRuntime.gilstate.autoTLSkey);
tcur = (PyThreadState *)PyThread_tss_get(&_PyRuntime.gilstate.autoTSSkey);
if (tcur == NULL) {
/* At startup, Python has no concrete GIL. If PyGILState_Ensure() is
called from a new thread for the first time, we need the create the
@ -919,8 +924,8 @@ PyGILState_Ensure(void)
void
PyGILState_Release(PyGILState_STATE oldstate)
{
PyThreadState *tcur = (PyThreadState *)PyThread_get_key_value(
_PyRuntime.gilstate.autoTLSkey);
PyThreadState *tcur = (PyThreadState *)PyThread_tss_get(
&_PyRuntime.gilstate.autoTSSkey);
if (tcur == NULL)
Py_FatalError("auto-releasing thread-state, "
"but no thread-state for this thread");

View file

@ -84,7 +84,7 @@ PyThread_init_thread(void)
# define PYTHREAD_NAME "nt"
# include "thread_nt.h"
#else
# error "Require native thread feature. See https://bugs.python.org/issue30832"
# error "Require native threads. See https://bugs.python.org/issue31370"
#endif
@ -111,41 +111,37 @@ PyThread_set_stacksize(size_t size)
}
/* ------------------------------------------------------------------------
Per-thread data ("key") support.
/* Thread Specific Storage (TSS) API
Use PyThread_create_key() to create a new key. This is typically shared
across threads.
Cross-platform components of TSS API implementation.
*/
Use PyThread_set_key_value(thekey, value) to associate void* value with
thekey in the current thread. Each thread has a distinct mapping of thekey
to a void* value. Caution: if the current thread already has a mapping
for thekey, value is ignored.
Py_tss_t *
PyThread_tss_alloc(void)
{
Py_tss_t *new_key = (Py_tss_t *)PyMem_RawMalloc(sizeof(Py_tss_t));
if (new_key == NULL) {
return NULL;
}
new_key->_is_initialized = 0;
return new_key;
}
Use PyThread_get_key_value(thekey) to retrieve the void* value associated
with thekey in the current thread. This returns NULL if no value is
associated with thekey in the current thread.
void
PyThread_tss_free(Py_tss_t *key)
{
if (key != NULL) {
PyThread_tss_delete(key);
PyMem_RawFree((void *)key);
}
}
Use PyThread_delete_key_value(thekey) to forget the current thread's associated
value for thekey. PyThread_delete_key(thekey) forgets the values associated
with thekey across *all* threads.
While some of these functions have error-return values, none set any
Python exception.
None of the functions does memory management on behalf of the void* values.
You need to allocate and deallocate them yourself. If the void* values
happen to be PyObject*, these functions don't do refcount operations on
them either.
The GIL does not need to be held when calling these functions; they supply
their own locking. This isn't true of PyThread_create_key(), though (see
next paragraph).
There's a hidden assumption that PyThread_create_key() will be called before
any of the other functions are called. There's also a hidden assumption
that calls to PyThread_create_key() are serialized externally.
------------------------------------------------------------------------ */
int
PyThread_tss_is_created(Py_tss_t *key)
{
assert(key != NULL);
return key->_is_initialized;
}
PyDoc_STRVAR(threadinfo__doc__,

View file

@ -349,10 +349,15 @@ _pythread_nt_set_stacksize(size_t size)
#define THREAD_SET_STACKSIZE(x) _pythread_nt_set_stacksize(x)
/* Thread Local Storage (TLS) API
This API is DEPRECATED since Python 3.7. See PEP 539 for details.
*/
int
PyThread_create_key(void)
{
DWORD result= TlsAlloc();
DWORD result = TlsAlloc();
if (result == TLS_OUT_OF_INDEXES)
return -1;
return (int)result;
@ -367,12 +372,8 @@ PyThread_delete_key(int key)
int
PyThread_set_key_value(int key, void *value)
{
BOOL ok;
ok = TlsSetValue(key, value);
if (!ok)
return -1;
return 0;
BOOL ok = TlsSetValue(key, value);
return ok ? 0 : -1;
}
void *
@ -399,9 +400,74 @@ PyThread_delete_key_value(int key)
TlsSetValue(key, NULL);
}
/* reinitialization of TLS is not necessary after fork when using
* the native TLS functions. And forking isn't supported on Windows either.
*/
void
PyThread_ReInitTLS(void)
{}
{
}
/* Thread Specific Storage (TSS) API
Platform-specific components of TSS API implementation.
*/
int
PyThread_tss_create(Py_tss_t *key)
{
assert(key != NULL);
/* If the key has been created, function is silently skipped. */
if (key->_is_initialized) {
return 0;
}
DWORD result = TlsAlloc();
if (result == TLS_OUT_OF_INDEXES) {
return -1;
}
/* In Windows, platform-specific key type is DWORD. */
key->_key = result;
key->_is_initialized = 1;
return 0;
}
void
PyThread_tss_delete(Py_tss_t *key)
{
assert(key != NULL);
/* If the key has not been created, function is silently skipped. */
if (!key->_is_initialized) {
return;
}
TlsFree(key->_key);
key->_key = TLS_OUT_OF_INDEXES;
key->_is_initialized = 0;
}
int
PyThread_tss_set(Py_tss_t *key, void *value)
{
assert(key != NULL);
BOOL ok = TlsSetValue(key->_key, value);
return ok ? 0 : -1;
}
void *
PyThread_tss_get(Py_tss_t *key)
{
assert(key != NULL);
/* because TSS is used in the Py_END_ALLOW_THREAD macro,
* it is necessary to preserve the windows error state, because
* it is assumed to be preserved across the call to the macro.
* Ideally, the macro should be fixed, but it is simpler to
* do it here.
*/
DWORD error = GetLastError();
void *result = TlsGetValue(key->_key);
SetLastError(error);
return result;
}

View file

@ -589,9 +589,25 @@ _pythread_pthread_set_stacksize(size_t size)
#define THREAD_SET_STACKSIZE(x) _pythread_pthread_set_stacksize(x)
/* Thread Local Storage (TLS) API
This API is DEPRECATED since Python 3.7. See PEP 539 for details.
*/
/* Issue #25658: On platforms where native TLS key is defined in a way that
cannot be safely cast to int, PyThread_create_key returns immediately a
failure status and other TLS functions all are no-ops. This indicates
clearly that the old API is not supported on platforms where it cannot be
used reliably, and that no effort will be made to add such support.
Note: PTHREAD_KEY_T_IS_COMPATIBLE_WITH_INT will be unnecessary after
removing this API.
*/
int
PyThread_create_key(void)
{
#ifdef PTHREAD_KEY_T_IS_COMPATIBLE_WITH_INT
pthread_key_t key;
int fail = pthread_key_create(&key, NULL);
if (fail)
@ -603,34 +619,102 @@ PyThread_create_key(void)
return -1;
}
return (int)key;
#else
return -1; /* never return valid key value. */
#endif
}
void
PyThread_delete_key(int key)
{
#ifdef PTHREAD_KEY_T_IS_COMPATIBLE_WITH_INT
pthread_key_delete(key);
#endif
}
void
PyThread_delete_key_value(int key)
{
#ifdef PTHREAD_KEY_T_IS_COMPATIBLE_WITH_INT
pthread_setspecific(key, NULL);
#endif
}
int
PyThread_set_key_value(int key, void *value)
{
int fail;
fail = pthread_setspecific(key, value);
#ifdef PTHREAD_KEY_T_IS_COMPATIBLE_WITH_INT
int fail = pthread_setspecific(key, value);
return fail ? -1 : 0;
#else
return -1;
#endif
}
void *
PyThread_get_key_value(int key)
{
#ifdef PTHREAD_KEY_T_IS_COMPATIBLE_WITH_INT
return pthread_getspecific(key);
#else
return NULL;
#endif
}
void
PyThread_ReInitTLS(void)
{}
{
}
/* Thread Specific Storage (TSS) API
Platform-specific components of TSS API implementation.
*/
int
PyThread_tss_create(Py_tss_t *key)
{
assert(key != NULL);
/* If the key has been created, function is silently skipped. */
if (key->_is_initialized) {
return 0;
}
int fail = pthread_key_create(&(key->_key), NULL);
if (fail) {
return -1;
}
key->_is_initialized = 1;
return 0;
}
void
PyThread_tss_delete(Py_tss_t *key)
{
assert(key != NULL);
/* If the key has not been created, function is silently skipped. */
if (!key->_is_initialized) {
return;
}
pthread_key_delete(key->_key);
/* pthread has not provided the defined invalid value for the key. */
key->_is_initialized = 0;
}
int
PyThread_tss_set(Py_tss_t *key, void *value)
{
assert(key != NULL);
int fail = pthread_setspecific(key->_key, value);
return fail ? -1 : 0;
}
void *
PyThread_tss_get(Py_tss_t *key)
{
assert(key != NULL);
return pthread_getspecific(key->_key);
}

View file

@ -760,7 +760,7 @@ _Py_DumpTracebackThreads(int fd, PyInterpreterState *interp,
PyThreadState_Get() doesn't give the state of the thread that caused
the fault if the thread released the GIL, and so this function
cannot be used. Read the thread local storage (TLS) instead: call
cannot be used. Read the thread specific storage (TSS) instead: call
PyGILState_GetThisThreadState(). */
current_tstate = PyGILState_GetThisThreadState();
}