Starting to work on proper cancellation

This commit is contained in:
nocturn9x 2020-07-14 21:14:59 +00:00
parent 7d3b5da90f
commit 77268f63bf
3 changed files with 39 additions and 16 deletions

View File

@ -107,8 +107,10 @@ class AsyncScheduler:
): # Schedules tasks that are waiting on events ): # Schedules tasks that are waiting on events
self.check_events() self.check_events()
except CancelledError as cancelled: except CancelledError as cancelled:
if cancelled.args[0] in self.tasks: task = cancelled.args[0]
self.tasks.remove(cancelled.args[0]) # Remove the dead task task.cancelled = True
if task in self.tasks:
self.tasks.remove(task) # Remove the dead task
self.tasks.append(self.current_task) self.tasks.append(self.current_task)
except StopIteration as e: # Coroutine ends except StopIteration as e: # Coroutine ends
self.current_task.result = e.args[0] if e.args else None 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): def sleep(self, seconds: int or float):
"""Puts the caller to sleep for a given amount of seconds""" """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.current_task.status = "sleep"
self.paused.put(self.current_task, seconds) self.paused.put(self.current_task, seconds)
else: else:
@ -276,8 +280,12 @@ class AsyncScheduler:
"sleep", "sleep",
"I/O", "I/O",
): # It is safe to cancel a task while blocking ): # It is safe to cancel a task while blocking
task.cancelled = True try:
task.throw(CancelledError(task)) task.throw(CancelledError(task))
except Exception as error:
task.cancelled = True # The task died
self.tasks.append(self.current_task)
task.exc = error
else: else:
task.status = "cancel" # Cancellation is deferred task.status = "cancel" # Cancellation is deferred

View File

@ -1,5 +1,5 @@
from ._core import AsyncScheduler from ._core import AsyncScheduler
from .exceptions import ErrorStack from .exceptions import ErrorStack, CancelledError
import itertools import itertools
@ -20,18 +20,22 @@ class TaskManager(object):
async def _cancel_and_raise(self, err): async def _cancel_and_raise(self, err):
"""Cancels all tasks and raises an exception""" """Cancels all tasks and raises an exception"""
exc = ErrorStack() errors = []
for task in itertools.chain( for task in itertools.chain(
self.scheduler.tasks.copy(), self.scheduler.tasks.copy(),
self.scheduler.paused.items(), self.scheduler.paused.items(),
*self.scheduler.event_waiting.values() self.scheduler.event_waiting.values(),
): ):
await task.cancel()
try: try:
await task.cancel() await task.join()
except Exception as err: except Exception as fault:
exc.errors.append(err) fault.__cause__ = None # We clear this to avoid unrelated tracebacks
if exc.errors: errors.append(fault)
exc.errors.insert(err, 0) if errors:
exc = ErrorStack()
errors.insert(0, err)
exc.errors = errors
raise exc raise exc
raise err raise err

View File

@ -24,14 +24,25 @@ async def countup(stop: int, step: int = 1):
raise ValueError("Ciao") 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(): async def main():
try: try:
async with giambio.TaskManager(scheduler) as manager: async with giambio.TaskManager(scheduler) as manager:
cdown = manager.create_task(countdown(10))
cup = manager.create_task(countup(5, 2)) cup = manager.create_task(countup(5, 2))
cdown = manager.create_task(countdown(10))
# cdown2 = manager.create_task(countdown2(5))
print("Counters started, awaiting completion") print("Counters started, awaiting completion")
except Exception: except Exception as err:
print("Exceptions propagate!") print(f"An error occurred!\n{type(err).__name__}: {err}")
print("Task execution complete") print("Task execution complete")