|
6 | 6 | import functools |
7 | 7 | import inspect |
8 | 8 | import itertools |
9 | | -import pickle |
10 | | -import platform |
11 | 9 | import time |
12 | 10 | import traceback |
13 | 11 | import warnings |
|
44 | 42 |
|
45 | 43 |
|
46 | 44 | # -- Runner definitions |
47 | | -if platform.system() == "Linux": |
48 | | - _default_executor = concurrent.ProcessPoolExecutor # type: ignore[misc] |
49 | | -else: |
50 | | - # On Windows and MacOS functions, the __main__ module must be |
51 | | - # importable by worker subprocesses. This means that |
52 | | - # ProcessPoolExecutor will not work in the interactive interpreter. |
53 | | - # On Linux the whole process is forked, so the issue does not appear. |
54 | | - # See https://docs.python.org/3/library/concurrent.futures.html#processpoolexecutor |
55 | | - # and https://github.com/python-adaptive/adaptive/issues/301 |
56 | | - _default_executor = loky.get_reusable_executor # type: ignore[misc] |
| 45 | +# Functions submitted to a stdlib ProcessPoolExecutor must be importable from |
| 46 | +# `__main__` by the worker, which fails for functions defined interactively |
| 47 | +# (notebooks, doc pages). This used to work on Linux because workers were |
| 48 | +# forked, but Python 3.14 changed the default start method on Linux to |
| 49 | +# "forkserver", which re-imports `__main__` like Windows/macOS always did. |
| 50 | +# loky serializes functions by value with cloudpickle, so it works everywhere. |
| 51 | +# See https://docs.python.org/3/library/concurrent.futures.html#processpoolexecutor, |
| 52 | +# https://github.com/python-adaptive/adaptive/issues/301, |
| 53 | +# and https://github.com/python/cpython/issues/84559 |
| 54 | +_default_executor = loky.get_reusable_executor |
57 | 55 |
|
58 | 56 |
|
59 | 57 | class BaseRunner(metaclass=abc.ABCMeta): |
@@ -86,8 +84,7 @@ class BaseRunner(metaclass=abc.ABCMeta): |
86 | 84 | `mpi4py.futures.MPIPoolExecutor`, `ipyparallel.Client` or\ |
87 | 85 | `loky.get_reusable_executor`, optional |
88 | 86 | The executor in which to evaluate the function to be learned. |
89 | | - If not provided, a new `~concurrent.futures.ProcessPoolExecutor` on |
90 | | - Linux, and a `loky.get_reusable_executor` on MacOS and Windows. |
| 87 | + If not provided, a new `loky.get_reusable_executor` is used. |
91 | 88 | ntasks : int, optional |
92 | 89 | The number of concurrent function evaluations. Defaults to the number |
93 | 90 | of cores available in `executor`. |
@@ -373,8 +370,7 @@ class BlockingRunner(BaseRunner): |
373 | 370 | `mpi4py.futures.MPIPoolExecutor`, `ipyparallel.Client` or\ |
374 | 371 | `loky.get_reusable_executor`, optional |
375 | 372 | The executor in which to evaluate the function to be learned. |
376 | | - If not provided, a new `~concurrent.futures.ProcessPoolExecutor` on |
377 | | - Linux, and a `loky.get_reusable_executor` on MacOS and Windows. |
| 373 | + If not provided, a new `loky.get_reusable_executor` is used. |
378 | 374 | ntasks : int, optional |
379 | 375 | The number of concurrent function evaluations. Defaults to the number |
380 | 376 | of cores available in `executor`. |
@@ -520,8 +516,7 @@ class AsyncRunner(BaseRunner): |
520 | 516 | `mpi4py.futures.MPIPoolExecutor`, `ipyparallel.Client` or\ |
521 | 517 | `loky.get_reusable_executor`, optional |
522 | 518 | The executor in which to evaluate the function to be learned. |
523 | | - If not provided, a new `~concurrent.futures.ProcessPoolExecutor` on |
524 | | - Linux, and a `loky.get_reusable_executor` on MacOS and Windows. |
| 519 | + If not provided, a new `loky.get_reusable_executor` is used. |
525 | 520 | ntasks : int, optional |
526 | 521 | The number of concurrent function evaluations. Defaults to the number |
527 | 522 | of cores available in `executor`. |
@@ -595,22 +590,6 @@ def __init__( |
595 | 590 | retries: int = 0, |
596 | 591 | raise_if_retries_exceeded: bool = True, |
597 | 592 | ) -> None: |
598 | | - if ( |
599 | | - executor is None |
600 | | - and _default_executor is concurrent.ProcessPoolExecutor |
601 | | - and not inspect.iscoroutinefunction(learner.function) |
602 | | - ): |
603 | | - try: |
604 | | - pickle.dumps(learner.function) |
605 | | - except pickle.PicklingError as e: |
606 | | - raise ValueError( |
607 | | - "`learner.function` cannot be pickled (is it a lamdba function?)" |
608 | | - " and therefore does not work with the default executor." |
609 | | - " Either make sure the function is pickleble or use an executor" |
610 | | - " that might work with 'hard to pickle'-functions" |
611 | | - " , e.g. `ipyparallel` with `dill`." |
612 | | - ) from e |
613 | | - |
614 | 593 | super().__init__( |
615 | 594 | learner, |
616 | 595 | goal=goal, |
|
0 commit comments