Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-Pebble for openSUSE:Factory checked in at 2024-04-02 16:41:05 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-Pebble (Old) and /work/SRC/openSUSE:Factory/.python-Pebble.new.1905 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-Pebble" Tue Apr 2 16:41:05 2024 rev:16 rq:1163617 version:5.0.7 Changes: -------- --- /work/SRC/openSUSE:Factory/python-Pebble/python-Pebble.changes 2023-12-28 23:03:41.071313043 +0100 +++ /work/SRC/openSUSE:Factory/.python-Pebble.new.1905/python-Pebble.changes 2024-04-02 16:42:36.793584257 +0200 @@ -1,0 +2,10 @@ +Fri Mar 22 21:01:33 UTC 2024 - Dirk Müller <dmuel...@suse.com> + +- update to 5.0.7: + * issue #96: handle race condition under different interpreters + * issue #125: handle frozen classes as exceptions + * issue #127: handle unexpected errors when reading from pipe in + process decorators + * issue #128: allow different context from `multiprocessing` ones + +------------------------------------------------------------------- Old: ---- Pebble-5.0.6.tar.gz New: ---- Pebble-5.0.7.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-Pebble.spec ++++++ --- /var/tmp/diff_new_pack.FDGHc1/_old 2024-04-02 16:42:38.313638516 +0200 +++ /var/tmp/diff_new_pack.FDGHc1/_new 2024-04-02 16:42:38.317638658 +0200 @@ -1,7 +1,7 @@ # # spec file for package python-Pebble # -# Copyright (c) 2023 SUSE LLC +# Copyright (c) 2024 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -17,7 +17,7 @@ Name: python-Pebble -Version: 5.0.6 +Version: 5.0.7 Release: 0 Summary: Threading and multiprocessing eye-candy for Python License: LGPL-3.0-only ++++++ Pebble-5.0.6.tar.gz -> Pebble-5.0.7.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Pebble-5.0.6/PKG-INFO new/Pebble-5.0.7/PKG-INFO --- old/Pebble-5.0.6/PKG-INFO 2023-12-25 17:22:05.110159200 +0100 +++ new/Pebble-5.0.7/PKG-INFO 2024-03-21 23:33:58.758192300 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: Pebble -Version: 5.0.6 +Version: 5.0.7 Summary: Threading and multiprocessing eye-candy. Home-page: https://github.com/noxdafox/pebble Author: Matteo Cafasso diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Pebble-5.0.6/Pebble.egg-info/PKG-INFO new/Pebble-5.0.7/Pebble.egg-info/PKG-INFO --- old/Pebble-5.0.6/Pebble.egg-info/PKG-INFO 2023-12-25 17:22:05.000000000 +0100 +++ new/Pebble-5.0.7/Pebble.egg-info/PKG-INFO 2024-03-21 23:33:58.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: Pebble -Version: 5.0.6 +Version: 5.0.7 Summary: Threading and multiprocessing eye-candy. Home-page: https://github.com/noxdafox/pebble Author: Matteo Cafasso diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Pebble-5.0.6/pebble/__init__.py new/Pebble-5.0.7/pebble/__init__.py --- old/Pebble-5.0.6/pebble/__init__.py 2023-12-25 17:18:15.000000000 +0100 +++ new/Pebble-5.0.7/pebble/__init__.py 2024-03-21 23:20:27.000000000 +0100 @@ -1,5 +1,5 @@ __author__ = 'Matteo Cafasso' -__version__ = '5.0.6' +__version__ = '5.0.7' __license__ = 'LGPL' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Pebble-5.0.6/pebble/asynchronous/process.py new/Pebble-5.0.7/pebble/asynchronous/process.py --- old/Pebble-5.0.6/pebble/asynchronous/process.py 2023-12-06 17:32:40.000000000 +0100 +++ new/Pebble-5.0.7/pebble/asynchronous/process.py 2024-03-11 22:23:47.000000000 +0100 @@ -1,5 +1,5 @@ # This file is part of Pebble. -# Copyright (c) 2013-2023, Matteo Cafasso +# Copyright (c) 2013-2024, Matteo Cafasso # Pebble is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License @@ -62,7 +62,7 @@ return _process_wrapper(args[0], timeout, name, daemon, multiprocessing) # decorator with parameters - _validate_parameters(timeout, name, daemon, mp_context) + _validate_parameters(timeout, name, daemon) mp_context = mp_context if mp_context is not None else multiprocessing # without @pie syntax @@ -163,7 +163,7 @@ except (EOFError, OSError): return Result(ERROR, ProcessExpired('Abnormal termination')) except Exception as error: - return error + return Result(ERROR, error) def _function_handler( @@ -183,21 +183,13 @@ send_result(writer, result) -def _validate_parameters( - timeout: float, - name: str, - daemon: bool, - mp_context: multiprocessing.context.BaseContext -): +def _validate_parameters(timeout: float, name: str, daemon: bool): if timeout is not None and not isinstance(timeout, (int, float)): raise TypeError('Timeout expected to be None or integer or float') if name is not None and not isinstance(name, str): raise TypeError('Name expected to be None or string') if daemon is not None and not isinstance(daemon, bool): raise TypeError('Daemon expected to be None or bool') - if mp_context is not None and not isinstance( - mp_context, multiprocessing.context.BaseContext): - raise TypeError('Context expected to be None or multiprocessing.context') def _get_asyncio_loop() -> asyncio.BaseEventLoop: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Pebble-5.0.6/pebble/asynchronous/thread.py new/Pebble-5.0.7/pebble/asynchronous/thread.py --- old/Pebble-5.0.6/pebble/asynchronous/thread.py 2023-12-06 17:33:03.000000000 +0100 +++ new/Pebble-5.0.7/pebble/asynchronous/thread.py 2024-03-20 22:35:22.000000000 +0100 @@ -1,5 +1,5 @@ # This file is part of Pebble. -# Copyright (c) 2013-2023, Matteo Cafasso +# Copyright (c) 2013-2024, Matteo Cafasso # Pebble is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License @@ -20,7 +20,7 @@ from functools import wraps from traceback import format_exc -from pebble.common import launch_thread +from pebble.common import execute, launch_thread, SUCCESS def thread(*args, **kwargs) -> Callable: @@ -79,13 +79,12 @@ """Runs the actual function in separate thread and returns its result.""" loop = future.get_loop() - try: - result = function(*args, **kwargs) - except BaseException as error: - error.traceback = format_exc() - loop.call_soon_threadsafe(future.set_exception, error) + result = execute(function, *args, **kwargs) + + if result.status == SUCCESS: + loop.call_soon_threadsafe(future.set_result, result.value) else: - loop.call_soon_threadsafe(future.set_result, result) + loop.call_soon_threadsafe(future.set_exception, result.value) def _validate_parameters(name: str, daemon: bool): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Pebble-5.0.6/pebble/common.py new/Pebble-5.0.7/pebble/common.py --- old/Pebble-5.0.6/pebble/common.py 2023-12-25 11:45:33.000000000 +0100 +++ new/Pebble-5.0.7/pebble/common.py 2024-03-21 23:14:01.000000000 +0100 @@ -1,5 +1,5 @@ # This file is part of Pebble. -# Copyright (c) 2013-2023, Matteo Cafasso +# Copyright (c) 2013-2024, Matteo Cafasso # Pebble is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License @@ -122,7 +122,11 @@ def rebuild_exception(exception, traceback): - exception.__cause__ = RemoteTraceback(traceback) + try: + exception.traceback = traceback + exception.__cause__ = RemoteTraceback(traceback) + except AttributeError: # Frozen exception + pass return exception @@ -165,7 +169,11 @@ try: return Result(SUCCESS, function(*args, **kwargs)) except BaseException as error: - error.traceback = format_exc() + try: + error.traceback = format_exc() + except AttributeError: # Frozen exception + pass + return Result(FAILURE, error) @@ -174,8 +182,7 @@ try: return Result(SUCCESS, function(*args, **kwargs)) except BaseException as error: - error.traceback = format_exc() - return Result(FAILURE, RemoteException(error, error.traceback)) + return Result(FAILURE, RemoteException(error, format_exc())) def send_result(pipe, data): @@ -183,8 +190,7 @@ try: pipe.send(data) except (pickle.PicklingError, TypeError) as error: - error.traceback = format_exc() - pipe.send(Result(ERROR, RemoteException(error, error.traceback))) + pipe.send(Result(ERROR, RemoteException(error, format_exc()))) Result = namedtuple('Result', ('status', 'value')) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Pebble-5.0.6/pebble/concurrent/process.py new/Pebble-5.0.7/pebble/concurrent/process.py --- old/Pebble-5.0.6/pebble/concurrent/process.py 2023-12-06 17:40:14.000000000 +0100 +++ new/Pebble-5.0.7/pebble/concurrent/process.py 2024-03-11 22:23:38.000000000 +0100 @@ -1,5 +1,5 @@ # This file is part of Pebble. -# Copyright (c) 2013-2023, Matteo Cafasso +# Copyright (c) 2013-2024, Matteo Cafasso # Pebble is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License @@ -62,7 +62,7 @@ return _process_wrapper(args[0], timeout, name, daemon, multiprocessing) # decorator with parameters - _validate_parameters(timeout, name, daemon, mp_context) + _validate_parameters(timeout, name, daemon) mp_context = mp_context if mp_context is not None else multiprocessing # without @pie syntax @@ -179,24 +179,16 @@ except (EOFError, OSError): return Result(ERROR, ProcessExpired('Abnormal termination')) except Exception as error: - return error + return Result(ERROR, error) -def _validate_parameters( - timeout: float, - name: str, - daemon: bool, - mp_context: multiprocessing.context.BaseContext -): +def _validate_parameters(timeout: float, name: str, daemon: bool): if timeout is not None and not isinstance(timeout, (int, float)): raise TypeError('Timeout expected to be None or integer or float') if name is not None and not isinstance(name, str): raise TypeError('Name expected to be None or string') if daemon is not None and not isinstance(daemon, bool): raise TypeError('Daemon expected to be None or bool') - if mp_context is not None and not isinstance( - mp_context, multiprocessing.context.BaseContext): - raise TypeError('Context expected to be None or multiprocessing.context') ################################################################################ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Pebble-5.0.6/pebble/concurrent/thread.py new/Pebble-5.0.7/pebble/concurrent/thread.py --- old/Pebble-5.0.6/pebble/concurrent/thread.py 2023-12-06 17:32:06.000000000 +0100 +++ new/Pebble-5.0.7/pebble/concurrent/thread.py 2024-03-20 22:19:09.000000000 +0100 @@ -1,5 +1,5 @@ # This file is part of Pebble. -# Copyright (c) 2013-2023, Matteo Cafasso +# Copyright (c) 2013-2024, Matteo Cafasso # Pebble is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License @@ -19,7 +19,7 @@ from traceback import format_exc from concurrent.futures import Future -from pebble.common import launch_thread, SUCCESS +from pebble.common import execute, launch_thread, SUCCESS def thread(*args, **kwargs) -> Callable: @@ -77,13 +77,12 @@ """Runs the actual function in separate thread and returns its result.""" future.set_running_or_notify_cancel() - try: - result = function(*args, **kwargs) - except BaseException as error: - error.traceback = format_exc() - future.set_exception(error) + result = execute(function, *args, **kwargs) + + if result.status == SUCCESS: + future.set_result(result.value) else: - future.set_result(result) + future.set_exception(result.value) def _validate_parameters(name: str, daemon: bool): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Pebble-5.0.6/pebble/decorators.py new/Pebble-5.0.7/pebble/decorators.py --- old/Pebble-5.0.6/pebble/decorators.py 2023-11-21 17:34:59.000000000 +0100 +++ new/Pebble-5.0.7/pebble/decorators.py 2024-02-28 22:12:32.000000000 +0100 @@ -1,5 +1,5 @@ # This file is part of Pebble. -# Copyright (c) 2013-2023, Matteo Cafasso +# Copyright (c) 2013-2024, Matteo Cafasso # Pebble is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Pebble-5.0.6/pebble/functions.py new/Pebble-5.0.7/pebble/functions.py --- old/Pebble-5.0.6/pebble/functions.py 2023-11-21 17:34:59.000000000 +0100 +++ new/Pebble-5.0.7/pebble/functions.py 2024-02-28 22:12:32.000000000 +0100 @@ -1,5 +1,5 @@ # This file is part of Pebble. -# Copyright (c) 2013-2023, Matteo Cafasso +# Copyright (c) 2013-2024, Matteo Cafasso # Pebble is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Pebble-5.0.6/pebble/pool/base_pool.py new/Pebble-5.0.7/pebble/pool/base_pool.py --- old/Pebble-5.0.6/pebble/pool/base_pool.py 2023-12-25 11:57:19.000000000 +0100 +++ new/Pebble-5.0.7/pebble/pool/base_pool.py 2024-02-28 22:12:32.000000000 +0100 @@ -1,5 +1,5 @@ # This file is part of Pebble. -# Copyright (c) 2013-2023, Matteo Cafasso +# Copyright (c) 2013-2024, Matteo Cafasso # Pebble is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Pebble-5.0.6/pebble/pool/channel.py new/Pebble-5.0.7/pebble/pool/channel.py --- old/Pebble-5.0.6/pebble/pool/channel.py 2023-11-21 17:34:59.000000000 +0100 +++ new/Pebble-5.0.7/pebble/pool/channel.py 2024-02-28 22:12:32.000000000 +0100 @@ -1,5 +1,5 @@ # This file is part of Pebble. -# Copyright (c) 2013-2023, Matteo Cafasso +# Copyright (c) 2013-2024, Matteo Cafasso # Pebble is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Pebble-5.0.6/pebble/pool/process.py new/Pebble-5.0.7/pebble/pool/process.py --- old/Pebble-5.0.6/pebble/pool/process.py 2023-11-26 17:46:20.000000000 +0100 +++ new/Pebble-5.0.7/pebble/pool/process.py 2024-02-28 22:12:32.000000000 +0100 @@ -1,5 +1,5 @@ # This file is part of Pebble. -# Copyright (c) 2013-2023, Matteo Cafasso +# Copyright (c) 2013-2024, Matteo Cafasso # Pebble is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License @@ -266,13 +266,12 @@ self.task_manager.task_done(task.id, Result(TASK_ERROR, error)) def find_expired_task(self, worker_id: int) -> Task: - tasks = tuple(self.task_manager.tasks.values()) + tasks = dictionary_values(self.task_manager.tasks) running_tasks = tuple(t for t in tasks if t.worker_id != 0) - if running_tasks: return task_worker_lookup(running_tasks, worker_id) - else: - raise BrokenProcessPool("All workers expired") + + raise BrokenProcessPool("All workers expired") class TaskManager: @@ -317,10 +316,11 @@ self.task_done(task_id, Result(TASK_ERROR, error)) def timeout_tasks(self) -> tuple: - return tuple(t for t in tuple(self.tasks.values()) if self.timeout(t)) + return tuple(t for t in dictionary_values(self.tasks) + if self.timeout(t)) def cancelled_tasks(self) -> tuple: - return tuple(t for t in tuple(self.tasks.values()) + return tuple(t for t in dictionary_values(self.tasks) if t.timestamp != 0 and t.future.cancelled()) @staticmethod @@ -371,8 +371,8 @@ Returns the workers which have unexpectedly ended. """ - workers = tuple(self.workers.values()) - expired = tuple(w for w in workers if not w.is_alive()) + expired = tuple(w for w in dictionary_values(self.workers) + if not w.is_alive()) for worker in expired: self.workers.pop(worker.pid) @@ -495,6 +495,15 @@ stop_process(worker) +def dictionary_values(dictionary: dict) -> tuple: + """Returns a snapshot of the dictionary values handling race conditions.""" + while True: + try: + return tuple(dictionary.values()) + except RuntimeError: # race condition + pass + + atexit.register(interpreter_shutdown) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Pebble-5.0.6/pebble/pool/thread.py new/Pebble-5.0.7/pebble/pool/thread.py --- old/Pebble-5.0.6/pebble/pool/thread.py 2023-11-26 17:17:06.000000000 +0100 +++ new/Pebble-5.0.7/pebble/pool/thread.py 2024-02-28 22:12:32.000000000 +0100 @@ -1,5 +1,5 @@ # This file is part of Pebble. -# Copyright (c) 2013-2023, Matteo Cafasso +# Copyright (c) 2013-2024, Matteo Cafasso # Pebble is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Pebble-5.0.6/test/test_asynchronous_process_fork.py new/Pebble-5.0.7/test/test_asynchronous_process_fork.py --- old/Pebble-5.0.6/test/test_asynchronous_process_fork.py 2023-12-25 12:20:13.000000000 +0100 +++ new/Pebble-5.0.7/test/test_asynchronous_process_fork.py 2024-03-20 22:29:58.000000000 +0100 @@ -5,6 +5,7 @@ import asyncio import unittest import threading +import dataclasses import multiprocessing from concurrent.futures import CancelledError, TimeoutError @@ -55,6 +56,16 @@ return event +@dataclasses.dataclass(frozen=True) +class FrozenError(Exception): + pass + + +@asynchronous.process(context=mp_context) +def frozen_error_decorated(): + raise FrozenError() + + @asynchronous.process(context=mp_context) def critical_decorated(): os._exit(123) @@ -303,6 +314,14 @@ with self.assertRaises((pickle.PicklingError, TypeError)): asyncio.run(test()) + def test_frozen_error_decorated(self): + """Process Fork frozen errors are raised by future.result.""" + async def test(): + return await frozen_error_decorated() + + with self.assertRaises(FrozenError): + asyncio.run(test()) + def test_timeout_decorated(self): """Process Fork raises TimeoutError if so.""" async def test(): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Pebble-5.0.6/test/test_asynchronous_process_forkserver.py new/Pebble-5.0.7/test/test_asynchronous_process_forkserver.py --- old/Pebble-5.0.6/test/test_asynchronous_process_forkserver.py 2023-11-26 16:14:24.000000000 +0100 +++ new/Pebble-5.0.7/test/test_asynchronous_process_forkserver.py 2024-03-20 22:31:00.000000000 +0100 @@ -5,6 +5,7 @@ import asyncio import unittest import threading +import dataclasses import multiprocessing from concurrent.futures import CancelledError, TimeoutError @@ -55,6 +56,16 @@ return event +@dataclasses.dataclass(frozen=True) +class FrozenError(Exception): + pass + + +@asynchronous.process(context=mp_context) +def frozen_error_decorated(): + raise FrozenError() + + @asynchronous.process(context=mp_context) def critical_decorated(): os._exit(123) @@ -286,6 +297,14 @@ with self.assertRaises((pickle.PicklingError, TypeError)): asyncio.run(test()) + def test_frozen_error_decorated(self): + """Process Fork frozen errors are raised by future.result.""" + async def test(): + return await frozen_error_decorated() + + with self.assertRaises(FrozenError): + asyncio.run(test()) + def test_timeout_decorated(self): """Process Forkserver raises TimeoutError if so.""" async def test(): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Pebble-5.0.6/test/test_asynchronous_process_spawn.py new/Pebble-5.0.7/test/test_asynchronous_process_spawn.py --- old/Pebble-5.0.6/test/test_asynchronous_process_spawn.py 2023-11-26 16:13:50.000000000 +0100 +++ new/Pebble-5.0.7/test/test_asynchronous_process_spawn.py 2024-03-20 22:39:09.000000000 +0100 @@ -5,6 +5,7 @@ import asyncio import unittest import threading +import dataclasses import multiprocessing from concurrent.futures import CancelledError, TimeoutError @@ -55,6 +56,16 @@ return event +@dataclasses.dataclass(frozen=True) +class FrozenError(Exception): + pass + + +@asynchronous.process(context=mp_context) +def frozen_error_decorated(): + raise FrozenError() + + @asynchronous.process(context=mp_context) def critical_decorated(): os._exit(123) @@ -286,6 +297,14 @@ with self.assertRaises((pickle.PicklingError, TypeError)): asyncio.run(test()) + def test_frozen_error_decorated(self): + """Process Spawn frozen errors are raised by future.result.""" + async def test(): + return await frozen_error_decorated() + + with self.assertRaises(FrozenError): + asyncio.run(test()) + def test_timeout_decorated(self): """Process Spawn raises TimeoutError if so.""" async def test(): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Pebble-5.0.6/test/test_asynchronous_thread.py new/Pebble-5.0.7/test/test_asynchronous_thread.py --- old/Pebble-5.0.6/test/test_asynchronous_thread.py 2023-11-26 16:14:05.000000000 +0100 +++ new/Pebble-5.0.7/test/test_asynchronous_thread.py 2024-03-20 22:52:16.000000000 +0100 @@ -1,6 +1,7 @@ import asyncio import unittest import threading +import dataclasses from pebble import asynchronous @@ -25,6 +26,16 @@ return RuntimeError("BOOM!") +@dataclasses.dataclass(frozen=True) +class FrozenError(Exception): + pass + + +@asynchronous.thread() +def frozen_error_decorated(): + raise FrozenError() + + @asynchronous.thread() def name_keyword_argument(name='function_kwarg'): return name @@ -168,6 +179,14 @@ self.assertTrue(isinstance(self.exception, RuntimeError), msg=str(self.exception)) + def test_frozen_error_decorated(self): + """Thread frozen errors are raised by future.result.""" + async def test(): + return await frozen_error_decorated() + + with self.assertRaises(FrozenError): + asyncio.run(test()) + def test_name_keyword_argument(self): """name keyword can be passed to a decorated function process without name """ async def test(): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Pebble-5.0.6/test/test_concurrent_process_fork.py new/Pebble-5.0.7/test/test_concurrent_process_fork.py --- old/Pebble-5.0.6/test/test_concurrent_process_fork.py 2023-11-26 16:06:08.000000000 +0100 +++ new/Pebble-5.0.7/test/test_concurrent_process_fork.py 2024-03-20 22:11:01.000000000 +0100 @@ -4,6 +4,7 @@ import signal import unittest import threading +import dataclasses import multiprocessing from concurrent.futures import CancelledError, TimeoutError @@ -54,6 +55,16 @@ return event +@dataclasses.dataclass(frozen=True) +class FrozenError(Exception): + pass + + +@concurrent.process(context=mp_context) +def frozen_error_decorated(): + raise FrozenError() + + @concurrent.process(context=mp_context) def critical_decorated(): os._exit(123) @@ -253,6 +264,12 @@ with self.assertRaises((pickle.PicklingError, TypeError)): future.result() + def test_frozen_error_decorated(self): + """Process Fork frozen errors are raised by future.result.""" + future = frozen_error_decorated() + with self.assertRaises(FrozenError): + future.result() + def test_timeout_decorated(self): """Process Fork raises TimeoutError if so.""" future = long_decorated() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Pebble-5.0.6/test/test_concurrent_process_forkserver.py new/Pebble-5.0.7/test/test_concurrent_process_forkserver.py --- old/Pebble-5.0.6/test/test_concurrent_process_forkserver.py 2023-11-26 16:10:09.000000000 +0100 +++ new/Pebble-5.0.7/test/test_concurrent_process_forkserver.py 2024-03-20 22:27:50.000000000 +0100 @@ -4,6 +4,7 @@ import signal import unittest import threading +import dataclasses import multiprocessing from concurrent.futures import CancelledError, TimeoutError @@ -54,6 +55,16 @@ return event +@dataclasses.dataclass(frozen=True) +class FrozenError(Exception): + pass + + +@concurrent.process(context=mp_context) +def frozen_error_decorated(): + raise FrozenError() + + @concurrent.process(context=mp_context) def critical_decorated(): os._exit(123) @@ -214,6 +225,12 @@ with self.assertRaises((pickle.PicklingError, TypeError)): future.result() + def test_frozen_error_decorated(self): + """Process Fork frozen errors are raised by future.result.""" + future = frozen_error_decorated() + with self.assertRaises(FrozenError): + future.result() + def test_timeout_decorated(self): """Process Forkserver raises TimeoutError if so.""" future = long_decorated() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Pebble-5.0.6/test/test_concurrent_process_spawn.py new/Pebble-5.0.7/test/test_concurrent_process_spawn.py --- old/Pebble-5.0.6/test/test_concurrent_process_spawn.py 2023-11-26 16:09:54.000000000 +0100 +++ new/Pebble-5.0.7/test/test_concurrent_process_spawn.py 2024-03-20 22:28:32.000000000 +0100 @@ -4,6 +4,7 @@ import signal import unittest import threading +import dataclasses import multiprocessing from concurrent.futures import CancelledError, TimeoutError @@ -54,6 +55,16 @@ return event +@dataclasses.dataclass(frozen=True) +class FrozenError(Exception): + pass + + +@concurrent.process(context=mp_context) +def frozen_error_decorated(): + raise FrozenError() + + @concurrent.process(context=mp_context) def critical_decorated(): os._exit(123) @@ -214,6 +225,12 @@ with self.assertRaises((pickle.PicklingError, TypeError)): future.result() + def test_error_decorated(self): + """Process Fork errors are raised by future.result.""" + future = error_decorated() + with self.assertRaises(RuntimeError): + future.result() + def test_timeout_decorated(self): """Process Spawn raises TimeoutError if so.""" future = long_decorated() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Pebble-5.0.6/test/test_process_pool_fork.py new/Pebble-5.0.7/test/test_process_pool_fork.py --- old/Pebble-5.0.6/test/test_process_pool_fork.py 2023-12-25 12:05:45.000000000 +0100 +++ new/Pebble-5.0.7/test/test_process_pool_fork.py 2024-03-20 21:43:09.000000000 +0100 @@ -6,6 +6,7 @@ import asyncio import unittest import threading +import dataclasses import multiprocessing from concurrent.futures import CancelledError, TimeoutError @@ -64,6 +65,15 @@ return BaseException("BOOM!") +@dataclasses.dataclass(frozen=True) +class FrozenError(Exception): + pass + + +def frozen_error_function(): + raise FrozenError() + + def pickle_error_function(): return threading.Lock() @@ -187,6 +197,12 @@ future = pool.schedule(pickle_error_function) self.assertRaises((pickle.PicklingError, TypeError), future.result) + def test_process_pool_frozen_error(self): + """Process Pool Fork frozen errors are raised by future get.""" + with ProcessPool(max_workers=1, context=mp_context) as pool: + future = pool.schedule(frozen_error_function) + self.assertRaises(FrozenError, future.result) + def test_process_pool_timeout(self): """Process Pool Fork future raises TimeoutError if so.""" with ProcessPool(max_workers=1, context=mp_context) as pool: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Pebble-5.0.6/test/test_process_pool_forkserver.py new/Pebble-5.0.7/test/test_process_pool_forkserver.py --- old/Pebble-5.0.6/test/test_process_pool_forkserver.py 2023-12-25 12:37:54.000000000 +0100 +++ new/Pebble-5.0.7/test/test_process_pool_forkserver.py 2024-03-20 22:45:06.000000000 +0100 @@ -6,6 +6,7 @@ import asyncio import unittest import threading +import dataclasses import multiprocessing from concurrent.futures import CancelledError, TimeoutError @@ -70,6 +71,15 @@ return threading.Lock() +@dataclasses.dataclass(frozen=True) +class FrozenError(Exception): + pass + + +def frozen_error_function(): + raise FrozenError() + + def long_function(value=1): time.sleep(value) return value @@ -189,6 +199,12 @@ future = pool.schedule(pickle_error_function) self.assertRaises((pickle.PicklingError, TypeError), future.result) + def test_process_pool_frozen_error(self): + """Process Pool Forkserver frozen errors are raised by future get.""" + with ProcessPool(max_workers=1, context=mp_context) as pool: + future = pool.schedule(frozen_error_function) + self.assertRaises(FrozenError, future.result) + def test_process_pool_timeout(self): """Process Pool Forkserver future raises TimeoutError if so.""" with ProcessPool(max_workers=1, context=mp_context) as pool: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Pebble-5.0.6/test/test_process_pool_spawn.py new/Pebble-5.0.7/test/test_process_pool_spawn.py --- old/Pebble-5.0.6/test/test_process_pool_spawn.py 2023-12-25 12:39:14.000000000 +0100 +++ new/Pebble-5.0.7/test/test_process_pool_spawn.py 2024-03-20 22:48:47.000000000 +0100 @@ -6,6 +6,7 @@ import asyncio import unittest import threading +import dataclasses import multiprocessing from concurrent.futures import CancelledError, TimeoutError @@ -68,6 +69,15 @@ return threading.Lock() +@dataclasses.dataclass(frozen=True) +class FrozenError(Exception): + pass + + +def frozen_error_function(): + raise FrozenError() + + def long_function(value=1): time.sleep(value) return value @@ -187,6 +197,12 @@ future = pool.schedule(pickle_error_function) self.assertRaises((pickle.PicklingError, TypeError), future.result) + def test_process_pool_frozen_error(self): + """Process Pool Spawn frozen errors are raised by future get.""" + with ProcessPool(max_workers=1, context=mp_context) as pool: + future = pool.schedule(frozen_error_function) + self.assertRaises(FrozenError, future.result) + def test_process_pool_timeout(self): """Process Pool Spawn future raises TimeoutError if so.""" with ProcessPool(max_workers=1, context=mp_context) as pool: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Pebble-5.0.6/test/test_thread_pool.py new/Pebble-5.0.7/test/test_thread_pool.py --- old/Pebble-5.0.6/test/test_thread_pool.py 2023-12-25 11:55:53.000000000 +0100 +++ new/Pebble-5.0.7/test/test_thread_pool.py 2024-03-20 22:43:08.000000000 +0100 @@ -2,6 +2,7 @@ import asyncio import unittest import threading +import dataclasses from pebble import ThreadPool @@ -38,6 +39,15 @@ raise BaseException("BOOM!") +@dataclasses.dataclass(frozen=True) +class FrozenError(Exception): + pass + + +def frozen_error_function(): + raise FrozenError() + + def long_function(value=0): time.sleep(1) return value @@ -105,6 +115,12 @@ self.event.wait() self.assertTrue(isinstance(self.exception, BaseException)) + def test_process_pool_frozen_error(self): + """Thread Pool frozen errors are raised by future get.""" + with ThreadPool(max_workers=1) as pool: + future = pool.schedule(frozen_error_function) + self.assertRaises(FrozenError, future.result) + def test_thread_pool_cancel_callback(self): """Thread Pool FutureCancelled is forwarded to callback.""" with ThreadPool(max_workers=1) as pool: