This repository has been archived on 2023-05-12. You can view files and clone it, but cannot push or open issues or pull requests.
aiosched/aiosched/internals/syscalls.py

173 lines
4.7 KiB
Python

"""
aiosched: I'm bored and I'm making an async event loop again
Copyright (C) 2020 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.
"""
import inspect
from types import coroutine
from typing import Any, Callable, Coroutine
from aiosched.task import Task, TaskState
from aiosched.errors import SchedulerError
from selectors import EVENT_READ, EVENT_WRITE
@coroutine
def syscall(method: str, *args, **kwargs) -> Any | None:
"""
Lowest-level primitive to interact with the event loop:
calls a loop method with the provided arguments. This
function should not be used directly, but through abstraction
layers. All positional and keyword arguments are passed to
the method itself and its return value is provided once the
loop yields control back to us
:param method: The loop method to call
:type method: str
:returns: The result of the method call, if any
"""
result = yield method, args, kwargs
return result
async def spawn(func: Callable[..., Coroutine[Any, Any, Any]], *args, **kwargs) -> Task:
"""
Spawns a task from a coroutine and returns it. The coroutine
is put on the running cute and is executed as soon as possible.
Any positional and keyword arguments are passed along to the coroutine
:param func: The coroutine function to instantiate. Note that this should NOT
be a coroutine object: the arguments to the coroutine should be passed to
spawn as well
:return:
"""
if inspect.iscoroutine(func):
raise TypeError(
"Looks like you tried to call spawn(your_func(arg1, arg2, ...)), that is wrong!"
"\nWhat you wanna do, instead, is this: spawn(your_func, arg1, arg2, ...)"
)
elif inspect.iscoroutinefunction(func):
return await syscall("spawn", func, *args, **kwargs)
else:
raise TypeError("func must be a coroutine function")
async def sleep(delay: int | float):
"""
Puts the calling task to sleep for the
given amount of time. If the delay is equal
to zero, this call acts as a checkpoint to
perform task switching and is useful to release
pressure from the scheduler in highly concurrent
environments
:param delay: The amount of time (in seconds) that
the task has to be put to sleep for. Must be
greater than zero
:type delay: int | float
"""
await syscall("sleep", delay)
async def checkpoint():
"""
Shorthand for sleep(0)
"""
await sleep(0)
async def suspend():
"""
Suspends the current task. The task is not
rescheduled until some other event (for example
a timer, an event or an I/O operation) reschedules
it
"""
await syscall("suspend")
async def wait(task: Task) -> Any | None:
"""
Waits for the completion of a
given task and returns its
return value. Can be called
multiple times by multiple tasks.
Raises an error if the task has
completed already. Please note that
exceptions are propagated, too
:param task: The task to wait for
:type task: :class: Task
:returns: The task's return value, if any
"""
if task.done():
raise SchedulerError(f"task {task.name!r} has completed already")
await syscall("wait", task)
if task.exc:
raise task.exc
return task.result
async def cancel(task: Task):
"""
Cancels the given task. Note that
cancellations may not happen immediately
if the task is blocked in an uninterruptible
state
:param task: The task to wait for
:type task: :class: Task
"""
await syscall("cancel", task)
if task.state != TaskState.CANCELLED:
raise SchedulerError(f"task {task.name!r} ignored cancellation")
async def closing(stream):
"""
Notifies the event loop that the
given stream is about to be closed,
causing all callers waiting on it
to error out with an exception instead
of blocking forever
"""
await syscall("notify_closing", stream)
async def wait_readable(stream):
"""
Waits until the given stream is
readable
"""
await syscall("perform_io", stream, EVENT_READ)
async def wait_writable(stream):
"""
Waits until the given stream is
writable
"""
await syscall("perform_io", stream, EVENT_WRITE)