This rule raises an issue when a cancellation scope (timeout context) is used without any checkpoints making the timeout functionality ineffective.

Why is this an issue?

When using asynchronous programming with libraries like trio or anyio, cancellation scopes (timeout contexts) are used to implement timeouts and cancellation. However, these mechanisms only work when there are checkpoints within the scope where cancellation can occur. Without any checkpoints, the timeout will never be triggered, making it ineffective.

A checkpoint is a point in the code where cancellation can be detected and acted upon. Common checkpoints include:

What is the potential impact?

Without proper checkpoints in cancel scopes:

How to fix it in Asyncio

There is no direct checkpoint method in asyncio, but you can use await asyncio.sleep(0) as a workaround.

Code examples

Noncompliant code example

import asyncio

async def process_data(data):
    try:
        async with asyncio.timeout(1.0):  # Noncompliant
            result = expensive_computation(data)
            return result
    except asyncio.TimeoutError:
        return None

Compliant solution

import asyncio

async def process_data(data):
    try:
        async with asyncio.timeout(1.0):  # Compliant
            result = expensive_computation(data)
            await asyncio.sleep(0)
            return result
    except asyncio.TimeoutError:
        return None

How to fix it in Trio

Code examples

Noncompliant code example

import trio

async def process_data(data):
    async with trio.move_on_after(1.0):  # Noncompliant
        result = expensive_computation(data)
        return result

Compliant solution

import trio

async def process_data(data):
    async with trio.move_on_after(1.0):  # Compliant
        result = expensive_computation(data)
        await trio.lowlevel.checkpoint()
        return result

How to fix it in AnyIO

Code examples

Noncompliant code example

import anyio

async def process_data(data):
    async with anyio.move_on_after(1.0):  # Noncompliant
        result = expensive_computation(data)
        return result

Compliant solution

import anyio

async def process_data(data):
    async with anyio.move_on_after(1.0):  # Compliant
        result = expensive_computation(data)
        await anyio.lowlevel.checkpoint()
        return result

Resources

Documentation