cancel-async-tasks

安装量: 35
排名: #19622

安装

npx skills add https://github.com/letta-ai/skills --skill cancel-async-tasks
Cancel Async Tasks
Overview
This skill provides guidance for implementing robust asyncio task cancellation in Python, particularly when dealing with signal handling (SIGINT/KeyboardInterrupt), semaphore-based concurrency limiting, and ensuring proper cleanup of all tasks including those waiting in queues.
Key Concepts
Signal Propagation in Asyncio
Understanding how signals interact with asyncio is critical:
KeyboardInterrupt vs CancelledError
When SIGINT is received during
asyncio.run()
, the behavior differs from catching exceptions inside async code. The event loop typically converts the interrupt to
CancelledError
that propagates through tasks.
Signal handler context
Signal handlers run in the main thread, but asyncio tasks may be in various states (running, waiting on semaphore, waiting on I/O).
Event loop state
The event loop's handling of SIGINT depends on whether it's running
asyncio.run()
vs manual loop management.
Task Lifecycle States
When cancellation occurs, tasks can be in different states:
Running tasks
Currently executing code
Awaiting tasks
Blocked on I/O or other coroutines
Semaphore-waiting tasks
Waiting to acquire a semaphore for concurrency limiting
Not-yet-started tasks
Created but not yet scheduled
Each state requires different handling for proper cleanup.
Potential Approaches
Approach 1: Task Group with Exception Handling
Use
asyncio.TaskGroup
(Python 3.11+) for automatic cancellation propagation:
TaskGroup automatically cancels remaining tasks when one fails
Provides structured concurrency guarantees
Consider whether this matches the cleanup requirements
Approach 2: Manual Task Tracking with Shield
Track all task objects explicitly and handle cancellation:
Maintain a list of all created task objects
Use
asyncio.shield()
for cleanup operations that must complete
Implement explicit cancellation loop for all tracked tasks
Approach 3: Signal Handler Registration
Register explicit signal handlers for SIGINT/SIGTERM:
Use
loop.add_signal_handler()
to register custom handlers
Set a cancellation flag or event that tasks check
Coordinate shutdown through the event loop
Approach 4: Context Manager Pattern
Wrap task execution in a context manager that handles cleanup:
aenter
sets up tasks and tracking
aexit
ensures all tasks are cancelled and awaited
Handles exceptions uniformly
Verification Strategies
Testing with Real Signals
Critical
Test with actual signals, not timeouts:

Correct approach: Use subprocess with actual SIGINT

import subprocess import signal import time proc = subprocess . Popen ( [ 'python' , 'script.py' ] ) time . sleep ( 1 )

Let tasks start

proc . send_signal ( signal . SIGINT ) stdout , stderr = proc . communicate ( timeout = 5 )

Verify cleanup messages in output

Incorrect approach
(gives false confidence):
Using
asyncio.wait_for()
with timeout does not replicate SIGINT behavior
Using
asyncio.CancelledError
directly differs from signal-triggered cancellation
Verification Checklist
Running task cleanup
Verify tasks actively executing receive cancellation
Waiting task cleanup
Verify tasks blocked on I/O are cancelled
Semaphore queue cleanup
Verify tasks waiting on semaphore acquisition are cancelled
Cleanup code execution
Verify finally blocks and cleanup handlers run
No resource leaks
Verify file handles, connections, etc. are closed
Exit code verification
Verify process exits with expected code after interrupt
Test Scenarios to Cover
Interrupt when all slots are filled (max_concurrent tasks running)
Interrupt when tasks are queued waiting for semaphore
Interrupt during cleanup phase itself
Rapid repeated interrupts
Interrupt before any task starts
Common Pitfalls
Pitfall 1: Catching KeyboardInterrupt Inside Async Functions
Problem
:
KeyboardInterrupt
doesn't propagate normally through asyncio - it's typically converted to
CancelledError
by the event loop.
Symptom
Exception handlers for
KeyboardInterrupt
inside async functions never trigger during actual Ctrl+C.
Solution
Handle
CancelledError
instead, or register explicit signal handlers at the event loop level.
Pitfall 2: asyncio.gather Doesn't Cancel Queued Tasks
Problem
When using
asyncio.gather
with more tasks than can run concurrently (via semaphore), cancelling gather doesn't automatically cancel tasks waiting to acquire the semaphore.
Symptom
Tasks that haven't started don't have their cleanup code run.
Solution
Explicitly track all task objects and cancel them individually, not just rely on gather's cancellation.
Pitfall 3: Testing with Timeouts Instead of Signals
Problem
Using
asyncio.wait_for()
timeout to simulate interruption doesn't replicate actual signal handling behavior.
Symptom
Tests pass but actual Ctrl+C behavior differs.
Solution
Use
subprocess
with
signal.SIGINT
to test actual signal handling behavior.
Pitfall 4: Cleanup During Cancellation
Problem
Cleanup code itself may be cancelled if not protected.
Symptom
Partial cleanup, resources not released.
Solution
Use
asyncio.shield()
for critical cleanup operations, or handle
CancelledError
and re-raise after cleanup.
Pitfall 5: Duplicate Exception Handling Code
Problem
Identical cleanup code in multiple exception handlers (
CancelledError
,
KeyboardInterrupt
, etc.).
Symptom
Code duplication, maintenance burden.
Solution
Use a single handler with
except (asyncio.CancelledError, KeyboardInterrupt)
or abstract cleanup into a helper function.
Pitfall 6: Not Awaiting Cancelled Tasks
Problem
Cancelling a task and not awaiting it leaves the task in a partially-cleaned-up state.
Symptom
Resource leaks, warnings about pending tasks.
Solution
Always
await asyncio.gather(*cancelled_tasks, return_exceptions=True)
after cancelling.
Decision Framework
When implementing async task cancellation, consider:
Python version
TaskGroup (3.11+) vs manual management
Concurrency model
Fixed pool, semaphore-limited, or unlimited
Cleanup requirements
What must happen before exit?
Signal handling needs
Just SIGINT, or also SIGTERM, SIGHUP?
Testing environment
Can tests send real signals? Debugging Tips Add logging at task entry, exit, and cancellation points Log the task state when cancellation is received Use asyncio.current_task() to identify which task is executing Check task.cancelled() vs task.done() states Enable asyncio debug mode: asyncio.run(main(), debug=True)
返回排行榜