Python asyncio Task Cancelled: 흔한 원인과 해결 방향
마지막 업데이트

Python asyncio Task Cancelled: 흔한 원인과 해결 방향


Python asyncio task 가 계속 cancelled 로 끝날 때 문제는 cancellation 자체보다, timeout scope, parent-task ownership, shutdown flow, 혹은 더 오래 살아 있어야 할 작업을 어느 한 계층이 너무 빨리 cancel 하고 있는 경우가 많습니다.

짧게 말하면 핵심은 이것입니다. 누가 cancellation 을 시작하는지, 그리고 그 task 가 정말 그 cancelled work 의 lifecycle owner 인지 먼저 봐야 합니다. asyncio 에서 cancellation 은 자주 정상 동작입니다. 진짜 질문은 올바른 task 가 올바른 lifetime 을 소유하고 있는가입니다.


누가 누구를 cancel 하는지부터 본다

cancellation 은 자동으로 에러가 아닙니다.

runtime 관점에서는 cancelled task 가 정확히 지시받은 대로 움직인 것일 수 있습니다. incident 는 보통 아래 때 시작됩니다.

  • timeout 이 너무 공격적이다
  • parent scope 가 너무 넓다
  • cleanup 이 너무 빨리 끊긴다
  • 잘못된 task 가 잘못된 lifecycle 을 상속받는다

그래서 CancelledError 가 보였다는 사실보다 ownership 이 더 중요합니다.


cancellation 문제는 운영에서 어떻게 보이나

보통 이런 모습으로 나타납니다.

  • 정상 부하인데도 task 가 너무 자주 cancelled 된다
  • request timeout 때문에 background job 까지 사라진다
  • shutdown 이 cleanup 이나 result handling 이 끝나기 전에 작업을 멈춘다
  • 어떤 cancellation 은 예상된 것인데 운영자는 모두 실패로 본다

핵심은 intended cancellation 과 mis-scoped cancellation 을 구분하는 것입니다.


흔한 원인

1. timeout 설정이 너무 공격적이다

실제 작업 시간보다 deadline 이 짧아서 task 가 cancelled 될 수 있습니다.

task = asyncio.create_task(do_work())
await asyncio.wait_for(task, timeout=1)

do_work() 가 보통 1초보다 오래 걸린다면 cancellation 은 랜덤한 실패가 아니라 설정된 결과입니다.

2. parent scope 가 너무 넓다

parent task 하나를 cancel 할 때 child task 가 너무 많이 같이 취소될 수 있습니다.

특히 이런 상황이 위험합니다.

  • request-scoped task 가 background work 를 띄움
  • helper task 가 너무 짧은 handler lifecycle 을 상속받음
  • structured cancellation boundary 가 분명하지 않음

3. shutdown flow 가 거칠다

애플리케이션 shutdown 이 아래 작업이 끝나기 전에 task 를 끊을 수 있습니다.

  • cleanup
  • checkpointing
  • queue draining
  • result delivery

이 경우 cancellation 은 기술적으로는 예상된 것이어도 운영적으로는 충분히 해로울 수 있습니다.

4. queue 와 consumer lifetime 이 맞지 않는다

producer, worker, cleanup path 가 언제 작업이 끝나야 하는지 서로 다르게 이해할 수 있습니다.

한 계층은 pipeline 이 끝났다고 생각하는데 다른 계층은 아직 completion 을 기대하면, cancellation 이 random 하게 느껴질 수 있습니다.

5. cancellation 을 잘못 처리한다

문제는 task 가 cancelled 되는 것보다, 그 cancellation 을 코드가 잘못 다루는 경우도 많습니다.

예를 들면:

  • cancellation 을 잡아서 무시함
  • cleanup loop 가 끝나지 않음
  • cancellation 뒤 task state 를 잃음

이러면 정상 signal 이 지저분한 failure mode 로 바뀝니다.


실전 점검 순서

1. cancellation 이 어디서 시작되는지 찾는다

그걸 트리거하는 caller 또는 scope 를 찾으세요.

질문은 이렇습니다.

  • timeout 인가
  • parent task 인가
  • shutdown logic 인가
  • explicit manual cancel 인가

2. timeout 설정과 실제 task duration 을 비교한다

timeout 이 실제 작업 시간보다 짧다면, cancellation 은 미스터리가 아닙니다.

그냥 설정 mismatch 입니다.

3. parent-child task ownership 을 점검한다

cancelled task 가 정말 그 parent 의 lifecycle 을 상속받아야 하는지 확인하세요.

request-scoped background bug 가 많이 숨어 있는 부분입니다.

4. shutdown 과 cleanup 순서를 본다

shutdown 중 cancellation 이 일어난다면, 중요한 task 가 cleanup 이나 state handoff 를 끝낼 시간을 충분히 받는지 봐야 합니다.

5. intended task 만 cancellation 을 상속받는지 확인한다

마지막으로 cancellation boundary 가 실제 service ownership 과 맞는지 확인해야 합니다.

맞지 않으면 runtime 은 옳고 설계가 틀린 상태일 수 있습니다.


예시: request timeout 이 background work 까지 취소하는 경우

async def handler():
    task = asyncio.create_task(store_result())
    await asyncio.wait_for(fetch_data(), timeout=1)
    await task

fetch_data() 가 timeout 되고 handler 가 cancel 되면, ownership 이 제대로 분리되지 않았을 때 store_result() 도 같이 사라질 수 있습니다.

이건 “request timeout” 과 “중요 background work 유실” 의 차이를 만듭니다.


cancellation path 를 찾은 뒤 무엇을 바꾸면 좋나

timeout 이 너무 짧다면

실제 task duration 에 맞게 조정하거나, 더 의미 있는 중간 경계에 deadline 을 둬야 합니다.

parent scope 가 너무 넓다면

background work 를 짧은 request ownership 에서 분리해야 합니다.

shutdown 이 너무 거칠다면

cleanup 순서를 명시적으로 만들고 중요한 task 가 critical exit path 를 마칠 시간을 줘야 합니다.

cancellation 을 잘못 처리한다면

CancelledError 를 무조건 삼키지 말고 의도적으로 다뤄야 합니다.

lifecycle boundary 가 불분명하다면

각 task 에 clear owner 와 intentional cancellation policy 를 줘야 합니다.


장애 중에 던져볼 질문

이 질문이 꽤 유용합니다.

이 task 가 정말 유용한 lifetime 을 다해서 cancel 되는 건가, 아니면 다른 곳의 잘못된 lifetime 을 상속받아서 cancel 되는 건가?

이 질문이 설계상의 진짜 버그를 잘 드러냅니다.


FAQ

Q. cancellation 은 항상 에러인가

아닙니다. 올바른 signal 일 수도 있지만, 잘못된 task 가 그 signal 을 받을 수 있습니다.

Q. 가장 빠른 첫 단계는 무엇인가

cancellation 을 트리거하는 caller 를 찾고, 그 scope 가 task 의 intended lifetime 과 맞는지 비교하는 것입니다.

Q. CancelledError 를 잡고 무시해도 되나

보통은 아닙니다. 잡더라도 shutdown behavior 를 망치지 않게 의도적으로 처리해야 합니다.

Q. 이건 주로 timeout 문제인가

때로는 그렇지만, parent scope 와 shutdown ownership 도 매우 흔한 원인입니다.


Sources:

먼저 읽어볼 가이드

검색 유입이 많은 핵심 글부터 이어서 보세요.