Skip to content

python.uncancelled_tasks

Stability Medium

Detects async tasks that are created but never properly cancelled during shutdown, leading to resource leaks and hung processes.

Uncancelled tasks cause:

  • Resource leaks — Tasks hold connections, file handles, memory
  • Hung shutdowns — Process waits for tasks that never complete
  • Orphaned operations — Tasks continue after they should stop
  • Zombie processes — Incomplete cleanup on termination
# ❌ Before (tasks never tracked/cancelled)
async def start_background_work():
asyncio.create_task(monitor_metrics())
asyncio.create_task(process_queue())
# Tasks are orphaned - no way to cancel them
# ✅ After (tasks tracked and cancelled)
class BackgroundWorker:
def __init__(self):
self.tasks: set[asyncio.Task] = set()
def create_task(self, coro):
task = asyncio.create_task(coro)
self.tasks.add(task)
task.add_done_callback(self.tasks.discard)
return task
async def shutdown(self):
for task in self.tasks:
task.cancel()
await asyncio.gather(*self.tasks, return_exceptions=True)
worker = BackgroundWorker()
async def start_background_work():
worker.create_task(monitor_metrics())
worker.create_task(process_queue())
async def cleanup():
await worker.shutdown()
  • asyncio.create_task() without task tracking
  • Tasks created without cancellation handling
  • Missing cleanup in shutdown handlers
  • Fire-and-forget task patterns

Unfault generates patches that add task tracking:

# TaskGroup (Python 3.11+)
async def main():
async with asyncio.TaskGroup() as tg:
tg.create_task(task1())
tg.create_task(task2())
# All tasks automatically cancelled on exit
# For long-running services
import signal
shutdown_event = asyncio.Event()
async def graceful_shutdown():
shutdown_event.set()
# Give tasks time to finish
await asyncio.sleep(5)
# Cancel remaining tasks
tasks = [t for t in asyncio.all_tasks() if t is not asyncio.current_task()]
for task in tasks:
task.cancel()
await asyncio.gather(*tasks, return_exceptions=True)
# Register signal handlers
loop = asyncio.get_event_loop()
loop.add_signal_handler(signal.SIGTERM, lambda: asyncio.create_task(graceful_shutdown()))