Skip to content

Commit 9b30b96

Browse files
GH-95909: Make _PyArg_Parser initialization thread safe (GH-95958)
1 parent 48174fa commit 9b30b96

File tree

3 files changed

+48
-12
lines changed

3 files changed

+48
-12
lines changed

Include/internal/pycore_runtime.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ extern "C" {
1414
#include "pycore_interp.h" // PyInterpreterState
1515
#include "pycore_unicodeobject.h" // struct _Py_unicode_runtime_ids
1616

17+
struct _getargs_runtime_state {
18+
PyThread_type_lock mutex;
19+
};
1720

1821
/* ceval state */
1922

@@ -114,6 +117,7 @@ typedef struct pyruntimestate {
114117

115118
struct _ceval_runtime_state ceval;
116119
struct _gilstate_runtime_state gilstate;
120+
struct _getargs_runtime_state getargs;
117121

118122
PyPreConfig preconfig;
119123

Python/getargs.c

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1974,15 +1974,10 @@ new_kwtuple(const char * const *keywords, int total, int pos)
19741974
}
19751975

19761976
static int
1977-
parser_init(struct _PyArg_Parser *parser)
1977+
_parser_init(struct _PyArg_Parser *parser)
19781978
{
19791979
const char * const *keywords = parser->keywords;
19801980
assert(keywords != NULL);
1981-
1982-
if (parser->initialized) {
1983-
assert(parser->kwtuple != NULL);
1984-
return 1;
1985-
}
19861981
assert(parser->pos == 0 &&
19871982
(parser->format == NULL || parser->fname == NULL) &&
19881983
parser->custom_msg == NULL &&
@@ -2035,6 +2030,28 @@ parser_init(struct _PyArg_Parser *parser)
20352030
return 1;
20362031
}
20372032

2033+
static int
2034+
parser_init(struct _PyArg_Parser *parser)
2035+
{
2036+
// volatile as it can be modified by other threads
2037+
// and should not be optimized or reordered by compiler
2038+
if (*((volatile int *)&parser->initialized)) {
2039+
assert(parser->kwtuple != NULL);
2040+
return 1;
2041+
}
2042+
PyThread_acquire_lock(_PyRuntime.getargs.mutex, WAIT_LOCK);
2043+
// Check again if another thread initialized the parser
2044+
// while we were waiting for the lock.
2045+
if (*((volatile int *)&parser->initialized)) {
2046+
assert(parser->kwtuple != NULL);
2047+
PyThread_release_lock(_PyRuntime.getargs.mutex);
2048+
return 1;
2049+
}
2050+
int ret = _parser_init(parser);
2051+
PyThread_release_lock(_PyRuntime.getargs.mutex);
2052+
return ret;
2053+
}
2054+
20382055
static void
20392056
parser_clear(struct _PyArg_Parser *parser)
20402057
{

Python/pystate.c

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ _Py_COMP_DIAG_POP
5757

5858
static int
5959
alloc_for_runtime(PyThread_type_lock *plock1, PyThread_type_lock *plock2,
60-
PyThread_type_lock *plock3)
60+
PyThread_type_lock *plock3, PyThread_type_lock *plock4)
6161
{
6262
/* Force default allocator, since _PyRuntimeState_Fini() must
6363
use the same allocator than this function. */
@@ -82,11 +82,20 @@ alloc_for_runtime(PyThread_type_lock *plock1, PyThread_type_lock *plock2,
8282
return -1;
8383
}
8484

85+
PyThread_type_lock lock4 = PyThread_allocate_lock();
86+
if (lock4 == NULL) {
87+
PyThread_free_lock(lock1);
88+
PyThread_free_lock(lock2);
89+
PyThread_free_lock(lock3);
90+
return -1;
91+
}
92+
8593
PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
8694

8795
*plock1 = lock1;
8896
*plock2 = lock2;
8997
*plock3 = lock3;
98+
*plock4 = lock4;
9099
return 0;
91100
}
92101

@@ -97,7 +106,8 @@ init_runtime(_PyRuntimeState *runtime,
97106
Py_ssize_t unicode_next_index,
98107
PyThread_type_lock unicode_ids_mutex,
99108
PyThread_type_lock interpreters_mutex,
100-
PyThread_type_lock xidregistry_mutex)
109+
PyThread_type_lock xidregistry_mutex,
110+
PyThread_type_lock getargs_mutex)
101111
{
102112
if (runtime->_initialized) {
103113
Py_FatalError("runtime already initialized");
@@ -119,6 +129,8 @@ init_runtime(_PyRuntimeState *runtime,
119129

120130
runtime->xidregistry.mutex = xidregistry_mutex;
121131

132+
runtime->getargs.mutex = getargs_mutex;
133+
122134
// Set it to the ID of the main thread of the main interpreter.
123135
runtime->main_thread = PyThread_get_thread_ident();
124136

@@ -141,8 +153,8 @@ _PyRuntimeState_Init(_PyRuntimeState *runtime)
141153
// is called multiple times.
142154
Py_ssize_t unicode_next_index = runtime->unicode_ids.next_index;
143155

144-
PyThread_type_lock lock1, lock2, lock3;
145-
if (alloc_for_runtime(&lock1, &lock2, &lock3) != 0) {
156+
PyThread_type_lock lock1, lock2, lock3, lock4;
157+
if (alloc_for_runtime(&lock1, &lock2, &lock3, &lock4) != 0) {
146158
return _PyStatus_NO_MEMORY();
147159
}
148160

@@ -152,7 +164,7 @@ _PyRuntimeState_Init(_PyRuntimeState *runtime)
152164
memcpy(runtime, &initial, sizeof(*runtime));
153165
}
154166
init_runtime(runtime, open_code_hook, open_code_userdata, audit_hook_head,
155-
unicode_next_index, lock1, lock2, lock3);
167+
unicode_next_index, lock1, lock2, lock3, lock4);
156168

157169
return _PyStatus_OK();
158170
}
@@ -172,6 +184,7 @@ _PyRuntimeState_Fini(_PyRuntimeState *runtime)
172184
FREE_LOCK(runtime->interpreters.mutex);
173185
FREE_LOCK(runtime->xidregistry.mutex);
174186
FREE_LOCK(runtime->unicode_ids.lock);
187+
FREE_LOCK(runtime->getargs.mutex);
175188

176189
#undef FREE_LOCK
177190
PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
@@ -194,6 +207,7 @@ _PyRuntimeState_ReInitThreads(_PyRuntimeState *runtime)
194207
int reinit_interp = _PyThread_at_fork_reinit(&runtime->interpreters.mutex);
195208
int reinit_xidregistry = _PyThread_at_fork_reinit(&runtime->xidregistry.mutex);
196209
int reinit_unicode_ids = _PyThread_at_fork_reinit(&runtime->unicode_ids.lock);
210+
int reinit_getargs = _PyThread_at_fork_reinit(&runtime->getargs.mutex);
197211

198212
PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
199213

@@ -204,7 +218,8 @@ _PyRuntimeState_ReInitThreads(_PyRuntimeState *runtime)
204218
if (reinit_interp < 0
205219
|| reinit_main_id < 0
206220
|| reinit_xidregistry < 0
207-
|| reinit_unicode_ids < 0)
221+
|| reinit_unicode_ids < 0
222+
|| reinit_getargs < 0)
208223
{
209224
return _PyStatus_ERR("Failed to reinitialize runtime locks");
210225

0 commit comments

Comments
 (0)