This rule raises an issue when a cancellation excception is caught without re-raising it.
Asynchronous frameworks like asyncio, trio, and anyio use special exceptions to signal that a task or
operation should be cancelled. These exceptions are not typical errors indicating a logical flaw in the task but are directives for the task to
terminate its execution prematurely and perform necessary cleanup.
When a task is cancelled, the framework typically injects this cancellation exception into it. The task is expected to:
If a cancellation exception is caught and not re-raised (e.g., it’s suppressed with a pass statement, only logged, the handler returns
normally, or a different exception is raised instead), the cancellation signal is effectively "swallowed".
This prevents the framework and any calling code from knowing that the task has acknowledged the cancellation and is stopping. The task might even
continue running parts of its code after the except block, which is contrary to the purpose of cancellation.
Properly propagating cancellation exceptions is crucial for the cooperative multitasking model these frameworks rely on.
Suppressing cancellation exceptions can lead to significant problems:
If you need to catch cancellation exceptions for cleanup purposes, make sure to re-raise them after your cleanup code.
Alternatively, you could add a specific handler for cancellation exceptions before your general exception handler.
import asyncio
async def compute_result(data): ...
async def process_data(data):
try:
result = await compute_result(data)
return result
except asyncio.CancelledError:
return # Noncompliant
import asyncio
async def compute_result(data): ...
async def process_data(data):
try:
result = await compute_result(data)
return result
except asyncio.CancelledError: # Compliant
raise
If you need to catch cancellation exceptions for cleanup purposes, make sure to re-raise them after your cleanup code.
Alternatively, you could add a specific handler for cancellation exceptions before your general exception handler.
import trio
async def compute_result(data): ...
async def process_data(data):
try:
result = await compute_result(data)
return result
except trio.Cancelled: # Noncompliant
return
import trio
async def compute_result(data): ...
async def process_data(data):
try:
result = await compute_result(data)
return result
except trio.Cancelled: # Compliant
raise
If you need to catch cancellation exceptions for cleanup purposes, make sure to re-raise them after your cleanup code.
Alternatively, you could add a specific handler for cancellation exceptions before your general exception handler.
import anyio
async def compute_result(data): ...
async def process_data(data):
try:
result = await compute_result(data)
return result
except anyio.get_cancelled_exc_class(): # Noncompliant
return
import anyio
async def compute_result(data): ...
async def process_data(data):
try:
result = await compute_result(data)
return result
except anyio.get_cancelled_exc_class(): # Compliant
raise
Asynchronous cleanup operations in except CancelledError or finally blocks can themselves be interrupted by cancellation.
While asyncio.shield() (or library equivalents) can protect critical cleanup code, use it sparingly as it may delay shutdown.