diff --git a/giambio/_core.py b/giambio/_core.py index 0992325..48871ff 100644 --- a/giambio/_core.py +++ b/giambio/_core.py @@ -107,8 +107,10 @@ class AsyncScheduler: ): # Schedules tasks that are waiting on events self.check_events() except CancelledError as cancelled: - if cancelled.args[0] in self.tasks: - self.tasks.remove(cancelled.args[0]) # Remove the dead task + task = cancelled.args[0] + task.cancelled = True + if task in self.tasks: + self.tasks.remove(task) # Remove the dead task self.tasks.append(self.current_task) except StopIteration as e: # Coroutine ends self.current_task.result = e.args[0] if e.args else None @@ -238,7 +240,9 @@ class AsyncScheduler: def sleep(self, seconds: int or float): """Puts the caller to sleep for a given amount of seconds""" - if seconds: + if seconds < 0: + raise ValueError("the delay can't be negative") + elif seconds: self.current_task.status = "sleep" self.paused.put(self.current_task, seconds) else: @@ -276,8 +280,12 @@ class AsyncScheduler: "sleep", "I/O", ): # It is safe to cancel a task while blocking - task.cancelled = True - task.throw(CancelledError(task)) + try: + task.throw(CancelledError(task)) + except Exception as error: + task.cancelled = True # The task died + self.tasks.append(self.current_task) + task.exc = error else: task.status = "cancel" # Cancellation is deferred diff --git a/giambio/_managers.py b/giambio/_managers.py index 27008ec..d6e42fe 100644 --- a/giambio/_managers.py +++ b/giambio/_managers.py @@ -1,5 +1,5 @@ from ._core import AsyncScheduler -from .exceptions import ErrorStack +from .exceptions import ErrorStack, CancelledError import itertools @@ -20,18 +20,22 @@ class TaskManager(object): async def _cancel_and_raise(self, err): """Cancels all tasks and raises an exception""" - exc = ErrorStack() + errors = [] for task in itertools.chain( self.scheduler.tasks.copy(), self.scheduler.paused.items(), - *self.scheduler.event_waiting.values() + self.scheduler.event_waiting.values(), ): + await task.cancel() try: - await task.cancel() - except Exception as err: - exc.errors.append(err) - if exc.errors: - exc.errors.insert(err, 0) + await task.join() + except Exception as fault: + fault.__cause__ = None # We clear this to avoid unrelated tracebacks + errors.append(fault) + if errors: + exc = ErrorStack() + errors.insert(0, err) + exc.errors = errors raise exc raise err diff --git a/tests/raise.py b/tests/raise.py index e60b0d6..a24e218 100644 --- a/tests/raise.py +++ b/tests/raise.py @@ -24,14 +24,25 @@ async def countup(stop: int, step: int = 1): raise ValueError("Ciao") +async def countdown2(n: int): + while n > 0: + print(f"Down {n}") + n -= 1 + await giambio.sleep(1) + raise Exception("bruh") + print("Countdown over") + return 0 + + async def main(): try: async with giambio.TaskManager(scheduler) as manager: - cdown = manager.create_task(countdown(10)) cup = manager.create_task(countup(5, 2)) + cdown = manager.create_task(countdown(10)) +# cdown2 = manager.create_task(countdown2(5)) print("Counters started, awaiting completion") - except Exception: - print("Exceptions propagate!") + except Exception as err: + print(f"An error occurred!\n{type(err).__name__}: {err}") print("Task execution complete")