1212from adaptive .notebook_integration import in_ipynb , live_info , live_plot
1313
1414try :
15- import ipyparallel
15+ if sys .version_info < (3 , 8 ):
16+ # XXX: remove when ipyparallel 6.2.5 is released
17+ import ipyparallel
1618
17- with_ipyparallel = True
19+ with_ipyparallel = True
20+ else :
21+ with_ipyparallel = False
1822except ModuleNotFoundError :
1923 with_ipyparallel = False
2024
3236except ModuleNotFoundError :
3337 with_mpi4py = False
3438
39+ try :
40+ import loky
41+
42+ with_loky = True
43+ except ModuleNotFoundError :
44+ with_loky = False
45+
3546with suppress (ModuleNotFoundError ):
3647 import uvloop
3748
@@ -232,10 +243,13 @@ def _remove_unfinished(self):
232243
233244 def _cleanup (self ):
234245 if self .shutdown_executor :
235- # XXX: temporary set wait=True for Python 3.7
246+ # XXX: temporary set wait=True because of a bug with Python ≥3.7
247+ # and loky in any Python version.
236248 # see https://github.com/python-adaptive/adaptive/issues/156
237249 # and https://github.com/python-adaptive/adaptive/pull/164
238- self .executor .shutdown (wait = True if sys .version_info >= (3 , 7 ) else False )
250+ # and https://bugs.python.org/issue36281
251+ # and https://github.com/joblib/loky/issues/241
252+ self .executor .shutdown (wait = True )
239253 self .end_time = time .time ()
240254
241255 @property
@@ -269,7 +283,8 @@ class BlockingRunner(BaseRunner):
269283 the learner as its sole argument, and return True when we should
270284 stop requesting more points.
271285 executor : `concurrent.futures.Executor`, `distributed.Client`,\
272- `mpi4py.futures.MPIPoolExecutor`, or `ipyparallel.Client`, optional
286+ `mpi4py.futures.MPIPoolExecutor`, `ipyparallel.Client` or\
287+ `loky.get_reusable_executor`, optional
273288 The executor in which to evaluate the function to be learned.
274289 If not provided, a new `~concurrent.futures.ProcessPoolExecutor`.
275290 ntasks : int, optional
@@ -386,7 +401,8 @@ class AsyncRunner(BaseRunner):
386401 stop requesting more points. If not provided, the runner will run
387402 forever, or until ``self.task.cancel()`` is called.
388403 executor : `concurrent.futures.Executor`, `distributed.Client`,\
389- `mpi4py.futures.MPIPoolExecutor`, or `ipyparallel.Client`, optional
404+ `mpi4py.futures.MPIPoolExecutor`, `ipyparallel.Client` or\
405+ `loky.get_reusable_executor`, optional
390406 The executor in which to evaluate the function to be learned.
391407 If not provided, a new `~concurrent.futures.ProcessPoolExecutor`.
392408 ntasks : int, optional
@@ -740,9 +756,16 @@ def shutdown(self, wait=True):
740756 pass
741757
742758
759+ def _default_executor ():
760+ if with_loky :
761+ return loky .get_reusable_executor ()
762+ else :
763+ return concurrent .ProcessPoolExecutor ()
764+
765+
743766def _ensure_executor (executor ):
744767 if executor is None :
745- executor = concurrent . ProcessPoolExecutor ()
768+ executor = _default_executor ()
746769
747770 if isinstance (executor , concurrent .Executor ):
748771 return executor
@@ -765,6 +788,8 @@ def _get_ncores(ex):
765788 ex , (concurrent .ProcessPoolExecutor , concurrent .ThreadPoolExecutor )
766789 ):
767790 return ex ._max_workers # not public API!
791+ elif with_loky and isinstance (ex , loky .reusable_executor ._ReusablePoolExecutor ):
792+ return ex ._max_workers # not public API!
768793 elif isinstance (ex , SequentialExecutor ):
769794 return 1
770795 elif with_distributed and isinstance (ex , distributed .cfexecutor .ClientExecutor ):
0 commit comments