Skip to content

Commit cde31ec

Browse files
zangjiuchengclaude
andauthored
gh-151644: Fix data race in sys.setdlopenflags/getdlopenflags under free-threading (gh-151768)
In free-threading builds, concurrent calls to sys.getdlopenflags() and sys.setdlopenflags() race on interp->imports.dlopenflags. Fix by using FT_ATOMIC_LOAD_INT_RELAXED / FT_ATOMIC_STORE_INT_RELAXED in _PyImport_GetDLOpenFlags and _PyImport_SetDLOpenFlags, consistent with how analogous interpreter-state integer fields (lazy_imports_mode, pystats_enabled) are protected. Relaxed ordering is correct here: dlopenflags is a standalone config integer with no ordering relationship to other memory. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 423ae0f commit cde31ec

3 files changed

Lines changed: 27 additions & 2 deletions

File tree

Lib/test/test_free_threading/test_sys.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,27 @@
44

55

66
class SysModuleTest(unittest.TestCase):
7+
@unittest.skipUnless(hasattr(sys, "setdlopenflags"),
8+
"test needs sys.setdlopenflags()")
9+
def test_dlopenflags_concurrent(self):
10+
# gh-151644: getdlopenflags() and setdlopenflags() must be safe to
11+
# call concurrently in free-threaded builds.
12+
original = sys.getdlopenflags()
13+
self.addCleanup(sys.setdlopenflags, original)
14+
15+
# Use a small set of known-valid flag values to avoid integer overflow.
16+
flag_values = [1, 2, 256, 257]
17+
18+
def worker(worker_id):
19+
for i in range(20_000):
20+
if worker_id % 2 == 0:
21+
sys.getdlopenflags()
22+
else:
23+
sys.setdlopenflags(flag_values[worker_id % len(flag_values)])
24+
25+
workers = [lambda i=i: worker(i) for i in range(6)]
26+
threading_helper.run_concurrently(workers)
27+
728
def test_int_max_str_digits_thread(self):
829
# gh-151218: Check that it's safe to call get_int_max_str_digits() and
930
# set_int_max_str_digits() in parallel. Previously, this test triggered
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fix a data race in :func:`sys.setdlopenflags` and :func:`sys.getdlopenflags`
2+
when called concurrently in the free-threaded build. The underlying
3+
``_PyImport_GetDLOpenFlags`` and ``_PyImport_SetDLOpenFlags`` functions now
4+
use atomic load/store operations.

Python/import.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -908,13 +908,13 @@ _PyImport_SwapPackageContext(const char *newcontext)
908908
int
909909
_PyImport_GetDLOpenFlags(PyInterpreterState *interp)
910910
{
911-
return DLOPENFLAGS(interp);
911+
return FT_ATOMIC_LOAD_INT_RELAXED(DLOPENFLAGS(interp));
912912
}
913913

914914
void
915915
_PyImport_SetDLOpenFlags(PyInterpreterState *interp, int new_val)
916916
{
917-
DLOPENFLAGS(interp) = new_val;
917+
FT_ATOMIC_STORE_INT_RELAXED(DLOPENFLAGS(interp), new_val);
918918
}
919919
#endif // HAVE_DLOPEN
920920

0 commit comments

Comments
 (0)