Cancel scopes¶
quattro contains an independent, asyncio implementation of Trio cancel scopes.
from quattro import move_on_after
async def my_handler():
with move_on_after(1.0) as cancel_scope:
await long_query()
# 1 second later, the function continues running
quattro contains the following helpers:
When and where to use
Use to make use of deadlines, which are more powerful than timeouts since they affect groups of operations.
Use to gain access to
get_current_effective_deadline(), which enables deadline propagation between services.
All helpers produce instances of quattro.CancelScope, which is largely similar to the Trio variant.
CancelScopes have the following attributes:
cancel()- a method through which the scope can be cancelled manually.cancel()can be called before the scope is entered; entering the scope will cancel it at the first opportunitydeadline- read/write, an optional deadline for the scope, at which the scope will be cancelledcancelled_caught- a readonly bool property, whether the scope finished via cancellation
quattro also supports retrieving the current effective deadline in a task using quattro.get_current_effective_deadline().
The current effective deadline is a float value, with float('inf') standing in for no deadline.
Python versions 3.11 and higher contain similar helpers, asyncio.timeout and asyncio.timeout_at.
The quattro fail_after() and fail_at() helpers are effectively equivalent to the asyncio timeouts, and pass the test suite for them.
The differences are:
The quattro versions are normal context managers (used with just
with), asyncio versions are async context managers (usingasync with). Neither version needs to be async since nothing is awaited; quattro chooses to be non-async to signal there are no suspension points being hit, match Trio and be a little more readable.quattro additionally contains the
move_on_at()andmove_on_after()helpers.The quattro versions support getting the current effective deadline.
The quattro versions can be cancelled manually using
CancelScope.cancel(), and precancelled before they are enteredThe quattro versions are available on all supported Python versions, not just 3.11+.
asyncio and Trio differences¶
fail_after() and fail_at() raise TimeoutError instead of trio.Cancelled exceptions when they fail.
asyncio has edge-triggered cancellation semantics, while Trio has level-triggered cancellation semantics. The following example will behave differently in quattro and Trio:
with trio.move_on_after(TIMEOUT):
conn = make_connection()
try:
await conn.send_hello_msg()
finally:
await conn.send_goodbye_msg()
In Trio, if the TIMEOUT expires while awaiting send_hello_msg(), send_goodbye_msg() will also be cancelled.
In quattro, send_goodbye_msg() will run (and potentially block) anyway.
This is a limitation of the underlying framework.
In quattro, cancellation scopes cannot be shielded.