diff --git a/experiment.py b/experiment.py index ccd2dec..b918202 100644 --- a/experiment.py +++ b/experiment.py @@ -10,7 +10,7 @@ What works and what does not (25th March 2020 20:35) - Join mechanism: V - Sleep mechanism: V - Cancellation mechanism: V -- Exception propagation: V +- Exception propagation: X - Concurrent I/O: V - Return values of coroutines: V - Scheduling tasks for future execution: V @@ -23,6 +23,8 @@ async def countdown(n): while n > 0: print(f"Down {n}") n -= 1 + if n <= 2: # Test an exception that triggers only sometimes + raise ValueError await giambio.sleep(1) print("Countdown over") return "Count DOWN over" @@ -44,16 +46,13 @@ async def count(stop, step=1): async def main(): - try: print("Spawning countdown immediately, scheduling count for 4 secs from now") async with giambio.TaskManager(loop) as manager: - task = manager.spawn(countdown(4)) + task = manager.spawn(countdown(8)) manager.schedule(count(8, 2), 4) - await task.cancel() for task, ret in manager.values.items(): print(f"Function '{task.coroutine.__name__}' at {hex(id(task.coroutine))} returned an object of type '{type(ret).__name__}': {repr(ret)}") - except: # TODO: Fix this, see above - pass + loop.start(main) diff --git a/giambio/.exceptions.py.swp b/giambio/.exceptions.py.swp new file mode 100644 index 0000000..d9d6f91 Binary files /dev/null and b/giambio/.exceptions.py.swp differ diff --git a/giambio/abstractions.py b/giambio/abstractions.py index 7ee1a96..e0ee536 100644 --- a/giambio/abstractions.py +++ b/giambio/abstractions.py @@ -10,7 +10,7 @@ class Result: self.exc = exc def __repr__(self): - return f"giambio.core.Result({self.val}, {self.exc})" + return f"giambio.core.Result({self.val}, {repr(self.exc)})" class Task: @@ -19,7 +19,6 @@ class Task: def __init__(self, coroutine: types.coroutine, loop): self.coroutine = coroutine - self.status = False # Not ran yet self.joined = False # True if the task is joined self.result = None # Updated when the coroutine execution ends self.loop = loop # The EventLoop object that spawned the task @@ -30,9 +29,11 @@ class Task: def run(self): """Simple abstraction layer over the coroutines ``send`` method""" - self.status = True + self.steps += 1 + self.execution = "RUNNING" return self.coroutine.send(None) + def __repr__(self): return f"giambio.core.Task({self.coroutine}, {self.status}, {self.joined}, {self.result})" @@ -53,12 +54,4 @@ class Task: return await _join(self, silent) def get_result(self, silenced=False): - if self.result: - if not silenced: - if self.result.exc: - raise self.result.exc - else: - return self.result.val - return self.result.val if self.result.val else self.result.exc - - + return self.result diff --git a/giambio/core.py b/giambio/core.py index e1d8685..187b79d 100644 --- a/giambio/core.py +++ b/giambio/core.py @@ -1,3 +1,4 @@ + import types from collections import deque, defaultdict from selectors import DefaultSelector, EVENT_READ, EVENT_WRITE @@ -58,10 +59,13 @@ class EventLoop: self.to_run.extend(self.joined.pop(self.running, ())) # Reschedules the parent task except RuntimeError: self.to_run.extend(self.joined.pop(self.running, ())) # Reschedules the parent task - except Exception as has_raised: - self.to_run.extend(self.joined.pop(self.running, ())) # Reschedules the parent task - self.running.result = Result(None, has_raised) # Save the exception - raise + except CancelledError: + self.running.execution = "CANCELLED" + self.to_run.extend(self.joined.pop(self.running, ())) + except Exception as err: + self.running.execution = "ERRORED" + self.running.result = Result(None, err) + self.to_run.extend(self.joined.pop(self.running, ())) # Reschedules the parent task except KeyboardInterrupt: self.running.throw(KeyboardInterrupt) @@ -139,7 +143,6 @@ class EventLoop: def want_cancel(self, task): self.to_run.extend(self.joined.pop(self.running, ())) self.to_run.append(self.running) # Reschedules the parent task -# task.cancelled = True task.throw(CancelledError()) @@ -151,4 +154,4 @@ class EventLoop: await _want_write(sock) err = sock.getsockopt(SOL_SOCKET, SO_ERROR) if err != 0: - raise OSError(err, f'Connect call failed {addr}') + raise OSError(err, f'Connect call failed: {addr}') diff --git a/giambio/util.py b/giambio/util.py index 87aec07..4c121f1 100644 --- a/giambio/util.py +++ b/giambio/util.py @@ -15,19 +15,21 @@ class TaskManager: self.loop = loop # The event loop that spawned the TaskManager self.silent = silent # Make exceptions silent? (not recommended) + + async def _cancel_and_raise(self, exc): + for task in self.tasks: + await task.cancel() + async def __aenter__(self): return self async def __aexit__(self, type, value, traceback): - if type: - # TODO: Handle exceptions here - ... - else: - for task in self.tasks: - if not task.cancelled: - self.values[task] = await task.join() - else: - self.values[task] = CancelledError() + while self.tasks: + task = self.tasks.popleft() + self.values[task] = await task.join() + if task.result.exc: + if task.result.exc != CancelledError: + await self._cancel_and_raise(task.result.exc) def spawn(self, coroutine: types.coroutine): """Schedules a task for execution, appending it to the call stack"""