Fixed bugs with cancellations and exceptions

This commit is contained in:
Nocturn9x 2023-05-11 14:17:09 +02:00
parent 98a280ce39
commit 724b5c98ba
Signed by: nocturn9x
GPG Key ID: 8270F9F467971E59
4 changed files with 41 additions and 26 deletions

View File

@ -166,6 +166,8 @@ async def cancel(task: Task, block: bool = False):
if task.done(): if task.done():
return return
if task.scope and not task.scope.cancellable:
return
await syscall("cancel", task) await syscall("cancel", task)
if block: if block:
await wait(task) await wait(task)

View File

@ -126,10 +126,12 @@ class FIFOKernel:
:return: :return:
""" """
self._sigint_handled = True
if not currently_protected(): if not currently_protected():
self.run_ready.appendleft(self.entry_point) self.run_ready.appendleft(self.entry_point)
self.entry_point.pending_exception = KeyboardInterrupt()
self.handle_errors(self.run_task_step) self.handle_errors(self.run_task_step)
else:
self._sigint_handled = True
def done(self) -> bool: def done(self) -> bool:
""" """
@ -352,28 +354,17 @@ class FIFOKernel:
# there are no more runnable tasks # there are no more runnable tasks
return return
self.current_task = self.run_ready.popleft() self.current_task = self.run_ready.popleft()
if self.current_task.pending_cancellation:
# We perform the deferred cancellation
# if it was previously scheduled
self.cancel(self.current_task)
if self.current_task.done():
return
self._running = True self._running = True
_runner = self.current_task.run _runner = self.current_task.run
_data = [self.data.pop(self.current_task, None)] _data = [self.data.pop(self.current_task, None)]
if exc := self.current_task.pending_exception:
self.current_task.pending_exception = None
_runner = partial(self.current_task.throw, exc)
_data = []
# Some debugging and internal chatter here # Some debugging and internal chatter here
self.current_task.steps += 1 self.current_task.steps += 1
self.current_task.state = TaskState.RUN self.current_task.state = TaskState.RUN
self.debugger.before_task_step(self.current_task) self.debugger.before_task_step(self.current_task)
if self._sigint_handled:
self._sigint_handled = False
self.current_task.throw(KeyboardInterrupt())
_runner = partial(self.current_task.throw, KeyboardInterrupt)
_data = []
elif exc := self.current_task.pending_exception:
self.current_task.pending_exception = None
_runner = partial(self.current_task.throw, exc)
_data = []
# Run a single step with the calculation (i.e. until a yield # Run a single step with the calculation (i.e. until a yield
# somewhere) # somewhere)
method, args, kwargs = _runner(*_data) method, args, kwargs = _runner(*_data)
@ -456,7 +447,9 @@ class FIFOKernel:
else: else:
task = self.current_task task = self.current_task
""" """
self._sigint_handled = False
task = self.entry_point task = self.entry_point
task.pending_exception = KeyboardInterrupt()
self.run_ready.appendleft(task) self.run_ready.appendleft(task)
self.handle_errors(self.run_task_step) self.handle_errors(self.run_task_step)
elif not self.run_ready: elif not self.run_ready:
@ -579,14 +572,14 @@ class FIFOKernel:
it fails it fails
""" """
if not task.scope or task.scope.cancellable:
self.paused.discard(task)
self.io_release_task(task)
self.run_ready.append(task)
self.handle_errors(partial(task.throw, Cancelled(task)), task)
if task.state != TaskState.CANCELLED:
task.pending_cancellation = True
self.reschedule_running() self.reschedule_running()
self.paused.discard(task)
self.io_release_task(task)
self.run_ready.appendleft(task)
self.handle_errors(partial(task.throw, Cancelled()), task)
self.handle_errors(self.run_task_step)
if task.state != TaskState.CANCELLED:
task.pending_exception = Cancelled()
def throw(self, task, error): def throw(self, task, error):
""" """
@ -682,7 +675,7 @@ class FIFOKernel:
""" """
task = Task(func.__name__ or repr(func), func(*args, **kwargs)) task = Task(func.__name__ or repr(func), func(*args, **kwargs))
# We inject our magic secret variable into the coroutine's stack frame so # We inject our magic secret variable into the coroutine's stack frame, so
# we can look it up later # we can look it up later
task.coroutine.cr_frame.f_locals.setdefault(CTRLC_PROTECTION_ENABLED, False) task.coroutine.cr_frame.f_locals.setdefault(CTRLC_PROTECTION_ENABLED, False)
task.scope = self.current_scope task.scope = self.current_scope
@ -736,7 +729,8 @@ class FIFOKernel:
return return
elif self.current_task.last_io[1] == resource: elif self.current_task.last_io[1] == resource:
# If the event to listen for has changed we just modify it # If the event to listen for has changed we just modify it
self.selector.modify(resource, evt_type, {evt_type: self.current_task}) key = self.selector.get_key(resource)
self.selector.modify(resource, evt_type | key.events, key.data.update({evt_type: self.current_task}))
self.current_task.last_io = (evt_type, resource) self.current_task.last_io = (evt_type, resource)
self.debugger.on_io_schedule(resource, evt_type) self.debugger.on_io_schedule(resource, evt_type)
elif not self.current_task.last_io or self.current_task.last_io[1] != resource: elif not self.current_task.last_io or self.current_task.last_io[1] != resource:

15
tests/deadlock.py Normal file
View File

@ -0,0 +1,15 @@
import aiosched
async def deadlock():
await aiosched.Event().wait()
async def parent():
async with aiosched.skip_after(5):
await deadlock()
print("Done")
aiosched.run(parent)
print("Exited")

View File

@ -32,4 +32,8 @@ async def main():
print("Two-way proxy shutting down") print("Two-way proxy shutting down")
aiosched.run(main) try:
aiosched.run(main)
except KeyboardInterrupt:
print(f"Ctrl+C caught")