giambio/giambio/traps.py

150 lines
4.4 KiB
Python

"""
Implementation for all giambio traps, which are hooks
into the event loop and allow it to switch tasks.
These coroutines are the one and only way to interact
with the event loop from the user's perspective, and
the entire library is based on them
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
http://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 types
@types.coroutine
def create_trap(method, *args):
"""
Creates and yields a trap. This
is the lowest-level method to
interact with the event loop
"""
data = yield method, *args
return data
async def sleep(seconds: int):
"""
Pause the execution of an async function for a given amount of seconds.
This function is functionally equivalent to time.sleep, but can be used
within async code without blocking everything else.
This function is also useful as a sort of checkpoint, because it returns
control to the scheduler, which can then switch to another task. If your code
doesn't have enough calls to async functions (or 'checkpoints') this might
prevent the scheduler from switching tasks properly. If you feel like this
happens in your code, try adding a call to await giambio.sleep(0) somewhere.
This will act as a checkpoint without actually pausing the execution
of your function, but it will allow the scheduler to switch tasks
:param seconds: The amount of seconds to sleep for
:type seconds: int
"""
assert seconds >= 0, "The time delay can't be negative"
await create_trap("sleep", seconds)
async def io_release(resource):
"""
Notifies the event loop to release
the resources associated to the given
socket and stop listening on it
"""
await create_trap("io_release", resource)
async def current_task():
"""
Gets the currently running task in an asynchronous fashion
"""
return await create_trap("get_current")
async def join(task):
"""
Awaits a given task for completion
:param task: The task to join
:type task: class: Task
"""
return await create_trap("join", task)
async def cancel(task):
"""
Cancels the given task.
The concept of cancellation is tricky, because there is no real way to 'stop'
a task if not by raising an exception inside it and ignoring whatever it
returns (and also hoping that the task won't cause collateral damage). It
is highly recommended that when you write async code you take into account
that it might be cancelled at any time. You might think to just ignore the
cancellation exception and be done with it, but doing so *will* break your
code, so if you really wanna do that be sure to re-raise it when done!
"""
await create_trap("cancel", task)
assert task.cancelled, f"Task ignored CancelledError"
async def want_read(stream):
"""
Notifies the event loop that a task wants to read from the given
resource
:param stream: The resource that needs to be read
"""
await create_trap("register_sock", stream, "read")
async def want_write(stream):
"""
Notifies the event loop that a task wants to write on the given
resource
:param stream: The resource that needs to be written
"""
await create_trap("register_sock", stream, "write")
async def event_set(event):
"""
Communicates to the loop that the given event object
must be set. This is important as the loop constantly
checks for active events to deliver them
"""
await create_trap("event_set", event)
async def event_wait(event):
"""
Notifies the event loop that the current task has to wait
for the given event to trigger. This trap returns
immediately if the event has already been set
"""
if event.set:
return
await create_trap("event_wait", event)