Skip to content

Asyncio: Send arbitrary exceptions to Tasks #102847

Closed
@kristjanvalur

Description

@kristjanvalur

Feature or enhancement

I propose that it becomes possible to raise arbitrary expressions on Task objects, as well as provide custom instances
of asyncio.CancelledError to `Task.cancel()

Pitch

Currently the only way to "signal" a Task is by means of the Task.cancel() method. It will cause a CancelledError
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 that Task.cancel() results in task
cancellation. 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 the CancelledError to do so. This becomes problematic because it needs
to 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 an async 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 a
policy, such as "last exception wins", but wth "CancelledError" given special priority.

Here is a small list of suggested apis just to discuss this.

def cancel(msg:string="", exception:Optional[CancelledError]=None):
    """
    Schedule a cancelled exception to be raised on the task.  If `exception` is provided,
    raise the given instance.  `msg` is deprecated in favor or providing as an argument to the `exception` constructor.
    """
    
async def acancel(...):
   """
   same as `cancel()` except it causes an immediate switch to the task.
   """

def raise(exception:BaseException):
   """
   Schedule `exception` to be raised on the task.  `exception must not be a `CancelledError` or 
   a `RuntimeError` will be thrown.
   """
   
async def araise(..):
    """
    same as `raise()` except that the exception is delivered immediately.
    """

Previous discussion

This was started in issue #102780

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    Status

    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions