Added Event class and reformatted with black
This commit is contained in:
parent
3021544e7f
commit
c2bb63149b
|
@ -20,7 +20,7 @@ from aiosched.internals.syscalls import spawn, wait, sleep, cancel
|
||||||
import aiosched.task
|
import aiosched.task
|
||||||
import aiosched.errors
|
import aiosched.errors
|
||||||
import aiosched.context
|
import aiosched.context
|
||||||
|
from aiosched.sync import Event
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"run",
|
"run",
|
||||||
|
@ -33,4 +33,5 @@ __all__ = [
|
||||||
"errors",
|
"errors",
|
||||||
"cancel",
|
"cancel",
|
||||||
"with_context",
|
"with_context",
|
||||||
|
"Event",
|
||||||
]
|
]
|
||||||
|
|
|
@ -17,7 +17,14 @@ limitations under the License.
|
||||||
"""
|
"""
|
||||||
from aiosched.task import Task
|
from aiosched.task import Task
|
||||||
from aiosched.errors import Cancelled
|
from aiosched.errors import Cancelled
|
||||||
from aiosched.internals.syscalls import spawn, wait, cancel, set_context, close_context, join
|
from aiosched.internals.syscalls import (
|
||||||
|
spawn,
|
||||||
|
wait,
|
||||||
|
cancel,
|
||||||
|
set_context,
|
||||||
|
close_context,
|
||||||
|
join,
|
||||||
|
)
|
||||||
from typing import Any, Coroutine, Callable
|
from typing import Any, Coroutine, Callable
|
||||||
|
|
||||||
|
|
||||||
|
@ -139,7 +146,10 @@ class TaskContext(Task):
|
||||||
continue
|
continue
|
||||||
if not task.done():
|
if not task.done():
|
||||||
return False
|
return False
|
||||||
if not isinstance(self.entry_point, TaskContext) and not self.entry_point.done():
|
if (
|
||||||
|
not isinstance(self.entry_point, TaskContext)
|
||||||
|
and not self.entry_point.done()
|
||||||
|
):
|
||||||
return False
|
return False
|
||||||
if self.inner:
|
if self.inner:
|
||||||
return self.inner.done()
|
return self.inner.done()
|
||||||
|
|
|
@ -96,9 +96,7 @@ class ErrorStack(SchedulerError):
|
||||||
tracebacks = ""
|
tracebacks = ""
|
||||||
for i, err in enumerate(self.errors):
|
for i, err in enumerate(self.errors):
|
||||||
if i not in (1, len(self.errors)):
|
if i not in (1, len(self.errors)):
|
||||||
tracebacks += (
|
tracebacks += f"\n{''.join(traceback.format_exception(type(err), err, err.__traceback__))}\n{'-' * 32}\n"
|
||||||
f"\n{''.join(traceback.format_exception(type(err), err, err.__traceback__))}\n{'-' * 32}\n"
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
tracebacks += f"\n{''.join(traceback.format_exception(type(err), err, err.__traceback__))}"
|
tracebacks += f"\n{''.join(traceback.format_exception(type(err), err, err.__traceback__))}"
|
||||||
return f"Multiple errors occurred:\n{tracebacks}"
|
return f"Multiple errors occurred:\n{tracebacks}"
|
||||||
|
|
|
@ -43,6 +43,14 @@ def syscall(method: str, *args, **kwargs) -> Any | None:
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
async def schedule(task: Task):
|
||||||
|
"""
|
||||||
|
Reschedules a task that had been
|
||||||
|
previously suspended
|
||||||
|
"""
|
||||||
|
|
||||||
|
await syscall("schedule", task)
|
||||||
|
|
||||||
async def spawn(func: Callable[..., Coroutine[Any, Any, Any]], *args, **kwargs) -> Task:
|
async def spawn(func: Callable[..., Coroutine[Any, Any, Any]], *args, **kwargs) -> Task:
|
||||||
"""
|
"""
|
||||||
Spawns a task from a coroutine and returns it. The coroutine
|
Spawns a task from a coroutine and returns it. The coroutine
|
||||||
|
|
|
@ -23,7 +23,13 @@ from timeit import default_timer
|
||||||
from aiosched.internals.queue import TimeQueue
|
from aiosched.internals.queue import TimeQueue
|
||||||
from aiosched.util.debugging import BaseDebugger
|
from aiosched.util.debugging import BaseDebugger
|
||||||
from typing import Callable, Any, Coroutine
|
from typing import Callable, Any, Coroutine
|
||||||
from aiosched.errors import InternalError, ResourceBusy, Cancelled, ResourceClosed, ResourceBroken
|
from aiosched.errors import (
|
||||||
|
InternalError,
|
||||||
|
ResourceBusy,
|
||||||
|
Cancelled,
|
||||||
|
ResourceClosed,
|
||||||
|
ResourceBroken,
|
||||||
|
)
|
||||||
from aiosched.context import TaskContext
|
from aiosched.context import TaskContext
|
||||||
from selectors import DefaultSelector, BaseSelector
|
from selectors import DefaultSelector, BaseSelector
|
||||||
|
|
||||||
|
@ -118,7 +124,9 @@ class FIFOKernel:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not self.done() and not force:
|
if not self.done() and not force:
|
||||||
self.current_task.throw(InternalError("cannot shut down a running event loop"))
|
self.current_task.throw(
|
||||||
|
InternalError("cannot shut down a running event loop")
|
||||||
|
)
|
||||||
for task in self.all():
|
for task in self.all():
|
||||||
self.cancel(task)
|
self.cancel(task)
|
||||||
|
|
||||||
|
@ -185,6 +193,15 @@ class FIFOKernel:
|
||||||
|
|
||||||
self.run_ready.append(self.current_task)
|
self.run_ready.append(self.current_task)
|
||||||
|
|
||||||
|
def schedule(self, task: Task):
|
||||||
|
"""
|
||||||
|
Schedules a task that was previously
|
||||||
|
suspended
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.run_ready.append(task)
|
||||||
|
self.reschedule_running()
|
||||||
|
|
||||||
def suspend(self):
|
def suspend(self):
|
||||||
"""
|
"""
|
||||||
Suspends execution of the current task. This is basically
|
Suspends execution of the current task. This is basically
|
||||||
|
@ -213,7 +230,7 @@ class FIFOKernel:
|
||||||
# We need to make sure we don't try to execute
|
# We need to make sure we don't try to execute
|
||||||
# exited tasks that are on the running queue
|
# exited tasks that are on the running queue
|
||||||
if not self.run_ready:
|
if not self.run_ready:
|
||||||
return # No more tasks to run!
|
return # No more tasks to run!
|
||||||
self.current_task = self.run_ready.popleft()
|
self.current_task = self.run_ready.popleft()
|
||||||
self.debugger.before_task_step(self.current_task)
|
self.debugger.before_task_step(self.current_task)
|
||||||
# Some debugging and internal chatter here
|
# Some debugging and internal chatter here
|
||||||
|
@ -226,15 +243,19 @@ class FIFOKernel:
|
||||||
else:
|
else:
|
||||||
# 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 = self.current_task.run(self.data.pop(self.current_task, None))
|
method, args, kwargs = self.current_task.run(
|
||||||
|
self.data.pop(self.current_task, None)
|
||||||
|
)
|
||||||
if not hasattr(self, method) or not callable(getattr(self, method)):
|
if not hasattr(self, method) or not callable(getattr(self, method)):
|
||||||
# This if block is meant to be triggered by other async
|
# This if block is meant to be triggered by other async
|
||||||
# libraries, which most likely have different trap names and behaviors
|
# libraries, which most likely have different trap names and behaviors
|
||||||
# compared to us. If you get this exception, and you're 100% sure you're
|
# compared to us. If you get this exception, and you're 100% sure you're
|
||||||
# not mixing async primitives from other libraries, then it's a bug!
|
# not mixing async primitives from other libraries, then it's a bug!
|
||||||
self.current_task.throw(InternalError(
|
self.current_task.throw(
|
||||||
"Uh oh! Something very bad just happened, did you try to mix primitives from other async libraries?"
|
InternalError(
|
||||||
))
|
"Uh oh! Something very bad just happened, did you try to mix primitives from other async libraries?"
|
||||||
|
)
|
||||||
|
)
|
||||||
# Sneaky method call, thanks to David Beazley for this ;)
|
# Sneaky method call, thanks to David Beazley for this ;)
|
||||||
getattr(self, method)(*args, **kwargs)
|
getattr(self, method)(*args, **kwargs)
|
||||||
self.debugger.after_task_step(self.current_task)
|
self.debugger.after_task_step(self.current_task)
|
||||||
|
@ -289,7 +310,11 @@ class FIFOKernel:
|
||||||
self.run()
|
self.run()
|
||||||
finally:
|
finally:
|
||||||
self.debugger.on_exit()
|
self.debugger.on_exit()
|
||||||
if self.entry_point.exc and self.entry_point.context is None and self.entry_point.propagate:
|
if (
|
||||||
|
self.entry_point.exc
|
||||||
|
and self.entry_point.context is None
|
||||||
|
and self.entry_point.propagate
|
||||||
|
):
|
||||||
# Contexts already manage exceptions for us,
|
# Contexts already manage exceptions for us,
|
||||||
# no need to raise it manually
|
# no need to raise it manually
|
||||||
raise self.entry_point.exc
|
raise self.entry_point.exc
|
||||||
|
@ -336,9 +361,7 @@ class FIFOKernel:
|
||||||
dict(self.selector.get_map()).values(),
|
dict(self.selector.get_map()).values(),
|
||||||
):
|
):
|
||||||
for task in k.data:
|
for task in k.data:
|
||||||
self.handle_task_run(
|
self.handle_task_run(partial(task.throw, exc), task)
|
||||||
partial(task.throw, exc), task
|
|
||||||
)
|
|
||||||
|
|
||||||
def cancel(self, task: Task):
|
def cancel(self, task: Task):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -62,11 +62,7 @@ def new_event_loop(clock_function: Callable, debugger: BaseDebugger | None = Non
|
||||||
local_storage.loop = FIFOKernel(clock_function, debugger)
|
local_storage.loop = FIFOKernel(clock_function, debugger)
|
||||||
|
|
||||||
|
|
||||||
def run(
|
def run(func: Callable[[Any, Any], Coroutine[Any, Any, Any]], *args, **kwargs):
|
||||||
func: Callable[[Any, Any], Coroutine[Any, Any, Any]],
|
|
||||||
*args,
|
|
||||||
**kwargs
|
|
||||||
):
|
|
||||||
"""
|
"""
|
||||||
Starts the event loop from a synchronous entry point
|
Starts the event loop from a synchronous entry point
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
"""
|
||||||
|
aiosched: Yet another Python async scheduler
|
||||||
|
|
||||||
|
Copyright (C) 2022 nocturn9x
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
https:www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
"""
|
||||||
|
from typing import Any
|
||||||
|
from aiosched.errors import SchedulerError
|
||||||
|
from aiosched.internals.syscalls import (
|
||||||
|
suspend,
|
||||||
|
schedule,
|
||||||
|
current_task,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Event:
|
||||||
|
"""
|
||||||
|
An asynchronous, non thread-safe event
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""
|
||||||
|
Object constructor
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.set = False
|
||||||
|
self.waiters = set()
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
"""
|
||||||
|
Resets the event's state
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.__init__()
|
||||||
|
|
||||||
|
async def trigger(self):
|
||||||
|
"""
|
||||||
|
Sets the event, waking up all tasks that called
|
||||||
|
wait() on it
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.set:
|
||||||
|
raise SchedulerError("The event has already been set")
|
||||||
|
self.set = True
|
||||||
|
for waiter in self.waiters:
|
||||||
|
await schedule(waiter)
|
||||||
|
|
||||||
|
async def wait(self) -> Any:
|
||||||
|
"""
|
||||||
|
Waits until the event is set
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.waiters.add(await current_task())
|
||||||
|
await suspend() # We get unsuspended by trigger()
|
|
@ -32,5 +32,6 @@ async def main(children: list[tuple[str, int]]):
|
||||||
print(f"[main] Child #{i + 1} has exited")
|
print(f"[main] Child #{i + 1} has exited")
|
||||||
print(f"[main] Children exited in {aiosched.clock() - before:.2f} seconds")
|
print(f"[main] Children exited in {aiosched.clock() - before:.2f} seconds")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
aiosched.run(main, [("first", 1), ("second", 2), ("third", 3)])
|
aiosched.run(main, [("first", 1), ("second", 2), ("third", 3)])
|
||||||
|
|
|
@ -11,7 +11,9 @@ async def main(children: list[tuple[str, int]]):
|
||||||
print("[main] Children spawned")
|
print("[main] Children spawned")
|
||||||
before = aiosched.clock()
|
before = aiosched.clock()
|
||||||
if ctx.exc:
|
if ctx.exc:
|
||||||
print(f"[main] Child raised an exception -> {type(ctx.exc).__name__}: {ctx.exc}")
|
print(
|
||||||
|
f"[main] Child raised an exception -> {type(ctx.exc).__name__}: {ctx.exc}"
|
||||||
|
)
|
||||||
print(f"[main] Children exited in {aiosched.clock() - before:.2f} seconds")
|
print(f"[main] Children exited in {aiosched.clock() - before:.2f} seconds")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -14,4 +14,4 @@ async def main(children: list[tuple[str, int]]):
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
aiosched.run(main, [("first", 1), ("second", 2), ("third", 3)], debugger=None)
|
aiosched.run(main, [("first", 1), ("second", 2), ("third", 3)], debugger=None)
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
from debugger import Debugger
|
||||||
|
import aiosched
|
||||||
|
|
||||||
|
|
||||||
|
async def child(ev: aiosched.Event, pause: int):
|
||||||
|
print("[child] Child is alive! Going to wait until notified")
|
||||||
|
start_total = aiosched.clock()
|
||||||
|
await ev.wait()
|
||||||
|
end_pause = aiosched.clock() - start_total
|
||||||
|
print(f"[child] Parent set the event with, exiting in {pause} seconds")
|
||||||
|
start_sleep = aiosched.clock()
|
||||||
|
await aiosched.sleep(pause)
|
||||||
|
end_sleep = aiosched.clock() - start_sleep
|
||||||
|
end_total = aiosched.clock() - start_total
|
||||||
|
print(
|
||||||
|
f"[child] Done! Slept for {end_total:.2f} seconds total ({end_pause:.2f} waiting, {end_sleep:.2f} sleeping), nice nap!"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def parent(pause: int = 1):
|
||||||
|
async with aiosched.with_context() as ctx:
|
||||||
|
event = aiosched.Event()
|
||||||
|
print("[parent] Spawning child task")
|
||||||
|
await ctx.spawn(child, event, pause + 2)
|
||||||
|
start = aiosched.clock()
|
||||||
|
print(f"[parent] Sleeping {pause} second(s) before setting the event")
|
||||||
|
await aiosched.sleep(pause)
|
||||||
|
await event.trigger()
|
||||||
|
print("[parent] Event set, awaiting child completion")
|
||||||
|
end = aiosched.clock() - start
|
||||||
|
print(f"[parent] Child exited in {end:.2f} seconds")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
aiosched.run(parent, 3, debugger=None)
|
|
@ -4,7 +4,9 @@ from wait import child as successful
|
||||||
from debugger import Debugger
|
from debugger import Debugger
|
||||||
|
|
||||||
|
|
||||||
async def main(children_outer: list[tuple[str, int]], children_inner: list[tuple[str, int]]):
|
async def main(
|
||||||
|
children_outer: list[tuple[str, int]], children_inner: list[tuple[str, int]]
|
||||||
|
):
|
||||||
before = aiosched.clock()
|
before = aiosched.clock()
|
||||||
async with aiosched.with_context() as ctx:
|
async with aiosched.with_context() as ctx:
|
||||||
print("[main] Spawning children in first context")
|
print("[main] Spawning children in first context")
|
||||||
|
@ -23,4 +25,9 @@ async def main(children_outer: list[tuple[str, int]], children_inner: list[tuple
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
aiosched.run(main, [("first", 1), ("second", 2)], [("third", 3), ("fourth", 4)], debugger=None)
|
aiosched.run(
|
||||||
|
main,
|
||||||
|
[("first", 1), ("second", 2)],
|
||||||
|
[("third", 3), ("fourth", 4)],
|
||||||
|
debugger=None,
|
||||||
|
)
|
||||||
|
|
|
@ -4,7 +4,9 @@ from debugger import Debugger
|
||||||
|
|
||||||
|
|
||||||
# TODO: This crashes 1 second later than it should be
|
# TODO: This crashes 1 second later than it should be
|
||||||
async def main(children_outer: list[tuple[str, int]], children_inner: list[tuple[str, int]]):
|
async def main(
|
||||||
|
children_outer: list[tuple[str, int]], children_inner: list[tuple[str, int]]
|
||||||
|
):
|
||||||
try:
|
try:
|
||||||
async with aiosched.with_context() as ctx:
|
async with aiosched.with_context() as ctx:
|
||||||
before = aiosched.clock()
|
before = aiosched.clock()
|
||||||
|
@ -23,4 +25,9 @@ async def main(children_outer: list[tuple[str, int]], children_inner: list[tuple
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
aiosched.run(main, [("first", 1), ("second", 2)], [("third", 3), ("fourth", 4)], debugger=None)
|
aiosched.run(
|
||||||
|
main,
|
||||||
|
[("first", 1), ("second", 2)],
|
||||||
|
[("third", 3), ("fourth", 4)],
|
||||||
|
debugger=None,
|
||||||
|
)
|
||||||
|
|
|
@ -3,7 +3,9 @@ from wait import child
|
||||||
from debugger import Debugger
|
from debugger import Debugger
|
||||||
|
|
||||||
|
|
||||||
async def main(children_outer: list[tuple[str, int]], children_inner: list[tuple[str, int]]):
|
async def main(
|
||||||
|
children_outer: list[tuple[str, int]], children_inner: list[tuple[str, int]]
|
||||||
|
):
|
||||||
async with aiosched.with_context() as ctx:
|
async with aiosched.with_context() as ctx:
|
||||||
before = aiosched.clock()
|
before = aiosched.clock()
|
||||||
print("[main] Spawning children in first context")
|
print("[main] Spawning children in first context")
|
||||||
|
@ -19,4 +21,9 @@ async def main(children_outer: list[tuple[str, int]], children_inner: list[tuple
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
aiosched.run(main, [("first", 1), ("second", 2)], [("third", 3), ("fourth", 4)], debugger=None)
|
aiosched.run(
|
||||||
|
main,
|
||||||
|
[("first", 1), ("second", 2)],
|
||||||
|
[("third", 3), ("fourth", 4)],
|
||||||
|
debugger=None,
|
||||||
|
)
|
||||||
|
|
Reference in New Issue