Skip to content

Commit 1b9a068

Browse files
Clean up executors (#1193)
* Clean up executors and make sequential executor truly synchronous. Rename "test_pickling" to "multiprocessing_with_pickling" and "debug_sequential" to "sequential_with_pickling". No longer use a multiprocessing pool of size 1 for "sequential" executor by replacing it with the former "debug_sequential" executor. Clean up and speed up multiprocessing and non-scheduling executor tests. * Remove MULTIPROCESSING_VIA_IO which was only needed for python <3.8 * Make Future forward references normal types * Remove unused map_unordered methods * Remove todo comment * clean up tests some more * Revert "Remove unused map_unordered methods" This reverts commit f0b0dbe. * Reapply "Remove unused map_unordered methods" This reverts commit 98929e8. * Fix slurm test by indirectly parametrizing the tests. Also update pytest to the newest version. * Fix linting and typing * avoid flaky tests * Apply suggestions from code review Co-authored-by: Philipp Otto <[email protected]> * PR feedback: Add deprecation warning for renamed executor strategies. Try to combat test flakiness for slurm sleep tests * update changelog * Apply PR feedback for execution order tests --------- Co-authored-by: Philipp Otto <[email protected]>
1 parent f24f24c commit 1b9a068

File tree

21 files changed

+376
-429
lines changed

21 files changed

+376
-429
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ jobs:
115115
if: ${{ matrix.executors == 'multiprocessing' }}
116116
run: |
117117
cd tests
118-
PYTEST_EXECUTORS=multiprocessing,sequential,test_pickling,debug_sequential \
118+
PYTEST_EXECUTORS=multiprocessing,sequential,multiprocessing_with_pickling,sequential_with_pickling \
119119
uv run --frozen python -m pytest -sv test_all.py test_multiprocessing.py
120120
121121
- name: Run slurm tests

cluster_tools/Changelog.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,13 @@ For upgrade instructions, please check the respective *Breaking Changes* section
1010
[Commits](https://github.com/scalableminds/webknossos-libs/compare/v0.15.11...HEAD)
1111

1212
### Breaking Changes
13+
- Removed the `map_unordered` function of executors. [#1193](https://github.com/scalableminds/webknossos-libs/pull/1193)
1314

1415
### Added
1516

1617
### Changed
18+
- Deprecated the test_pickling and debug_sequential executor strategies. The strategies multiprocessing_with_pickling and sequential should be used instead. [#1193](https://github.com/scalableminds/webknossos-libs/pull/1193)
19+
- The sequential executor strategy no longer uses multiprocessing functionality internally and instead executes functions sequentially and synchronously in the same process. [#1193](https://github.com/scalableminds/webknossos-libs/pull/1193)
1720

1821
### Fixed
1922

cluster_tools/cluster_tools/__init__.py

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1+
import warnings
12
from typing import Any, Literal, overload
23

34
from cluster_tools.executor_protocol import Executor
45
from cluster_tools.executors.dask import DaskExecutor
5-
from cluster_tools.executors.debug_sequential import DebugSequentialExecutor
66
from cluster_tools.executors.multiprocessing_ import MultiprocessingExecutor
7-
from cluster_tools.executors.pickle_ import PickleExecutor
7+
from cluster_tools.executors.multiprocessing_pickle import MultiprocessingPickleExecutor
88
from cluster_tools.executors.sequential import SequentialExecutor
9+
from cluster_tools.executors.sequential_pickle import SequentialPickleExecutor
910
from cluster_tools.schedulers.cluster_executor import (
1011
ClusterExecutor, # noqa: F401 `cluster_tools.schedulers.cluster_executor.ClusterExecutor` imported but unused;
1112
RemoteOutOfMemoryException, # noqa: F401 `cluster_tools.schedulers.cluster_executor.ClusterExecutor` imported but unused;
@@ -16,7 +17,7 @@
1617
from cluster_tools.schedulers.pbs import PBSExecutor
1718
from cluster_tools.schedulers.slurm import SlurmExecutor
1819

19-
# For backwards-compatibility:
20+
# For backwards-compatibility, remove in version 2.0:
2021
WrappedProcessPoolExecutor = MultiprocessingExecutor
2122

2223

@@ -77,6 +78,18 @@ def get_executor(
7778
) -> MultiprocessingExecutor: ...
7879

7980

81+
@overload
82+
def get_executor(
83+
environment: Literal["multiprocessing_with_pickling"], **kwargs: Any
84+
) -> MultiprocessingPickleExecutor: ...
85+
86+
87+
@overload
88+
def get_executor(
89+
environment: Literal["test_pickling"], **kwargs: Any
90+
) -> MultiprocessingPickleExecutor: ...
91+
92+
8093
@overload
8194
def get_executor(
8295
environment: Literal["sequential"], **kwargs: Any
@@ -86,13 +99,13 @@ def get_executor(
8699
@overload
87100
def get_executor(
88101
environment: Literal["debug_sequential"], **kwargs: Any
89-
) -> DebugSequentialExecutor: ...
102+
) -> SequentialExecutor: ...
90103

91104

92105
@overload
93106
def get_executor(
94-
environment: Literal["test_pickling"], **kwargs: Any
95-
) -> PickleExecutor: ...
107+
environment: Literal["sequential_with_pickling"], **kwargs: Any
108+
) -> SequentialPickleExecutor: ...
96109

97110

98111
def get_executor(environment: str, **kwargs: Any) -> "Executor":
@@ -116,8 +129,24 @@ def get_executor(environment: str, **kwargs: Any) -> "Executor":
116129
return MultiprocessingExecutor(**kwargs)
117130
elif environment == "sequential":
118131
return SequentialExecutor(**kwargs)
119-
elif environment == "debug_sequential":
120-
return DebugSequentialExecutor(**kwargs)
132+
elif environment == "sequential_with_pickling":
133+
return SequentialPickleExecutor(**kwargs)
134+
elif environment == "multiprocessing_with_pickling":
135+
return MultiprocessingPickleExecutor(**kwargs)
121136
elif environment == "test_pickling":
122-
return PickleExecutor(**kwargs)
137+
# For backwards-compatibility, remove in version 2.0:
138+
warnings.warn(
139+
"The test_pickling execution strategy is deprecated and will be removed in version 2.0. Use multiprocessing_with_pickling instead.",
140+
DeprecationWarning,
141+
stacklevel=2,
142+
)
143+
return MultiprocessingPickleExecutor(**kwargs)
144+
elif environment == "debug_sequential":
145+
# For backwards-compatibility, remove in version 2.0:
146+
warnings.warn(
147+
"The debug_sequential execution strategy is deprecated and will be removed in version 2.0. Use sequential instead.",
148+
DeprecationWarning,
149+
stacklevel=2,
150+
)
151+
return SequentialExecutor(**kwargs)
123152
raise Exception("Unknown executor: {}".format(environment))

cluster_tools/cluster_tools/executor_protocol.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,26 +20,22 @@
2020

2121
class Executor(Protocol, ContextManager["Executor"]):
2222
@classmethod
23-
def as_completed(cls, futures: List["Future[_T]"]) -> Iterator["Future[_T]"]: ...
23+
def as_completed(cls, futures: List[Future[_T]]) -> Iterator[Future[_T]]: ...
2424

2525
def submit(
2626
self,
2727
__fn: Callable[_P, _T],
2828
/,
2929
*args: _P.args,
3030
**kwargs: _P.kwargs,
31-
) -> "Future[_T]": ...
32-
33-
def map_unordered(
34-
self, fn: Callable[[_S], _T], args: Iterable[_S]
35-
) -> Iterator[_T]: ...
31+
) -> Future[_T]: ...
3632

3733
def map_to_futures(
3834
self,
3935
fn: Callable[[_S], _T],
4036
args: Iterable[_S],
4137
output_pickle_path_getter: Optional[Callable[[_S], PathLike]] = None,
42-
) -> List["Future[_T]"]: ...
38+
) -> List[Future[_T]]: ...
4339

4440
def map(
4541
self,
@@ -49,6 +45,6 @@ def map(
4945
chunksize: Optional[int] = None,
5046
) -> Iterator[_T]: ...
5147

52-
def forward_log(self, fut: "Future[_T]") -> _T: ...
48+
def forward_log(self, fut: Future[_T]) -> _T: ...
5349

5450
def shutdown(self, wait: bool = True, *, cancel_futures: bool = False) -> None: ...

cluster_tools/cluster_tools/executors/dask.py

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ def from_config(
162162
return cls(client, job_resources=job_resources)
163163

164164
@classmethod
165-
def as_completed(cls, futures: List["Future[_T]"]) -> Iterator["Future[_T]"]:
165+
def as_completed(cls, futures: List[Future[_T]]) -> Iterator[Future[_T]]:
166166
from distributed import as_completed
167167

168168
return as_completed(futures)
@@ -172,7 +172,7 @@ def submit( # type: ignore[override]
172172
__fn: Callable[_P, _T],
173173
*args: _P.args,
174174
**kwargs: _P.kwargs,
175-
) -> "Future[_T]":
175+
) -> Future[_T]:
176176
if "__cfut_options" in kwargs:
177177
output_pickle_path = cast(CFutDict, kwargs["__cfut_options"])[
178178
"output_pickle_path"
@@ -236,26 +236,14 @@ def check_resources(
236236
enrich_future_with_uncaught_warning(fut)
237237
return fut
238238

239-
def map_unordered(self, fn: Callable[[_S], _T], args: Iterable[_S]) -> Iterator[_T]:
240-
futs: List["Future[_T]"] = self.map_to_futures(fn, args)
241-
242-
# Return a separate generator to avoid that map_unordered
243-
# is executed lazily (otherwise, jobs would be submitted
244-
# lazily, as well).
245-
def result_generator() -> Iterator:
246-
for fut in self.as_completed(futs):
247-
yield fut.result()
248-
249-
return result_generator()
250-
251239
def map_to_futures(
252240
self,
253241
fn: Callable[[_S], _T],
254242
args: Iterable[
255243
_S
256244
], # TODO change: allow more than one arg per call # noqa FIX002 Line contains TODO
257245
output_pickle_path_getter: Optional[Callable[[_S], os.PathLike]] = None,
258-
) -> List["Future[_T]"]:
246+
) -> List[Future[_T]]:
259247
if output_pickle_path_getter is not None:
260248
futs = [
261249
self.submit( # type: ignore[call-arg]
@@ -283,7 +271,7 @@ def map( # type: ignore[override]
283271
chunksize = 1
284272
return super().map(fn, iterables, timeout=timeout, chunksize=chunksize)
285273

286-
def forward_log(self, fut: "Future[_T]") -> _T:
274+
def forward_log(self, fut: Future[_T]) -> _T:
287275
return fut.result()
288276

289277
def handle_kill(

cluster_tools/cluster_tools/executors/debug_sequential.py

Lines changed: 0 additions & 44 deletions
This file was deleted.

0 commit comments

Comments
 (0)