-
-
Notifications
You must be signed in to change notification settings - Fork 31.9k
Asyncio: Send arbitrary exceptions to Tasks #102847
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Since I mentioned synchronous event delivery, maybe this is as good a spot as any to plug my little async library, asynkit. It does, among other things, provide tools to control task switching by subclassing the EventLoop, such as async def acancel(self, msg):
self.cancel(msg)
await asynkit.task_switch(self) |
(We can't have a method named It would not be very hard to add this (at least the version that doesn't block until the exception is delivered), but I'm not sure I like the prospect of doing I'd need even more convincing for I'm also not convinced that |
yes, reserved keywords :) So, some free thoughts here, no scenarios yet, just thinking out loud a bit. Hope you don't mind. I think that what I'm suggesting is not something that should be generally employed by application-layer programmers, but having the mechanism in place for specialized low-level programming would be beneficial. The only real cases I can think of are I myself am also not sure that an async version of cancel is a patent solution to race conditions. It is just something I'm suggesting based on experience in my old stackless life, I recall that it was simpler to reason about task state by knowing that no other tasks would get in the way once you had decided what state change to perform. The way I implement Thinking about this, there is no way to synchronously and non-destructively send an exception to a runnable task, because that would overwrite the result that has already been delivered to it (e.g. a read() result) and then, the task wouldn't be able to, for example, re-try. Data has been lost. (stackless could do it, but it was a deliberately low level. someone sending an exception was supposed to know what he was doing, and also could query the state of the target tasklet)
In the first case, it is possible to immediately deliver an exception, put the task on the ready _queue, and optionally make it the next to run (pausing the caller, i.e. switching to it). (asynkit has I guess I need to dig better into |
User / non-core developer view, please take with a grain of salt. ~ Immediate switch to target task is weird, what if someone does I think being able to "[async-atomically] raise in a task and await that task" is a sufficient guarantee. Ultimately, it's always possible for yet another ~ What I'm thinking is that Would it be enough to pass a specific instance of I think that would work in the case of nested cc @ojii |
So, I took a look at As background information, where I'm (originally) coming from: In Anyway: In CPython, cancels are done as follows (ignoring details)
Now, the above is all fine and good if all we want to do is send Already with I realize that the case for a "ready queue" and "task scheduling" is a bit alien to asyncio, since it is designed around scheduling abstract callbacks, not tasks. But immediate task switching has the benefit that one can reason about program logic and is one of the benefits of cooperative multi-tasking. So, in short, I think that if we want to send arbitrary exceptions to tasks (as an advanced tool) it would be simplest to do so via some api like this: async def araise(self, exception: BaseException)->None:
"""
Arrange for `exception` to be sent to the task. The target task
is immediately scheduled. The target task must belong to the
same event loop as the caller, otherwise a RuntimeError is raised.
""" ...
Both of these will be wrapped in their own tasks and executed. Nothing interesting will happen. There is nothing inherently weird about immediate delivery. Think of it more like
Indeed, this is exactly how I think such things should be implemented. In fact, I would suggest a different BaseException, such as a BaseTimeoutError, to be used for timeout delivery. The problem is, in the absence of immediate delivery of an exception, that there are all kinds of complicated race situations possible. What if the CancelledError, or BaseTimeoutError, has been scheduled, but then someone else schedules another exception? Which one wins? One of the two instances must be discarded. Can we even make sense out of such behaviour? This is why the current |
It sounds to me that it isn't worth changing cancel semantics at this point, even if your way is better -- existing code depending on the current semantics would break and there's already enough of an ecosystem around asyncio that I'm not comfortable with that. Maybe you'll find a more sympathetic ear in the Trio community? |
I was going to suggest [Base]ExceptionGroup to accumulate several cancellations while a task is blocked/pending. However The next step would be Maybe reparenting P.S. IMHO, the immediate semantics are orthogonal to the issue of multiple exceptions pending for a task. |
Yes, this is "Realpolitik" in action :). I just also realized a few additional points that I think I should note here, for reference:
Not really, no, because the immediate semantics mean that there can be no multiple pending exceptions. If signal delivery is immediate, it is no longer a problem. This is the only place I know of, in Anyway, Maybe I can cook up a test implementation. If not for cancel, then for any other exception. Perhaps it is even doable via "asynkit" although it would require deep patching of asyncio. I'll close this for now, and thank you for your thougts :) |
Oh, I now have an experimental implementation of this in asynkit.
It is experimental because there are corner cases which I cannot handle for C-implemented It has taken this long because asynkit is a hobby project, and I wanted to clean up other aspects first, foremost my scheduling extensions and not having to rely on special subclassing of the event loop to accomplish those. Basically, a Of course, I'd like to see my scheduling extenstions to be considered upstream, particularly ways to modify the event loop queue, look up tasks in the queue, and so on. I have ideas about how that could be done better. My |
Feature or enhancement
I propose that it becomes possible to raise arbitrary expressions on
Task
objects, as well as provide custom instancesof
asyncio.CancelledError
to `Task.cancel()Pitch
Currently the only way to "signal" a
Task
is by means of theTask.cancel()
method. It will cause aCancelledError
to be raised in the task in due course, and it can accept a special
msg:str
argument.However, the primary usage of this mechanism is to actually cancel tasks. This has been cemented into place with
the addition of the
TaskGroup
construct, and various code put in place to ensure thatTask.cancel()
results in taskcancellation. This includes the "cancel count" on each task.
Sending "signals" (Exceptions) to task objects shouldn't be considered a very exotic operation. The new
asyncio.Timeout
context manager does that, when a timeout expires. However, it needs to co-opt theCancelledError
to do so. This becomes problematic because it needsto distinguish between proper cancellations and timeouts. The implementation would be much cleaner if it could simply raise its own subclass of
BaseException
, with its own custom arguments.Task.cancel()
is not anasync
method, meaning that cancellation is only scheduled to happen at a later point. (In a sense, it is asynchronous.)Other frameworks, such as stackless have similar, but synchronous mechanisms to deliver signals. This would avoid race conditions where an application tries to send more than one exception to a task. Only one can be delivered, which one wins?
If we consider immediate exception delivery to hard to achieve in the
asyncio
framework, we could instead decide on apolicy, such as "last exception wins", but wth "CancelledError" given special priority.
Here is a small list of suggested apis just to discuss this.
Previous discussion
This was started in issue #102780
The text was updated successfully, but these errors were encountered: