From 9efa479e5b4727b52572796c2e25e81d9e2aee67 Mon Sep 17 00:00:00 2001 From: "Yves.Duprat" <yduprat@gmail.com> Date: Fri, 4 Apr 2025 21:22:31 +0200 Subject: [PATCH 1/9] Initial commit --- Lib/concurrent/futures/_base.py | 2 +- Lib/concurrent/futures/process.py | 2 +- .../test_concurrent_futures/test_process_pool.py | 14 ++++++++++++++ .../test_concurrent_futures/test_thread_pool.py | 13 +++++++++++++ 4 files changed, 29 insertions(+), 2 deletions(-) diff --git a/Lib/concurrent/futures/_base.py b/Lib/concurrent/futures/_base.py index d5ba39e3d71774..d98b1ebdd584b5 100644 --- a/Lib/concurrent/futures/_base.py +++ b/Lib/concurrent/futures/_base.py @@ -390,7 +390,7 @@ def done(self): return self._state in [CANCELLED, CANCELLED_AND_NOTIFIED, FINISHED] def __get_result(self): - if self._exception: + if self._exception is not None: try: raise self._exception finally: diff --git a/Lib/concurrent/futures/process.py b/Lib/concurrent/futures/process.py index 4847550908adab..76b7b2abe836d8 100644 --- a/Lib/concurrent/futures/process.py +++ b/Lib/concurrent/futures/process.py @@ -440,7 +440,7 @@ def process_result_item(self, result_item): work_item = self.pending_work_items.pop(result_item.work_id, None) # work_item can be None if another process terminated (see above) if work_item is not None: - if result_item.exception: + if result_item.exception is not None: work_item.future.set_exception(result_item.exception) else: work_item.future.set_result(result_item.result) diff --git a/Lib/test/test_concurrent_futures/test_process_pool.py b/Lib/test/test_concurrent_futures/test_process_pool.py index 3f13a1900a4ca4..c2323b610f5f3e 100644 --- a/Lib/test/test_concurrent_futures/test_process_pool.py +++ b/Lib/test/test_concurrent_futures/test_process_pool.py @@ -337,6 +337,20 @@ def test_force_shutdown_workers_stops_pool(self, function_name): if not worker_process.is_alive(): break + class MyException(Exception): + def __bool__(self): + return False + + @classmethod + def raiser(cls): + raise cls.MyException("foo") + + def test_swallows_falsy_exceptions(self): + # fix gh-132063 issue + with self.assertRaisesRegex(self.MyException, "foo"): + with self.executor_type(max_workers=1) as executor: + executor.submit(self.raiser).result() + create_executor_tests(globals(), ProcessPoolExecutorTest, executor_mixins=(ProcessPoolForkMixin, diff --git a/Lib/test/test_concurrent_futures/test_thread_pool.py b/Lib/test/test_concurrent_futures/test_thread_pool.py index 4324241b374967..d85db59693badf 100644 --- a/Lib/test/test_concurrent_futures/test_thread_pool.py +++ b/Lib/test/test_concurrent_futures/test_thread_pool.py @@ -112,6 +112,19 @@ def log_n_wait(ident): # ident='third' is cancelled because it remained in the collection of futures self.assertListEqual(log, ["ident='first' started", "ident='first' stopped"]) + class MyException(Exception): + def __bool__(self): + return False + + @classmethod + def raiser(cls): + raise cls.MyException("foo") + + def test_swallows_falsy_exceptions(self): + # fix gh-132063 issue + with self.assertRaisesRegex(self.MyException, "foo"): + with self.executor_type(max_workers=1) as executor: + executor.submit(self.raiser).result() def setUpModule(): setup_module() From 8295a27f66bc9542aa7fd4303dbdc871f8adcdb7 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Sat, 5 Apr 2025 15:05:12 +0000 Subject: [PATCH 2/9] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2025-04-05-15-05-09.gh-issue-132063.KHnslU.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2025-04-05-15-05-09.gh-issue-132063.KHnslU.rst diff --git a/Misc/NEWS.d/next/Library/2025-04-05-15-05-09.gh-issue-132063.KHnslU.rst b/Misc/NEWS.d/next/Library/2025-04-05-15-05-09.gh-issue-132063.KHnslU.rst new file mode 100644 index 00000000000000..61d1e2d20950bf --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-04-05-15-05-09.gh-issue-132063.KHnslU.rst @@ -0,0 +1 @@ +Fix tests of exception attribute in `:method:process_result_item`of `:class:_ExecutorManagerThread` and `:method:__get_result` of `:class:Future` in module `:lib:concurrent`. From 81ec6588f2e369f616c56828a3d0f9b89335f32b Mon Sep 17 00:00:00 2001 From: Duprat <yduprat@gmail.com> Date: Sat, 5 Apr 2025 17:07:17 +0200 Subject: [PATCH 3/9] Update news --- .../next/Library/2025-04-05-15-05-09.gh-issue-132063.KHnslU.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2025-04-05-15-05-09.gh-issue-132063.KHnslU.rst b/Misc/NEWS.d/next/Library/2025-04-05-15-05-09.gh-issue-132063.KHnslU.rst index 61d1e2d20950bf..01831eff183258 100644 --- a/Misc/NEWS.d/next/Library/2025-04-05-15-05-09.gh-issue-132063.KHnslU.rst +++ b/Misc/NEWS.d/next/Library/2025-04-05-15-05-09.gh-issue-132063.KHnslU.rst @@ -1 +1 @@ -Fix tests of exception attribute in `:method:process_result_item`of `:class:_ExecutorManagerThread` and `:method:__get_result` of `:class:Future` in module `:lib:concurrent`. +Fix tests of exception attribute in :method:`process_result_item`of :class:`_ExecutorManagerThread` and :method:`__get_result` of :class:`Future` in module :lib:`concurrent`. From 098ed59ecdefea0ab4fe2bd806d9456237d4edcc Mon Sep 17 00:00:00 2001 From: Duprat <yduprat@gmail.com> Date: Tue, 8 Apr 2025 12:31:54 +0200 Subject: [PATCH 4/9] Update news --- .../Library/2025-04-05-15-05-09.gh-issue-132063.KHnslU.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2025-04-05-15-05-09.gh-issue-132063.KHnslU.rst b/Misc/NEWS.d/next/Library/2025-04-05-15-05-09.gh-issue-132063.KHnslU.rst index 01831eff183258..1501991478d7f6 100644 --- a/Misc/NEWS.d/next/Library/2025-04-05-15-05-09.gh-issue-132063.KHnslU.rst +++ b/Misc/NEWS.d/next/Library/2025-04-05-15-05-09.gh-issue-132063.KHnslU.rst @@ -1 +1,2 @@ -Fix tests of exception attribute in :method:`process_result_item`of :class:`_ExecutorManagerThread` and :method:`__get_result` of :class:`Future` in module :lib:`concurrent`. +Prevent exceptions, that evaluate as falsey (When `__bool__` method returns `False', when `__len__` method returns 0) +from being ignored by :class:`concurrent.futures.ProcessPoolExecutor` and :class:`concurrent.futures.ThreadPoolExecutor`. From bcf251083244d57ceae60813c176105c6fef38f7 Mon Sep 17 00:00:00 2001 From: "Yves.Duprat" <yduprat@gmail.com> Date: Tue, 8 Apr 2025 12:25:56 +0200 Subject: [PATCH 5/9] Move test to ExecutorTest and Updates the comment of test --- Lib/test/test_concurrent_futures/executor.py | 26 +++++++++++++++++++ .../test_process_pool.py | 14 ---------- .../test_thread_pool.py | 13 ---------- 3 files changed, 26 insertions(+), 27 deletions(-) diff --git a/Lib/test/test_concurrent_futures/executor.py b/Lib/test/test_concurrent_futures/executor.py index d88c34d1c8c8e4..853771a9f573ca 100644 --- a/Lib/test/test_concurrent_futures/executor.py +++ b/Lib/test/test_concurrent_futures/executor.py @@ -23,6 +23,19 @@ def my_method(self): def make_dummy_object(_): return MyObject() +# Used in test_swallows_falsey_exceptions +def raiser(exception, msg='std'): + raise exception(msg) + +class FalseyBoolException(Exception): + def __bool__(self): + return False + + +class FalseyLenException(Exception): + def __len__(self): + return 0 + class ExecutorTest: @@ -205,3 +218,16 @@ def test_free_reference(self): for _ in support.sleeping_retry(support.SHORT_TIMEOUT): if wr() is None: break + + def test_swallows_falsey_exceptions(self): + # see gh-132063: Prevent exceptions that evaluate as falsey + # from being ignored. + # Falsey exceptions return 0 when `__len__` method is called, + # False when `__bool__` method is called. + + msg = 'lenlen' + with self.assertRaisesRegex(FalseyLenException, msg): + self.executor.submit(raiser, FalseyLenException, msg).result() + msg = 'boolbool' + with self.assertRaisesRegex(FalseyBoolException, msg): + self.executor.submit(raiser, FalseyBoolException, msg).result() diff --git a/Lib/test/test_concurrent_futures/test_process_pool.py b/Lib/test/test_concurrent_futures/test_process_pool.py index c2323b610f5f3e..3f13a1900a4ca4 100644 --- a/Lib/test/test_concurrent_futures/test_process_pool.py +++ b/Lib/test/test_concurrent_futures/test_process_pool.py @@ -337,20 +337,6 @@ def test_force_shutdown_workers_stops_pool(self, function_name): if not worker_process.is_alive(): break - class MyException(Exception): - def __bool__(self): - return False - - @classmethod - def raiser(cls): - raise cls.MyException("foo") - - def test_swallows_falsy_exceptions(self): - # fix gh-132063 issue - with self.assertRaisesRegex(self.MyException, "foo"): - with self.executor_type(max_workers=1) as executor: - executor.submit(self.raiser).result() - create_executor_tests(globals(), ProcessPoolExecutorTest, executor_mixins=(ProcessPoolForkMixin, diff --git a/Lib/test/test_concurrent_futures/test_thread_pool.py b/Lib/test/test_concurrent_futures/test_thread_pool.py index d85db59693badf..4324241b374967 100644 --- a/Lib/test/test_concurrent_futures/test_thread_pool.py +++ b/Lib/test/test_concurrent_futures/test_thread_pool.py @@ -112,19 +112,6 @@ def log_n_wait(ident): # ident='third' is cancelled because it remained in the collection of futures self.assertListEqual(log, ["ident='first' started", "ident='first' stopped"]) - class MyException(Exception): - def __bool__(self): - return False - - @classmethod - def raiser(cls): - raise cls.MyException("foo") - - def test_swallows_falsy_exceptions(self): - # fix gh-132063 issue - with self.assertRaisesRegex(self.MyException, "foo"): - with self.executor_type(max_workers=1) as executor: - executor.submit(self.raiser).result() def setUpModule(): setup_module() From fbbe501537f2ba92c2cfa49e315c7214172f45dd Mon Sep 17 00:00:00 2001 From: Duprat <yduprat@gmail.com> Date: Tue, 8 Apr 2025 12:38:10 +0200 Subject: [PATCH 6/9] fix nits in news --- .../next/Library/2025-04-05-15-05-09.gh-issue-132063.KHnslU.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2025-04-05-15-05-09.gh-issue-132063.KHnslU.rst b/Misc/NEWS.d/next/Library/2025-04-05-15-05-09.gh-issue-132063.KHnslU.rst index 1501991478d7f6..59333a0b18e7d6 100644 --- a/Misc/NEWS.d/next/Library/2025-04-05-15-05-09.gh-issue-132063.KHnslU.rst +++ b/Misc/NEWS.d/next/Library/2025-04-05-15-05-09.gh-issue-132063.KHnslU.rst @@ -1,2 +1,2 @@ -Prevent exceptions, that evaluate as falsey (When `__bool__` method returns `False', when `__len__` method returns 0) +Prevent exceptions, that evaluate as falsey (When ``__bool__`` method returns ``False``, when ``__len__`` method returns 0) from being ignored by :class:`concurrent.futures.ProcessPoolExecutor` and :class:`concurrent.futures.ThreadPoolExecutor`. From e450739bea3edc9512f2d1d9f485d3503c614098 Mon Sep 17 00:00:00 2001 From: Duprat <yduprat@gmail.com> Date: Tue, 8 Apr 2025 14:18:48 +0200 Subject: [PATCH 7/9] Update news MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- .../next/Library/2025-04-05-15-05-09.gh-issue-132063.KHnslU.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2025-04-05-15-05-09.gh-issue-132063.KHnslU.rst b/Misc/NEWS.d/next/Library/2025-04-05-15-05-09.gh-issue-132063.KHnslU.rst index 59333a0b18e7d6..d3761759772d03 100644 --- a/Misc/NEWS.d/next/Library/2025-04-05-15-05-09.gh-issue-132063.KHnslU.rst +++ b/Misc/NEWS.d/next/Library/2025-04-05-15-05-09.gh-issue-132063.KHnslU.rst @@ -1,2 +1,2 @@ -Prevent exceptions, that evaluate as falsey (When ``__bool__`` method returns ``False``, when ``__len__`` method returns 0) +Prevent exceptions that evaluate as falsey (namely, when their ``__bool__`` method returns ``False`` or their ``__len__`` method returns 0) from being ignored by :class:`concurrent.futures.ProcessPoolExecutor` and :class:`concurrent.futures.ThreadPoolExecutor`. From ea66f0b0e2aa6580f9823498abc19c21b8ee84de Mon Sep 17 00:00:00 2001 From: Duprat <yduprat@gmail.com> Date: Tue, 8 Apr 2025 14:20:13 +0200 Subject: [PATCH 8/9] Add a blank line MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Lib/test/test_concurrent_futures/executor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/test/test_concurrent_futures/executor.py b/Lib/test/test_concurrent_futures/executor.py index 853771a9f573ca..36f9d26287514b 100644 --- a/Lib/test/test_concurrent_futures/executor.py +++ b/Lib/test/test_concurrent_futures/executor.py @@ -27,6 +27,7 @@ def make_dummy_object(_): def raiser(exception, msg='std'): raise exception(msg) + class FalseyBoolException(Exception): def __bool__(self): return False From b4de44eb62d42f4a4f6c09d61e8ea6691b4df281 Mon Sep 17 00:00:00 2001 From: "Yves.Duprat" <yduprat@gmail.com> Date: Tue, 8 Apr 2025 14:53:28 +0200 Subject: [PATCH 9/9] Update last reviews and comments --- Lib/test/test_concurrent_futures/executor.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_concurrent_futures/executor.py b/Lib/test/test_concurrent_futures/executor.py index 36f9d26287514b..95bf8fcd25bf54 100644 --- a/Lib/test/test_concurrent_futures/executor.py +++ b/Lib/test/test_concurrent_futures/executor.py @@ -23,6 +23,7 @@ def my_method(self): def make_dummy_object(_): return MyObject() + # Used in test_swallows_falsey_exceptions def raiser(exception, msg='std'): raise exception(msg) @@ -223,12 +224,12 @@ def test_free_reference(self): def test_swallows_falsey_exceptions(self): # see gh-132063: Prevent exceptions that evaluate as falsey # from being ignored. - # Falsey exceptions return 0 when `__len__` method is called, - # False when `__bool__` method is called. + # Recall: `x` is falsey if `len(x)` returns 0 or `bool(x)` returns False. - msg = 'lenlen' - with self.assertRaisesRegex(FalseyLenException, msg): - self.executor.submit(raiser, FalseyLenException, msg).result() msg = 'boolbool' with self.assertRaisesRegex(FalseyBoolException, msg): self.executor.submit(raiser, FalseyBoolException, msg).result() + + msg = 'lenlen' + with self.assertRaisesRegex(FalseyLenException, msg): + self.executor.submit(raiser, FalseyLenException, msg).result()