2020-11-17 10:54:18 +01:00
|
|
|
"""
|
|
|
|
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
|
2022-09-01 13:18:15 +02:00
|
|
|
the entire library is based on them. These low-level
|
|
|
|
primitives should not be used on their own unless you
|
|
|
|
know what you're doing: the library already abstracts
|
|
|
|
them away with more complex object wrappers and functions
|
2020-11-17 10:54:18 +01:00
|
|
|
|
|
|
|
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
|
|
|
|
|
2022-09-01 13:18:15 +02:00
|
|
|
https://www.apache.org/licenses/LICENSE-2.0
|
2020-11-17 10:54:18 +01:00
|
|
|
|
|
|
|
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
|
2021-06-08 17:21:59 +02:00
|
|
|
import inspect
|
|
|
|
from giambio.task import Task
|
2022-02-05 16:14:21 +01:00
|
|
|
from typing import Any, Union, Iterable, Coroutine, Callable
|
2021-06-08 17:21:59 +02:00
|
|
|
from giambio.exceptions import GiambioError
|
2020-11-17 10:54:18 +01:00
|
|
|
|
|
|
|
|
|
|
|
@types.coroutine
|
|
|
|
def create_trap(method, *args):
|
|
|
|
"""
|
|
|
|
Creates and yields a trap. This
|
|
|
|
is the lowest-level method to
|
|
|
|
interact with the event loop
|
|
|
|
"""
|
|
|
|
|
2022-09-01 13:18:15 +02:00
|
|
|
return (yield method, *args)
|
2020-11-17 10:54:18 +01:00
|
|
|
|
|
|
|
|
2022-02-04 20:19:48 +01:00
|
|
|
async def suspend() -> Any:
|
|
|
|
"""
|
2022-09-01 13:18:15 +02:00
|
|
|
Suspends the current task. The
|
|
|
|
task can still be woken up by
|
|
|
|
any pending timers, I/O or cancellations
|
2022-02-04 20:19:48 +01:00
|
|
|
"""
|
|
|
|
|
|
|
|
return await create_trap("suspend")
|
|
|
|
|
|
|
|
|
2022-09-01 13:18:15 +02:00
|
|
|
async def create_task(coro: Callable[[Any, Any], Coroutine[Any, Any, Any]], pool, *args, **kwargs):
|
2021-06-08 17:21:59 +02:00
|
|
|
"""
|
|
|
|
Spawns a new task in the current event loop from a bare coroutine
|
|
|
|
function. All extra positional arguments are passed to the function
|
|
|
|
|
|
|
|
This trap should *NOT* be used on its own, it is meant to be
|
|
|
|
called from internal giambio machinery
|
|
|
|
"""
|
|
|
|
|
|
|
|
if inspect.iscoroutine(coro):
|
|
|
|
raise GiambioError(
|
2022-09-01 13:18:15 +02:00
|
|
|
"Looks like you tried to call pool.create_task(your_func(arg1, arg2, ...)), that is wrong!"
|
|
|
|
"\nWhat you wanna do, instead, is this: pool.create_task(your_func, arg1, arg2, ...)"
|
2021-06-08 17:21:59 +02:00
|
|
|
)
|
|
|
|
elif inspect.iscoroutinefunction(coro):
|
2022-09-01 13:18:15 +02:00
|
|
|
return await create_trap("create_task", coro(*args, **kwargs), pool)
|
2021-06-08 17:21:59 +02:00
|
|
|
else:
|
2021-07-22 11:13:08 +02:00
|
|
|
raise TypeError("coro must be a coroutine function")
|
2021-06-08 17:21:59 +02:00
|
|
|
|
|
|
|
|
|
|
|
async def sleep(seconds: Union[int, float]):
|
2020-11-17 10:54:18 +01:00
|
|
|
"""
|
2021-06-03 16:34:26 +02:00
|
|
|
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.
|
2020-11-17 10:54:18 +01:00
|
|
|
|
|
|
|
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
|
2021-06-03 16:34:26 +02:00
|
|
|
happens in your code, try adding a call to await giambio.sleep(0) somewhere.
|
2020-11-17 10:54:18 +01:00
|
|
|
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)
|
|
|
|
|
|
|
|
|
2021-06-03 16:34:26 +02:00
|
|
|
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)
|
|
|
|
|
|
|
|
|
2020-11-17 10:54:18 +01:00
|
|
|
async def current_task():
|
|
|
|
"""
|
2020-12-19 15:18:12 +01:00
|
|
|
Gets the currently running task in an asynchronous fashion
|
2020-11-17 10:54:18 +01:00
|
|
|
"""
|
|
|
|
|
2021-06-08 17:21:59 +02:00
|
|
|
return await create_trap("get_current_task")
|
|
|
|
|
|
|
|
|
|
|
|
async def current_loop():
|
|
|
|
"""
|
|
|
|
Gets the currently running loop in an asynchronous fashion
|
|
|
|
"""
|
|
|
|
|
|
|
|
return await create_trap("get_current_loop")
|
|
|
|
|
|
|
|
|
|
|
|
async def current_pool():
|
|
|
|
"""
|
|
|
|
Gets the currently active task pool in an asynchronous fashion
|
|
|
|
"""
|
|
|
|
|
|
|
|
return await create_trap("get_current_pool")
|
2020-11-17 10:54:18 +01:00
|
|
|
|
|
|
|
|
|
|
|
async def join(task):
|
|
|
|
"""
|
|
|
|
Awaits a given task for completion
|
|
|
|
|
|
|
|
:param task: The task to join
|
2021-07-22 11:13:08 +02:00
|
|
|
:type task: :class: Task
|
2020-11-17 10:54:18 +01:00
|
|
|
"""
|
|
|
|
|
2020-11-22 14:35:07 +01:00
|
|
|
return await create_trap("join", task)
|
2020-11-17 10:54:18 +01:00
|
|
|
|
|
|
|
|
|
|
|
async def cancel(task):
|
|
|
|
"""
|
2020-12-05 17:09:59 +01:00
|
|
|
Cancels the given task.
|
2020-11-17 10:54:18 +01:00
|
|
|
|
|
|
|
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!
|
|
|
|
"""
|
|
|
|
|
2020-11-27 21:52:45 +01:00
|
|
|
await create_trap("cancel", task)
|
2022-02-27 12:41:23 +01:00
|
|
|
assert task.done(), f"Task ignored CancelledError"
|
2020-11-17 10:54:18 +01:00
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
"""
|
|
|
|
|
2020-12-20 15:58:53 +01:00
|
|
|
await create_trap("register_sock", stream, "read")
|
2020-11-17 10:54:18 +01:00
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
"""
|
|
|
|
|
2020-12-20 15:58:53 +01:00
|
|
|
await create_trap("register_sock", stream, "write")
|
2020-11-17 10:54:18 +01:00
|
|
|
|
|
|
|
|
2022-02-05 12:37:05 +01:00
|
|
|
async def schedule_tasks(tasks: Iterable[Task]):
|
|
|
|
"""
|
|
|
|
Schedules a list of tasks for execution. Usuaully
|
|
|
|
used to unsuspend them after they called suspend()
|
|
|
|
"""
|
|
|
|
|
|
|
|
await create_trap("schedule_tasks", tasks)
|
|
|
|
|
|
|
|
|
2020-11-17 10:54:18 +01:00
|
|
|
async def event_wait(event):
|
|
|
|
"""
|
|
|
|
Notifies the event loop that the current task has to wait
|
2020-12-05 17:09:59 +01:00
|
|
|
for the given event to trigger. This trap returns
|
|
|
|
immediately if the event has already been set
|
2020-11-17 10:54:18 +01:00
|
|
|
"""
|
|
|
|
|
2020-12-05 17:09:59 +01:00
|
|
|
if event.set:
|
|
|
|
return
|
2021-08-27 10:32:42 +02:00
|
|
|
task = await current_task()
|
|
|
|
event.waiters.add(task)
|
2022-02-26 21:59:18 +01:00
|
|
|
return await suspend()
|
2021-06-08 17:21:59 +02:00
|
|
|
|
|
|
|
|
2022-02-27 12:41:23 +01:00
|
|
|
async def event_set(event, value):
|
2021-06-08 17:21:59 +02:00
|
|
|
"""
|
|
|
|
Sets the given event and reawakens its
|
|
|
|
waiters
|
|
|
|
"""
|
|
|
|
|
|
|
|
event.set = True
|
2022-02-27 12:41:23 +01:00
|
|
|
event.value = value
|
2022-02-05 13:00:48 +01:00
|
|
|
loop = await current_loop()
|
|
|
|
for waiter in event.waiters:
|
|
|
|
loop._data[waiter] = event.value
|
2022-05-10 11:56:47 +02:00
|
|
|
await schedule_tasks(event.waiters)
|