-
-
Notifications
You must be signed in to change notification settings - Fork 35.2k
gh-108518: Make concurrent.futures.Executor.map() consistent with built-in map() #109497
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
ff49969
96699fc
2e0970e
1d0f9e4
2d147a1
a2c0f7e
488abe6
90b2cf8
fc93c75
e021fbd
17b229a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -314,7 +314,9 @@ def wait(fs, timeout=None, return_when=ALL_COMPLETED): | |
| def _result_or_cancel(fut, timeout=None): | ||
| try: | ||
| try: | ||
| return fut.result(timeout) | ||
| return _FutureResult.from_value(fut.result(timeout)) | ||
| except Exception as e: | ||
| return _FutureResult.from_exception(e) | ||
| finally: | ||
| fut.cancel() | ||
| finally: | ||
|
|
@@ -566,6 +568,46 @@ def set_exception(self, exception): | |
|
|
||
| __class_getitem__ = classmethod(types.GenericAlias) | ||
|
|
||
|
|
||
| class _FutureResult(object): | ||
| """ | ||
| This is used to record the exception instead of throwing them. | ||
|
|
||
| _FutureResult must contain either the value of future or an exception | ||
| that was thrown during the computation of future. Use is_exception | ||
| property to determine which one it is. | ||
| """ | ||
|
|
||
| def __init__(self, exception, value): | ||
| self._exception = exception | ||
| self._value = value | ||
|
|
||
| @classmethod | ||
| def from_exception(cls, exc): | ||
| return cls(exc, None) | ||
|
|
||
| @classmethod | ||
| def from_value(cls, value): | ||
| return cls(None, value) | ||
|
|
||
| @property | ||
| def exception(self): | ||
| if not self.is_exception: | ||
| raise RuntimeError("No exception thrown.") | ||
| return self._exception | ||
|
|
||
| @property | ||
| def value(self): | ||
| if self.is_exception: | ||
| raise RuntimeError( | ||
| "Cannot get result value because an exception was thrown.") | ||
| return self._value | ||
|
|
||
| @property | ||
| def is_exception(self): | ||
| return self._exception is not None | ||
|
|
||
|
|
||
| class Executor(object): | ||
| """This is an abstract base class for concrete asynchronous executors.""" | ||
|
|
||
|
|
@@ -602,6 +644,11 @@ def map(self, fn, *iterables, timeout=None, chunksize=1): | |
| before the given timeout. | ||
| Exception: If fn(*args) raises for any values. | ||
| """ | ||
| return _MapResultIterator.from_generator( | ||
| self._map(fn, *iterables, timeout=timeout) | ||
| ) | ||
|
|
||
| def _map(self, fn, *iterables, timeout=None): | ||
| if timeout is not None: | ||
| end_time = timeout + time.monotonic() | ||
|
|
||
|
|
@@ -648,6 +695,25 @@ def __exit__(self, exc_type, exc_val, exc_tb): | |
| return False | ||
|
|
||
|
|
||
| class _MapResultIterator(object): | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Current iterator has the |
||
| """The iterator returned by map().""" | ||
| def __init__(self, gen): | ||
| self.gen = gen | ||
|
|
||
| @classmethod | ||
| def from_generator(cls, gen): | ||
| return cls(gen) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is redundant. You can simply use constructor. |
||
|
|
||
| def __iter__(self): | ||
| return self | ||
|
|
||
| def __next__(self): | ||
| result = next(self.gen) | ||
| if result.is_exception: | ||
| raise result.exception | ||
| return result.value | ||
|
|
||
|
|
||
| class BrokenExecutor(RuntimeError): | ||
| """ | ||
| Raised when a executor has become non-functional after a severe failure. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -79,16 +79,13 @@ def log_n_wait(ident): | |
| # submit work to saturate the pool | ||
| fut = pool.submit(log_n_wait, ident="first") | ||
| try: | ||
| with contextlib.closing( | ||
| pool.map(log_n_wait, ["second", "third"], timeout=0) | ||
| ) as gen: | ||
| with self.assertRaises(TimeoutError): | ||
| next(gen) | ||
| iterator = pool.map(log_n_wait, ["second"], timeout=0) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This code should continue to work without changes. |
||
| with self.assertRaises(TimeoutError): | ||
| next(iterator) | ||
| finally: | ||
| stop_event.set() | ||
| fut.result() | ||
| # ident='second' is cancelled as a result of raising a TimeoutError | ||
| # ident='third' is cancelled because it remained in the collection of futures | ||
| self.assertListEqual(log, ["ident='first' started", "ident='first' stopped"]) | ||
|
|
||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks over-engineered. Why not simply use a result-exception tuple?