https://github.com/python/cpython/commit/cde31ec135905472f1137dddcc8227af2430d89c
commit: cde31ec135905472f1137dddcc8227af2430d89c
branch: main
author: Jiucheng(Oliver) <[email protected]>
committer: nascheme <[email protected]>
date: 2026-07-03T13:19:09-07:00
summary:

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 <[email protected]>

files:
A 
Misc/NEWS.d/next/Core_and_Builtins/2026-06-18-00-00-00.gh-issue-151644.5cFffN.rst
M Lib/test/test_free_threading/test_sys.py
M Python/import.c

diff --git a/Lib/test/test_free_threading/test_sys.py 
b/Lib/test/test_free_threading/test_sys.py
index 37b53bd723fd767..271fdd13c62b668 100644
--- a/Lib/test/test_free_threading/test_sys.py
+++ b/Lib/test/test_free_threading/test_sys.py
@@ -4,6 +4,27 @@
 
 
 class SysModuleTest(unittest.TestCase):
+    @unittest.skipUnless(hasattr(sys, "setdlopenflags"),
+                         "test needs sys.setdlopenflags()")
+    def test_dlopenflags_concurrent(self):
+        # gh-151644: getdlopenflags() and setdlopenflags() must be safe to
+        # call concurrently in free-threaded builds.
+        original = sys.getdlopenflags()
+        self.addCleanup(sys.setdlopenflags, original)
+
+        # Use a small set of known-valid flag values to avoid integer overflow.
+        flag_values = [1, 2, 256, 257]
+
+        def worker(worker_id):
+            for i in range(20_000):
+                if worker_id % 2 == 0:
+                    sys.getdlopenflags()
+                else:
+                    sys.setdlopenflags(flag_values[worker_id % 
len(flag_values)])
+
+        workers = [lambda i=i: worker(i) for i in range(6)]
+        threading_helper.run_concurrently(workers)
+
     def test_int_max_str_digits_thread(self):
         # gh-151218: Check that it's safe to call get_int_max_str_digits() and
         # set_int_max_str_digits() in parallel. Previously, this test triggered
diff --git 
a/Misc/NEWS.d/next/Core_and_Builtins/2026-06-18-00-00-00.gh-issue-151644.5cFffN.rst
 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-18-00-00-00.gh-issue-151644.5cFffN.rst
new file mode 100644
index 000000000000000..ff5058e14b42f15
--- /dev/null
+++ 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-18-00-00-00.gh-issue-151644.5cFffN.rst
@@ -0,0 +1,4 @@
+Fix a data race in :func:`sys.setdlopenflags` and :func:`sys.getdlopenflags`
+when called concurrently in the free-threaded build. The underlying
+``_PyImport_GetDLOpenFlags`` and ``_PyImport_SetDLOpenFlags`` functions now
+use atomic load/store operations.
diff --git a/Python/import.c b/Python/import.c
index 9b315b3125bd942..63e23e21beb1266 100644
--- a/Python/import.c
+++ b/Python/import.c
@@ -908,13 +908,13 @@ _PyImport_SwapPackageContext(const char *newcontext)
 int
 _PyImport_GetDLOpenFlags(PyInterpreterState *interp)
 {
-    return DLOPENFLAGS(interp);
+    return FT_ATOMIC_LOAD_INT_RELAXED(DLOPENFLAGS(interp));
 }
 
 void
 _PyImport_SetDLOpenFlags(PyInterpreterState *interp, int new_val)
 {
-    DLOPENFLAGS(interp) = new_val;
+    FT_ATOMIC_STORE_INT_RELAXED(DLOPENFLAGS(interp), new_val);
 }
 #endif  // HAVE_DLOPEN
 

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to