Skip to content

Commit 7a7aca0

Browse files
committed
getargs.c: make parser_init thread-safe
1 parent 6845b13 commit 7a7aca0

File tree

3 files changed

+25
-22
lines changed

3 files changed

+25
-22
lines changed

Include/cpython/modsupport.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ typedef struct _PyArg_Parser {
5656
const char * const *keywords;
5757
const char *fname;
5858
const char *custom_msg;
59+
_PyOnceFlag once; /* atomic one-time initialization flag */
5960
int pos; /* number of positional-only arguments */
6061
int min; /* minimal number of arguments */
6162
int max; /* maximal number of positional arguments */

PC/python_ver_rc.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#define PYTHON_COPYRIGHT "Copyright \xA9 2001-2023 Python Software Foundation. Copyright \xA9 2000 BeOpen.com. Copyright \xA9 1995-2001 CNRI. Copyright \xA9 1991-1995 SMC."
99

1010
#define MS_WINDOWS
11+
#define Py_LIMITED_API 3
1112
#include "modsupport.h"
1213
#include "patchlevel.h"
1314
#ifdef _DEBUG

Python/getargs.c

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1972,13 +1972,11 @@ new_kwtuple(const char * const *keywords, int total, int pos)
19721972
static int
19731973
_parser_init(struct _PyArg_Parser *parser)
19741974
{
1975+
// We allow threads to potentially race scanning the keywords
1976+
// and format, since these are read-only. Modifications to the
1977+
// _PyArg_Parser are protected by a _PyOnceFlag below.
19751978
const char * const *keywords = parser->keywords;
19761979
assert(keywords != NULL);
1977-
assert(parser->pos == 0 &&
1978-
(parser->format == NULL || parser->fname == NULL) &&
1979-
parser->custom_msg == NULL &&
1980-
parser->min == 0 &&
1981-
parser->max == 0);
19821980

19831981
int len, pos;
19841982
if (scan_keywords(keywords, &len, &pos) < 0) {
@@ -2012,6 +2010,18 @@ _parser_init(struct _PyArg_Parser *parser)
20122010
owned = 0;
20132011
}
20142012

2013+
if (!_PyBeginOnce(&parser->once)) {
2014+
// someone else initialized the parser
2015+
Py_DECREF(kwtuple);
2016+
return 1;
2017+
}
2018+
2019+
assert(parser->pos == 0 &&
2020+
(parser->format == NULL || parser->fname == NULL) &&
2021+
parser->custom_msg == NULL &&
2022+
parser->min == 0 &&
2023+
parser->max == 0);
2024+
20152025
parser->pos = pos;
20162026
parser->fname = fname;
20172027
parser->custom_msg = custommsg;
@@ -2021,31 +2031,22 @@ _parser_init(struct _PyArg_Parser *parser)
20212031
parser->initialized = owned ? 1 : -1;
20222032

20232033
assert(parser->next == NULL);
2024-
parser->next = _PyRuntime.getargs.static_parsers;
2025-
_PyRuntime.getargs.static_parsers = parser;
2034+
struct _PyArg_Parser *next;
2035+
do {
2036+
next = _Py_atomic_load_ptr(&_PyRuntime.getargs.static_parsers);
2037+
parser->next = next;
2038+
} while (!_Py_atomic_compare_exchange_ptr(&_PyRuntime.getargs.static_parsers, next, parser));
2039+
_PyEndOnce(&parser->once);
20262040
return 1;
20272041
}
20282042

20292043
static int
20302044
parser_init(struct _PyArg_Parser *parser)
20312045
{
2032-
// volatile as it can be modified by other threads
2033-
// and should not be optimized or reordered by compiler
2034-
if (*((volatile int *)&parser->initialized)) {
2035-
assert(parser->kwtuple != NULL);
2036-
return 1;
2037-
}
2038-
PyThread_acquire_lock(_PyRuntime.getargs.mutex, WAIT_LOCK);
2039-
// Check again if another thread initialized the parser
2040-
// while we were waiting for the lock.
2041-
if (*((volatile int *)&parser->initialized)) {
2042-
assert(parser->kwtuple != NULL);
2043-
PyThread_release_lock(_PyRuntime.getargs.mutex);
2046+
if (_PyOnce_Initialized(&parser->once)) {
20442047
return 1;
20452048
}
2046-
int ret = _parser_init(parser);
2047-
PyThread_release_lock(_PyRuntime.getargs.mutex);
2048-
return ret;
2049+
return _parser_init(parser);
20492050
}
20502051

20512052
static void

0 commit comments

Comments
 (0)