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
--- /dev/null
+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.
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