Skip to content

bpo-40522: Store tstate in a Thread Local Storage #23976

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
bpo-40522: Store tstate in a Thread Local Storage
If Python is built with GCC or clang, the current interpreter and the
current Python thread state are now stored in a Thread Local Storage.

Changes:

* configure checks for C11 _Thread_local keyword.
* Use _Thread_local keyword, GCC and clang __thread extension.
* Add set_current_tstate() sub-function which sets these two new TLS
  variables (if available).
* _PyThreadState_Swap() and _PyThreadState_DeleteCurrent() now call
  set_current_tstate().
* _PyThreadState_GET() and _PyInterpreterState_GET() now use the TLS
  variable if available.
  • Loading branch information
vstinner committed Dec 28, 2020
commit fd097fa8e8fe1721e6543d7ac5dafbe779bb7365
24 changes: 23 additions & 1 deletion Include/internal/pycore_pystate.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,22 @@ _Py_ThreadCanHandlePendingCalls(void)
PyAPI_FUNC(PyThreadState*) _PyThreadState_GetTSS(void);
#endif


// Thread local storage for the current interpreter and the current Python
// thread state, if supported by the compiler.
#ifdef HAVE__THREAD_LOCAL_KEYWORD
// C11 _Thread_local keyword
# define _Py_HAVE_TSS_TSTATE
PyAPI_DATA(_Thread_local PyInterpreterState*) _Py_current_interp;
PyAPI_DATA(_Thread_local PyThreadState*) _Py_current_tstate;
#elif defined(__GNUC__) || defined(__clang__)
// GCC and clang __thread extension
# define _Py_HAVE_TSS_TSTATE
PyAPI_DATA(__thread PyInterpreterState*) _Py_current_interp;
PyAPI_DATA(__thread PyThreadState*) _Py_current_tstate;
#endif


static inline PyThreadState*
_PyRuntimeState_GetThreadState(_PyRuntimeState *runtime)
{
Expand All @@ -75,7 +91,9 @@ _PyRuntimeState_GetThreadState(_PyRuntimeState *runtime)
static inline PyThreadState*
_PyThreadState_GET(void)
{
#ifdef EXPERIMENTAL_ISOLATED_SUBINTERPRETERS
#ifdef _Py_HAVE_TSS_TSTATE
return _Py_current_tstate;
#elif defined(EXPERIMENTAL_ISOLATED_SUBINTERPRETERS)
return _PyThreadState_GetTSS();
#else
return _PyRuntimeState_GetThreadState(&_PyRuntime);
Expand Down Expand Up @@ -110,11 +128,15 @@ _Py_EnsureFuncTstateNotNULL(const char *func, PyThreadState *tstate)
See also _PyInterpreterState_Get()
and _PyGILState_GetInterpreterStateUnsafe(). */
static inline PyInterpreterState* _PyInterpreterState_GET(void) {
#ifdef _Py_HAVE_TSS_TSTATE
return _Py_current_interp;
#else
PyThreadState *tstate = _PyThreadState_GET();
#ifdef Py_DEBUG
_Py_EnsureTstateNotNULL(tstate);
#endif
return tstate->interp;
#endif
}


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
If Python is built with GCC or clang, the current interpreter and the current
Python thread state are now stored in a Thread Local Storage.
37 changes: 29 additions & 8 deletions Python/pystate.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,16 @@ extern "C" {

#define _PyRuntimeGILState_GetThreadState(gilstate) \
((PyThreadState*)_Py_atomic_load_relaxed(&(gilstate)->tstate_current))
#define _PyRuntimeGILState_SetThreadState(gilstate, value) \
_Py_atomic_store_relaxed(&(gilstate)->tstate_current, \
(uintptr_t)(value))


#ifdef HAVE__THREAD_LOCAL_KEYWORD
_Thread_local PyInterpreterState *_Py_current_interp = NULL;
_Thread_local PyThreadState *_Py_current_tstate = NULL;
#elif defined(__GNUC__) || defined(__clang__)
# define _Py_HAVE_TSS_TSTATE
__thread PyInterpreterState *_Py_current_interp = NULL;
__thread PyThreadState *_Py_current_tstate = NULL;
#endif

/* Forward declarations */
static PyThreadState *_PyGILState_GetThisThreadState(struct _gilstate_runtime_state *gilstate);
Expand Down Expand Up @@ -603,6 +610,22 @@ PyInterpreterState_GetDict(PyInterpreterState *interp)
return interp->dict;
}


static void
set_current_tstate(struct _gilstate_runtime_state *gilstate,
PyThreadState *tstate)
{
#ifdef _Py_HAVE_TSS_TSTATE
_Py_current_tstate = tstate;
_Py_current_interp = tstate ? tstate->interp : NULL;
#endif
#ifdef EXPERIMENTAL_ISOLATED_SUBINTERPRETERS
PyThread_tss_set(&gilstate->autoTSSkey, tstate);
#endif
_Py_atomic_store_relaxed(&gilstate->tstate_current, (uintptr_t)tstate);
}


static PyThreadState *
new_threadstate(PyInterpreterState *interp, int init)
{
Expand Down Expand Up @@ -929,7 +952,7 @@ _PyThreadState_DeleteCurrent(PyThreadState *tstate)
_Py_EnsureTstateNotNULL(tstate);
struct _gilstate_runtime_state *gilstate = &tstate->interp->runtime->gilstate;
tstate_delete_common(tstate, gilstate);
_PyRuntimeGILState_SetThreadState(gilstate, NULL);
set_current_tstate(gilstate, NULL);
_PyEval_ReleaseLock(tstate);
PyMem_RawFree(tstate);
}
Expand Down Expand Up @@ -1018,7 +1041,8 @@ _PyThreadState_Swap(struct _gilstate_runtime_state *gilstate, PyThreadState *new
PyThreadState *oldts = _PyRuntimeGILState_GetThreadState(gilstate);
#endif

_PyRuntimeGILState_SetThreadState(gilstate, newts);
set_current_tstate(gilstate, newts);

/* It should not be possible for more than one thread state
to be used for a thread. Check this the best we can in debug
builds.
Expand All @@ -1034,9 +1058,6 @@ _PyThreadState_Swap(struct _gilstate_runtime_state *gilstate, PyThreadState *new
Py_FatalError("Invalid thread state for this thread");
errno = err;
}
#endif
#ifdef EXPERIMENTAL_ISOLATED_SUBINTERPRETERS
PyThread_tss_set(&gilstate->autoTSSkey, newts);
#endif
return oldts;
}
Expand Down
31 changes: 31 additions & 0 deletions configure
Original file line number Diff line number Diff line change
Expand Up @@ -17129,6 +17129,37 @@ $as_echo "#define HAVE_BUILTIN_ATOMIC 1" >>confdefs.h

fi

# Check for _Thread_local keyword
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for _Thread_local keyword" >&5
$as_echo_n "checking for _Thread_local keyword... " >&6; }
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */


_Thread_local int val;
int main() {
val = 1;
return val;
}


_ACEOF
if ac_fn_c_try_link "$LINENO"; then :
have__thread_local_keyword=yes
else
have__thread_local_keyword=no
fi
rm -f core conftest.err conftest.$ac_objext \
conftest$ac_exeext conftest.$ac_ext
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $have__thread_local_keyword" >&5
$as_echo "$have__thread_local_keyword" >&6; }

if test "$have__thread_local_keyword" = yes; then

$as_echo "#define HAVE__THREAD_LOCAL_KEYWORD 1" >>confdefs.h

fi

# ensurepip option
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ensurepip" >&5
$as_echo_n "checking for ensurepip... " >&6; }
Expand Down
18 changes: 18 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -5606,6 +5606,24 @@ if test "$have_builtin_atomic" = yes; then
AC_DEFINE(HAVE_BUILTIN_ATOMIC, 1, [Has builtin __atomic_load_n() and __atomic_store_n() functions])
fi

# Check for _Thread_local keyword
AC_MSG_CHECKING(for _Thread_local keyword)
AC_LINK_IFELSE(
[
AC_LANG_SOURCE([[
_Thread_local int val;
int main() {
val = 1;
return val;
}
]])
],[have__thread_local_keyword=yes],[have__thread_local_keyword=no])
AC_MSG_RESULT($have__thread_local_keyword)

if test "$have__thread_local_keyword" = yes; then
AC_DEFINE(HAVE__THREAD_LOCAL_KEYWORD, 1, [Has C11 _Thread_local keyword])
fi

# ensurepip option
AC_MSG_CHECKING(for ensurepip)
AC_ARG_WITH(ensurepip,
Expand Down
3 changes: 3 additions & 0 deletions pyconfig.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -1359,6 +1359,9 @@
/* Define to 1 if you have the `_getpty' function. */
#undef HAVE__GETPTY

/* Has C11 _Thread_local keyword */
#undef HAVE__THREAD_LOCAL_KEYWORD

/* Define to 1 if `major', `minor', and `makedev' are declared in <mkdev.h>.
*/
#undef MAJOR_IN_MKDEV
Expand Down