This rule raises issues when trio.sleep(0) or anyio.sleep(0) is used instead of the more explicit and descriptive trio.lowlevel.checkpoint() or anyio.lowlevel.checkpoint().

Why is this an issue?

When using async libraries like trio or anyio, developers sometimes use sleep(0) as a technique to yield control back to the event loop, allowing other pending tasks to run. While this approach technically works, it obscures the actual intent.

The sleep(0) pattern is confusing because it suggests waiting for a specific duration (which happens to be 0 seconds), when the actual intention is to create a checkpoint in the code where the current task can yield control to other tasks without any intended delay.

Both trio and anyio provide dedicated functions for this exact purpose: trio.lowlevel.checkpoint() and anyio.lowlevel.checkpoint(). These functions clearly communicate the intent to create a checkpoint for task switching.

What is the potential impact?

How to fix it in Trio

Replace trio.sleep(0) with trio.lowlevel.checkpoint().

Code examples

Noncompliant code example

import trio

async def main():
    await trio.sleep(0)  # Noncompliant

trio.run(main)

Compliant solution

import trio

async def main():
    await trio.lowlevel.checkpoint()  # Compliant

trio.run(main)

How to fix it in AnyIO

Replace anyio.sleep(0) with anyio.lowlevel.checkpoint().

Code examples

Noncompliant code example

import anyio

async def main():
    await anyio.sleep(0)  # Noncompliant

anyio.run(main)

Compliant solution

import anyio

async def main():
    await anyio.lowlevel.checkpoint()  # Compliant

anyio.run(main)

Resources

Documentation