Skip to content

Commit ae2ddf4

Browse files
authored
Merge pull request #300 from python-adaptive/loky-non-default
make loky a default on Windows and MacOS but not on Linux
2 parents 75a3274 + 9e0dad9 commit ae2ddf4

4 files changed

Lines changed: 46 additions & 37 deletions

File tree

adaptive/runner.py

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,14 @@
55
import inspect
66
import itertools
77
import pickle
8+
import platform
89
import time
910
import traceback
1011
import warnings
1112
from contextlib import suppress
1213

14+
import loky
15+
1316
from adaptive.notebook_integration import in_ipynb, live_info, live_plot
1417

1518
try:
@@ -33,22 +36,23 @@
3336
except ModuleNotFoundError:
3437
with_mpi4py = False
3538

36-
try:
37-
import loky
38-
39-
with_loky = True
40-
except ModuleNotFoundError:
41-
with_loky = False
4239

4340
with suppress(ModuleNotFoundError):
4441
import uvloop
4542

4643
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
4744

4845

49-
_default_executor = (
50-
loky.get_reusable_executor if with_loky else concurrent.ProcessPoolExecutor
51-
)
46+
if platform.system() == "Linux":
47+
_default_executor = concurrent.ProcessPoolExecutor
48+
else:
49+
# On Windows and MacOS functions, the __main__ module must be
50+
# importable by worker subprocesses. This means that
51+
# ProcessPoolExecutor will not work in the interactive interpreter.
52+
# On Linux the whole process is forked, so the issue does not appear.
53+
# See https://docs.python.org/3/library/concurrent.futures.html#processpoolexecutor
54+
# and https://github.com/python-adaptive/adaptive/issues/301
55+
_default_executor = loky.get_reusable_executor
5256

5357

5458
class BaseRunner(metaclass=abc.ABCMeta):
@@ -65,7 +69,8 @@ class BaseRunner(metaclass=abc.ABCMeta):
6569
`mpi4py.futures.MPIPoolExecutor`, `ipyparallel.Client` or\
6670
`loky.get_reusable_executor`, optional
6771
The executor in which to evaluate the function to be learned.
68-
If not provided, a new `~concurrent.futures.ProcessPoolExecutor`.
72+
If not provided, a new `~concurrent.futures.ProcessPoolExecutor` on
73+
Linux, and a `loky.get_reusable_executor` on MacOS and Windows.
6974
ntasks : int, optional
7075
The number of concurrent function evaluations. Defaults to the number
7176
of cores available in `executor`.
@@ -317,7 +322,8 @@ class BlockingRunner(BaseRunner):
317322
`mpi4py.futures.MPIPoolExecutor`, `ipyparallel.Client` or\
318323
`loky.get_reusable_executor`, optional
319324
The executor in which to evaluate the function to be learned.
320-
If not provided, a new `~concurrent.futures.ProcessPoolExecutor`.
325+
If not provided, a new `~concurrent.futures.ProcessPoolExecutor` on
326+
Linux, and a `loky.get_reusable_executor` on MacOS and Windows.
321327
ntasks : int, optional
322328
The number of concurrent function evaluations. Defaults to the number
323329
of cores available in `executor`.
@@ -433,7 +439,8 @@ class AsyncRunner(BaseRunner):
433439
`mpi4py.futures.MPIPoolExecutor`, `ipyparallel.Client` or\
434440
`loky.get_reusable_executor`, optional
435441
The executor in which to evaluate the function to be learned.
436-
If not provided, a new `~concurrent.futures.ProcessPoolExecutor`.
442+
If not provided, a new `~concurrent.futures.ProcessPoolExecutor` on
443+
Linux, and a `loky.get_reusable_executor` on MacOS and Windows.
437444
ntasks : int, optional
438445
The number of concurrent function evaluations. Defaults to the number
439446
of cores available in `executor`.
@@ -814,7 +821,7 @@ def _get_ncores(ex):
814821
ex, (concurrent.ProcessPoolExecutor, concurrent.ThreadPoolExecutor)
815822
):
816823
return ex._max_workers # not public API!
817-
elif with_loky and isinstance(ex, loky.reusable_executor._ReusablePoolExecutor):
824+
elif isinstance(ex, loky.reusable_executor._ReusablePoolExecutor):
818825
return ex._max_workers # not public API!
819826
elif isinstance(ex, SequentialExecutor):
820827
return 1

adaptive/tests/test_runner.py

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import asyncio
2-
import os
2+
import platform
33
import sys
44
import time
55

@@ -15,9 +15,10 @@
1515
stop_after,
1616
with_distributed,
1717
with_ipyparallel,
18-
with_loky,
1918
)
2019

20+
OPERATING_SYSTEM = platform.system()
21+
2122

2223
def blocking_runner(learner, goal):
2324
BlockingRunner(learner, goal, executor=SequentialExecutor())
@@ -72,22 +73,6 @@ async def f(x):
7273
# --- Test with different executors
7374

7475

75-
@pytest.fixture(scope="session")
76-
def ipyparallel_executor():
77-
from ipyparallel import Client
78-
79-
if os.name == "nt":
80-
import wexpect as expect
81-
else:
82-
import pexpect as expect
83-
84-
child = expect.spawn("ipcluster start -n 1")
85-
child.expect("Engines appear to have started successfully", timeout=35)
86-
yield Client()
87-
if not child.terminate(force=True):
88-
raise RuntimeError("Could not stop ipcluster")
89-
90-
9176
@pytest.fixture(scope="session")
9277
def loky_executor():
9378
import loky
@@ -118,17 +103,35 @@ def test_stop_after_goal():
118103

119104

120105
@pytest.mark.skipif(not with_ipyparallel, reason="IPyparallel is not installed")
121-
def test_ipyparallel_executor(ipyparallel_executor):
106+
@pytest.mark.skipif(
107+
OPERATING_SYSTEM == "Windows" and sys.version_info >= (3, 7),
108+
reason="Gets stuck in CI",
109+
)
110+
def test_ipyparallel_executor():
111+
from ipyparallel import Client
112+
113+
if OPERATING_SYSTEM == "Windows":
114+
import wexpect as expect
115+
else:
116+
import pexpect as expect
117+
118+
child = expect.spawn("ipcluster start -n 1")
119+
child.expect("Engines appear to have started successfully", timeout=35)
120+
ipyparallel_executor = Client()
122121
learner = Learner1D(linear, (-1, 1))
123122
BlockingRunner(learner, trivial_goal, executor=ipyparallel_executor)
123+
124124
assert learner.npoints > 0
125125

126+
if not child.terminate(force=True):
127+
raise RuntimeError("Could not stop ipcluster")
128+
126129

127130
@flaky.flaky(max_runs=5)
128131
@pytest.mark.timeout(60)
129132
@pytest.mark.skipif(not with_distributed, reason="dask.distributed is not installed")
130-
@pytest.mark.skipif(os.name == "nt", reason="XXX: seems to always fail")
131-
@pytest.mark.skipif(sys.platform == "darwin", reason="XXX: intermittently fails")
133+
@pytest.mark.skipif(OPERATING_SYSTEM == "Windows", reason="XXX: seems to always fail")
134+
@pytest.mark.skipif(OPERATING_SYSTEM == "Darwin", reason="XXX: intermittently fails")
132135
def test_distributed_executor():
133136
from distributed import Client
134137

@@ -139,7 +142,6 @@ def test_distributed_executor():
139142
assert learner.npoints > 0
140143

141144

142-
@pytest.mark.skipif(not with_loky, reason="loky not installed")
143145
def test_loky_executor(loky_executor):
144146
learner = Learner1D(lambda x: x, (-1, 1))
145147
BlockingRunner(

docs/environment.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,6 @@ dependencies:
1717
- atomicwrites=1.4.0
1818
- sphinx_fontawesome=0.0.6
1919
- sphinx=3.2.1
20-
- m2r2=0.2.5
20+
- m2r2=0.2.7
2121
- pip:
2222
- sphinx_rtd_theme

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ def get_version_and_cmdclass(package_name):
3030
"sortedcontainers >= 2.0",
3131
"atomicwrites",
3232
"cloudpickle",
33+
"loky >= 2.9",
3334
]
3435

3536
extras_require = {
@@ -55,7 +56,6 @@ def get_version_and_cmdclass(package_name):
5556
"dill",
5657
"distributed",
5758
"ipyparallel>=6.2.5", # because of https://github.com/ipython/ipyparallel/issues/404
58-
"loky",
5959
"scikit-optimize>=0.8.1", # because of https://github.com/scikit-optimize/scikit-optimize/issues/931
6060
"wexpect" if os.name == "nt" else "pexpect",
6161
],

0 commit comments

Comments
 (0)