mirror of https://github.com/nocturn9x/giambio.git
stuff
This commit is contained in:
parent
398baadb74
commit
d2a20a14fc
|
@ -16,13 +16,13 @@ limitations under the License.
|
||||||
|
|
||||||
# Import libraries and internal resources
|
# Import libraries and internal resources
|
||||||
import types
|
import types
|
||||||
from collections import deque
|
from collections import deque, defaultdict
|
||||||
from selectors import DefaultSelector, EVENT_READ, EVENT_WRITE
|
from selectors import DefaultSelector, EVENT_READ, EVENT_WRITE
|
||||||
import socket
|
import socket
|
||||||
from .exceptions import AlreadyJoinedError, CancelledError, ResourceBusy
|
from .exceptions import AlreadyJoinedError, CancelledError, ResourceBusy, GiambioError
|
||||||
from timeit import default_timer
|
from timeit import default_timer
|
||||||
from time import sleep as wait
|
from time import sleep as wait
|
||||||
from .socket import AsyncSocket, WantWrite, WantRead
|
from .socket import AsyncSocket, WantWrite
|
||||||
from ._layers import Task, TimeQueue
|
from ._layers import Task, TimeQueue
|
||||||
from socket import SOL_SOCKET, SO_ERROR
|
from socket import SOL_SOCKET, SO_ERROR
|
||||||
from ._traps import want_read, want_write
|
from ._traps import want_read, want_write
|
||||||
|
@ -49,7 +49,7 @@ class AsyncScheduler:
|
||||||
self.clock = default_timer # Monotonic clock to keep track of elapsed time reliably
|
self.clock = default_timer # Monotonic clock to keep track of elapsed time reliably
|
||||||
self.paused = TimeQueue(self.clock) # Tasks that are asleep
|
self.paused = TimeQueue(self.clock) # Tasks that are asleep
|
||||||
self.events = {} # All Event objects
|
self.events = {} # All Event objects
|
||||||
self.event_waiting = {} # Coroutines waiting on event objects
|
self.event_waiting = defaultdict(list) # Coroutines waiting on event objects
|
||||||
self.sequence = 0
|
self.sequence = 0
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
@ -81,7 +81,7 @@ class AsyncScheduler:
|
||||||
try:
|
try:
|
||||||
if self.current_task.status == "cancel": # Deferred cancellation
|
if self.current_task.status == "cancel": # Deferred cancellation
|
||||||
self.current_task.cancelled = True
|
self.current_task.cancelled = True
|
||||||
self.current_task.throw(CancelledError)
|
self.current_task.throw(CancelledError(self.current_task))
|
||||||
method, *args = self.current_task.run(self.current_task._notify) # Run a single step with the calculation (and awake event-waiting tasks if any)
|
method, *args = self.current_task.run(self.current_task._notify) # Run a single step with the calculation (and awake event-waiting tasks if any)
|
||||||
self.current_task.status = "run"
|
self.current_task.status = "run"
|
||||||
getattr(self, method)(*args) # Sneaky method call, thanks to David Beazley for this ;)
|
getattr(self, method)(*args) # Sneaky method call, thanks to David Beazley for this ;)
|
||||||
|
@ -89,6 +89,7 @@ class AsyncScheduler:
|
||||||
self.check_events()
|
self.check_events()
|
||||||
except CancelledError as cancelled:
|
except CancelledError as cancelled:
|
||||||
self.tasks.remove(cancelled.args[0]) # Remove the dead task
|
self.tasks.remove(cancelled.args[0]) # Remove the dead 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
|
||||||
self.current_task.finished = True
|
self.current_task.finished = True
|
||||||
|
@ -101,18 +102,12 @@ class AsyncScheduler:
|
||||||
def check_events(self):
|
def check_events(self):
|
||||||
"""Checks for ready or expired events and triggers them"""
|
"""Checks for ready or expired events and triggers them"""
|
||||||
|
|
||||||
for event, (timeout, _, task) in self.event_waiting.copy().items():
|
for event, tasks in self.event_waiting.copy().items():
|
||||||
if timeout and self.clock() > timeout:
|
if event._set:
|
||||||
event._timeout_expired = True
|
|
||||||
event._notify = task._notify = None
|
|
||||||
self.tasks.append(task)
|
|
||||||
self.tasks.append(event.notifier)
|
|
||||||
self.event_waiting.pop(event)
|
|
||||||
elif event._set:
|
|
||||||
event.event_caught = True
|
event.event_caught = True
|
||||||
task._notify = event._notify
|
for task in tasks:
|
||||||
self.tasks.append(task)
|
task._notify = event._notify
|
||||||
self.tasks.append(event.notifier)
|
self.tasks.extend(tasks + [event.notifier])
|
||||||
self.event_waiting.pop(event)
|
self.event_waiting.pop(event)
|
||||||
|
|
||||||
def check_sleeping(self):
|
def check_sleeping(self):
|
||||||
|
@ -139,19 +134,33 @@ class AsyncScheduler:
|
||||||
self.tasks.append(task)
|
self.tasks.append(task)
|
||||||
return task
|
return task
|
||||||
|
|
||||||
|
def schedule_task(self, coro: types.coroutine, n: int):
|
||||||
|
"""Schedules a task for execution after n seconds"""
|
||||||
|
|
||||||
|
task = Task(coro)
|
||||||
|
self.paused.put(task, n)
|
||||||
|
return task
|
||||||
|
|
||||||
def start(self, coro: types.coroutine):
|
def start(self, coro: types.coroutine):
|
||||||
"""Starts the event loop using a coroutine as an entry point.
|
"""Starts the event loop using a coroutine as an entry point.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.create_task(coro)
|
entry = self.create_task(coro)
|
||||||
self.run()
|
crashed = False
|
||||||
|
try:
|
||||||
|
self.run()
|
||||||
|
except BaseException as exc:
|
||||||
|
entry.exc = exc
|
||||||
|
crashed = True
|
||||||
|
if crashed:
|
||||||
|
raise GiambioError("Event loop crashed!") from entry.exc
|
||||||
|
return entry
|
||||||
|
|
||||||
def reschedule_parent(self, coro):
|
def reschedule_parent(self, coro):
|
||||||
"""Reschedules the parent task"""
|
"""Reschedules the parent task"""
|
||||||
|
|
||||||
parent = self.joined.pop(coro, None)
|
parent = self.joined.pop(coro, None)
|
||||||
if parent:
|
if parent:
|
||||||
assert parent not in self.tasks
|
|
||||||
self.tasks.append(parent)
|
self.tasks.append(parent)
|
||||||
return parent
|
return parent
|
||||||
|
|
||||||
|
@ -197,13 +206,10 @@ class AsyncScheduler:
|
||||||
coroutine returns or, if an exception gets raised, the exception will get propagated inside the
|
coroutine returns or, if an exception gets raised, the exception will get propagated inside the
|
||||||
parent task"""
|
parent task"""
|
||||||
|
|
||||||
if child.finished:
|
if child not in self.joined:
|
||||||
self.tasks.append(self.current_task)
|
self.joined[child] = self.current_task
|
||||||
else:
|
else:
|
||||||
if child not in self.joined:
|
raise AlreadyJoinedError("Joining the same task multiple times is not allowed!")
|
||||||
self.joined[child] = self.current_task
|
|
||||||
else:
|
|
||||||
raise AlreadyJoinedError("Joining the same task multiple times is not allowed!")
|
|
||||||
|
|
||||||
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"""
|
||||||
|
@ -222,19 +228,17 @@ class AsyncScheduler:
|
||||||
event._notify = value
|
event._notify = value
|
||||||
self.events[event] = value
|
self.events[event] = value
|
||||||
|
|
||||||
def event_wait(self, event, timeout):
|
def event_wait(self, event):
|
||||||
"""Waits for an event"""
|
"""Waits for an event"""
|
||||||
|
|
||||||
self.sequence += 1
|
|
||||||
if timeout:
|
|
||||||
timeout = self.clock() + timeout
|
|
||||||
else:
|
|
||||||
timeout = 0
|
|
||||||
if self.events.get(event, None):
|
if self.events.get(event, None):
|
||||||
return self.events.pop(event)
|
event.waiting -= 1
|
||||||
|
if event.waiting <= 0:
|
||||||
|
return self.events.pop(event)
|
||||||
|
else:
|
||||||
|
return self.events[event]
|
||||||
else:
|
else:
|
||||||
self.event_waiting[event] = timeout, self.sequence, self.current_task
|
self.event_waiting[event].append(self.current_task)
|
||||||
self.event_waiting = dict(sorted(self.event_waiting.items()))
|
|
||||||
|
|
||||||
def cancel(self, task):
|
def cancel(self, task):
|
||||||
"""Handler for the 'cancel' event, throws CancelledError inside a coroutine
|
"""Handler for the 'cancel' event, throws CancelledError inside a coroutine
|
||||||
|
@ -245,8 +249,7 @@ class AsyncScheduler:
|
||||||
task.cancelled = True
|
task.cancelled = True
|
||||||
task.throw(CancelledError(task))
|
task.throw(CancelledError(task))
|
||||||
elif task.status == "run":
|
elif task.status == "run":
|
||||||
task.status = "cancel"
|
task.status = "cancel" # Cancellation is deferred
|
||||||
self.reschedule_parent()
|
|
||||||
|
|
||||||
def wrap_socket(self, sock):
|
def wrap_socket(self, sock):
|
||||||
"""Wraps a standard socket into an AsyncSocket object"""
|
"""Wraps a standard socket into an AsyncSocket object"""
|
||||||
|
|
|
@ -17,6 +17,8 @@ limitations under the License.
|
||||||
import types
|
import types
|
||||||
from ._traps import join, cancel, event_set, event_wait
|
from ._traps import join, cancel, event_set, event_wait
|
||||||
from heapq import heappop, heappush
|
from heapq import heappop, heappush
|
||||||
|
from .exceptions import GiambioError
|
||||||
|
|
||||||
|
|
||||||
class Task:
|
class Task:
|
||||||
|
|
||||||
|
@ -61,26 +63,29 @@ class Task:
|
||||||
class Event:
|
class Event:
|
||||||
"""A class designed similarly to threading.Event, but with more features"""
|
"""A class designed similarly to threading.Event, but with more features"""
|
||||||
|
|
||||||
def __init__(self, loop):
|
def __init__(self):
|
||||||
"""Object constructor"""
|
"""Object constructor"""
|
||||||
|
|
||||||
self._set = False
|
self._set = False
|
||||||
self._notify = None
|
self._notify = None
|
||||||
self.notifier = loop.current_task
|
|
||||||
self._timeout_expired = False
|
|
||||||
self.event_caught = False
|
self.event_caught = False
|
||||||
self.timeout = None
|
self.timeout = None
|
||||||
|
self.waiting = 0
|
||||||
|
|
||||||
async def set(self, value=None):
|
async def set(self, value=True):
|
||||||
"""Sets the event, optionally taking a value. This can be used
|
"""Sets the event, optionally taking a value. This can be used
|
||||||
to control tasks' flow by 'sending' commands back and fort"""
|
to control tasks' flow by 'sending' commands back and fort"""
|
||||||
|
|
||||||
|
if self._set:
|
||||||
|
raise GiambioError("The event has already been set")
|
||||||
await event_set(self, value)
|
await event_set(self, value)
|
||||||
|
|
||||||
async def pause(self, timeout=0):
|
async def pause(self):
|
||||||
"""Waits until the event is set and returns a value"""
|
"""Waits until the event is set and returns a value"""
|
||||||
|
|
||||||
return await event_wait(self, timeout)
|
self.waiting += 1
|
||||||
|
return await event_wait(self)
|
||||||
|
|
||||||
|
|
||||||
class TimeQueue:
|
class TimeQueue:
|
||||||
"""An abstraction layer over a heap queue based on time. This is where
|
"""An abstraction layer over a heap queue based on time. This is where
|
||||||
|
|
|
@ -48,7 +48,6 @@ def join(task):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
yield "join", task
|
yield "join", task
|
||||||
return task.result
|
|
||||||
|
|
||||||
|
|
||||||
@types.coroutine
|
@types.coroutine
|
||||||
|
@ -95,7 +94,7 @@ def event_set(event, value):
|
||||||
|
|
||||||
|
|
||||||
@types.coroutine
|
@types.coroutine
|
||||||
def event_wait(event, timeout: int):
|
def event_wait(event):
|
||||||
|
|
||||||
msg = yield "event_wait", event, timeout
|
msg = yield "event_wait", event
|
||||||
return msg
|
return msg
|
||||||
|
|
|
@ -25,6 +25,7 @@ async def main():
|
||||||
await giambio.sleep(2)
|
await giambio.sleep(2)
|
||||||
print("Slept 2 seconds, killing countup")
|
print("Slept 2 seconds, killing countup")
|
||||||
await cup.cancel() ## DOES NOT WORK!!!
|
await cup.cancel() ## DOES NOT WORK!!!
|
||||||
|
print("Countup cancelled")
|
||||||
await cup.join()
|
await cup.join()
|
||||||
await cdown.join()
|
await cdown.join()
|
||||||
print("Task execution complete")
|
print("Task execution complete")
|
||||||
|
|
|
@ -1,34 +1,30 @@
|
||||||
import giambio
|
import giambio
|
||||||
|
|
||||||
|
|
||||||
async def child(notifier: giambio.Event, timeout: int):
|
async def child(notifier: giambio.Event, reply: giambio.Event, pause: int):
|
||||||
print("[child] Child is alive!")
|
print("[child] Child is alive! Going to sleep until notified")
|
||||||
if timeout:
|
notification = await notifier.pause()
|
||||||
print(f"[child] Waiting for events for up to {timeout} seconds")
|
print(f"[child] Parent said: '{notification}', replying in {pause} seconds")
|
||||||
else:
|
await giambio.sleep(pause)
|
||||||
print("[child] Waiting for events")
|
print("[child] Replying to parent")
|
||||||
notification = await notifier.pause(timeout=timeout)
|
await reply.set("Hi daddy!")
|
||||||
if not notifier.event_caught:
|
|
||||||
print("[child] Parent was too slow!")
|
|
||||||
else:
|
|
||||||
print(f"[child] Parent said: {notification}")
|
|
||||||
|
|
||||||
async def parent(pause: int = 1, child_timeout: int = 0):
|
|
||||||
event = giambio.Event(scheduler)
|
async def parent(pause: int = 1):
|
||||||
|
event = giambio.Event()
|
||||||
|
reply = giambio.Event()
|
||||||
print("[parent] Spawning child task")
|
print("[parent] Spawning child task")
|
||||||
task = scheduler.create_task(child(event, child_timeout))
|
task = scheduler.create_task(child(event, reply, pause))
|
||||||
print(f"[parent] Sleeping {pause} second(s) before setting the event")
|
print(f"[parent] Sleeping {pause} second(s) before setting the event")
|
||||||
await giambio.sleep(pause)
|
await giambio.sleep(pause)
|
||||||
print("[parent] Event set")
|
|
||||||
await event.set("Hi, my child")
|
await event.set("Hi, my child")
|
||||||
if not event.event_caught:
|
print("[parent] Event set, awaiting reply")
|
||||||
print("[parent] Event not delivered, the timeout has expired")
|
reply = await reply.pause()
|
||||||
else:
|
print(f"[parent] Child replied: '{reply}'")
|
||||||
print("[parent] Event delivered")
|
|
||||||
await task.join()
|
await task.join()
|
||||||
print("[parent] Child exited")
|
print("[parent] Child exited")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
scheduler = giambio.AsyncScheduler()
|
scheduler = giambio.AsyncScheduler()
|
||||||
scheduler.start(parent(4, 5))
|
scheduler.start(parent(5))
|
||||||
|
|
Loading…
Reference in New Issue