Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 4 additions & 18 deletions Lib/concurrent/futures/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
wait,
as_completed)

lazy from .process import ProcessPoolExecutor
lazy from .thread import ThreadPoolExecutor

__all__ = [
'FIRST_COMPLETED',
'FIRST_EXCEPTION',
Expand All @@ -40,26 +43,9 @@
_interpreters = None

if _interpreters:
lazy from .interpreter import InterpreterPoolExecutor # noqa: F401
__all__.append('InterpreterPoolExecutor')


def __dir__():
return __all__ + ['__author__', '__doc__']


def __getattr__(name):
global ProcessPoolExecutor, ThreadPoolExecutor, InterpreterPoolExecutor

if name == 'ProcessPoolExecutor':
from .process import ProcessPoolExecutor
return ProcessPoolExecutor

if name == 'ThreadPoolExecutor':
from .thread import ThreadPoolExecutor
return ThreadPoolExecutor

if _interpreters and name == 'InterpreterPoolExecutor':
from .interpreter import InterpreterPoolExecutor
return InterpreterPoolExecutor

raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
17 changes: 8 additions & 9 deletions Lib/test/test_concurrent_futures/test_shutdown.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,20 +115,21 @@ def test_hang_gh83386(self):

See https://github.com/python/cpython/issues/83386.
"""
if self.executor_type == futures.ProcessPoolExecutor:
raise unittest.SkipTest(
"Hangs, see https://github.com/python/cpython/issues/83386")

rc, out, err = assert_python_ok('-c', """if True:
from concurrent.futures import {executor_type}
from test.test_concurrent_futures.test_shutdown import sleep_and_print
if __name__ == "__main__":
if {context!r}: multiprocessing.set_start_method({context!r})
t = {executor_type}(max_workers=3)
context = '{context}'
if context == "":
t = {executor_type}(max_workers=3)
else:
from multiprocessing import get_context
t = {executor_type}(max_workers=3,
mp_context=get_context(context))
t.submit(sleep_and_print, 1.0, "apple")
t.shutdown(wait=False)
""".format(executor_type=self.executor_type.__name__,
context=getattr(self, 'ctx', None)))
context=getattr(self, 'ctx', '')))
self.assertFalse(err)
self.assertEqual(out.strip(), b"apple")

Expand Down Expand Up @@ -243,8 +244,6 @@ def test_thread_names_default(self):
t.join()

def test_cancel_futures_wait_false(self):
# Can only be reliably tested for TPE, since PPE often hangs with
# `wait=False` (even without *cancel_futures*).
rc, out, err = assert_python_ok('-c', """if True:
from concurrent.futures import ThreadPoolExecutor
from test.test_concurrent_futures.test_shutdown import sleep_and_print
Expand Down
21 changes: 21 additions & 0 deletions Lib/test/test_free_threading/test_sys.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
:class:`collections.deque` prevent rare crash when calling ``extend`` under
high memory pressure conditions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
:mod:`concurrent.futures` now uses lazy imports for its executor submodules
instead of a module ``__getattr__`` hook.
1 change: 0 additions & 1 deletion Modules/_collectionsmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -512,7 +512,6 @@ deque_extend_impl(dequeobject *deque, PyObject *iterable)
iternext = *Py_TYPE(it)->tp_iternext;
while ((item = iternext(it)) != NULL) {
if (deque_append_lock_held(deque, item, maxlen) == -1) {
Py_DECREF(item);
Py_DECREF(it);
return NULL;
}
Expand Down
4 changes: 2 additions & 2 deletions Python/import.c
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Loading