Python asyncio task가 끝나지 않을 때
마지막 업데이트

Python asyncio task가 끝나지 않을 때


asyncio task 가 끝나지 않을 때 원인은 보통 “asyncio 가 고장났다” 가 아닙니다. 더 흔한 경우는 특정 await 가 끝나지 않거나, queue 균형이 깨졌거나, clean 한 cancellation path 가 없거나, event loop 진행이 와야 하는데 그 조건이 오지 않는 경우입니다.

짧게 말하면 핵심은 이것입니다. 끝나지 않는 task 가 정확히 어디서 기다리고 있는지 먼저 찾아야 합니다. 많은 task 가 같은 queue, lock, future, await path 에 몰려 있다면, 이건 성능 문제보다 coordination failure 인 경우가 많습니다.


기다리는 지점부터 본다

가장 빠른 단서는 task 이름보다, unfinished task 가 실제로 시간을 보내는 위치입니다.

예를 들면:

  • 같은 await path 가 반복되는가
  • task 가 하나의 queue 또는 semaphore 에 몰려 있는가
  • worker 가 이미 멈춘 producer 를 기다리는가
  • 취소됐어야 할 task 가 아직 살아 있는가

이 구분이 단순히 “event loop 가 건강한가” 를 묻는 것보다 훨씬 도움이 됩니다.


”끝나지 않음” 은 운영에서 어떻게 보이나

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

  • 요청이 끝나지 않는다
  • shutdown 중에 background task 가 남아 프로세스가 멈춘다
  • queue consumer 가 영원히 다음 작업을 기다린다
  • 모니터링에는 pending task 가 많은데 실제 처리량은 낮다
  • 운영자는 loop starvation 을 의심하지만 실제 문제는 task coordination 이다

같아 보여도 failure shape 가 조금씩 다르고, 어떤 waiting path 가 지배적인지에 따라 fix 도 달라집니다.


흔한 원인

1. awaited work 가 끝나지 않는다

하나의 awaited call 이 나머지 task tree 를 모두 묶을 수 있습니다.

보통 이런 경우입니다.

  • timeout 없는 remote I/O 대기
  • 끝나지 않는 다른 task 대기
  • fulfill 되지 않는 future 대기
  • 이미 멈춘 component 에 의존하는 cleanup 대기
task = asyncio.create_task(worker())
await task

worker() 가 끝나지 않는 I/O 를 기다리거나 shutdown path 를 놓치면 await task 는 돌아오지 않습니다.

2. cancellation 이 없거나 불완전하다

timeout 과 shutdown 경로가 task 를 clean 하게 취소하지 못해서, task 가 의도보다 오래 살아남습니다.

특히 이런 패턴이 흔합니다.

  • create_task 했지만 추적하지 않음
  • cancel 은 했지만 await 하지 않음
  • background worker 가 CancelledError 를 잘못 삼킴
  • shutdown 이 task ownership 을 올바르게 cascade 하지 않음

3. queue 또는 producer-consumer 균형이 깨진다

파이프라인의 반대편이 느리거나 사라졌거나 이미 멈췄기 때문에 task 가 영원히 기다릴 수 있습니다.

예를 들면:

  • producer 는 멈췄는데 consumer 는 계속 기다림
  • consumer 가 너무 느려 completion 조건이 오지 않음
  • sentinel 이나 shutdown signal 이 전달되지 않음

4. 하나의 coordination primitive 가 병목이 된다

lock, semaphore, queue 중 하나가 멈추면, 여러 task 가 동시에 멈춘 것처럼 보일 수 있습니다.

이 경우 broad event-loop issue 처럼 보여도 실제로는 하나의 synchronization point 문제일 수 있습니다.

5. loop 는 건강한데 task ownership 이 잘못되었다

runtime 은 멀쩡하고 task 설계가 문제인 경우도 많습니다.

task 에 명확한 owner 가 없고, 종료 조건이 없고, shutdown path 가 없다면, 사실 hang 라기보다 completion 이 보장되지 않는 lifecycle 인 셈입니다.


실전 점검 순서

1. unfinished task 가 어디서 기다리는지 찾는다

가장 가치가 큰 첫 단계입니다.

질문은 이렇습니다.

  • 어떤 await 인가
  • 어떤 queue 또는 lock 인가
  • 많은 task 가 같은 곳에서 멈추는가

2. timeout 과 cancellation 흐름을 본다

원래 이 task 가 이미 멈췄어야 하는지 확인하세요.

멈췄어야 했다면:

  • 누가 lifecycle owner 인가
  • 누가 cancel 해야 하는가
  • 누가 그 cancellation completion 을 기다리는가

3. producer 와 consumer 속도를 비교한다

queue 를 쓴다면 한쪽이 사라졌거나 심하게 뒤처졌을 수 있습니다.

이게 obvious error 없이 task 가 pending 상태로 남는 이유가 되곤 합니다.

4. queue, semaphore, lock 사용을 점검한다

공유 coordination primitive 하나가 막히면, 여러 task 가 한꺼번에 hang 된 것처럼 보입니다.

5. 최근 코드나 traffic 변화를 같이 본다

새 background task, 바뀐 queue 동작, 달라진 shutdown flow 가 task completion 문제를 갑자기 만들었을 수 있습니다.


예시: shutdown path 에서 task ownership 누락

async def main():
    asyncio.create_task(worker())
    await serve_requests()

겉으로는 평범해 보여도, worker() 를 추적하지 않고 shutdown 때 cancel 하지 않으면 background task 하나 때문에 프로세스가 hang 된 것처럼 보일 수 있습니다.

더 안전한 패턴은 보통 이렇습니다.

  • 생성한 task 추적하기
  • shutdown 중 명시적으로 cancel 하기
  • cleanup 이 실제 끝날 때까지 await 하기

waiting pattern 을 찾은 뒤 무엇을 바꾸면 좋나

awaited work 가 끝나지 않는다면

timeout, ownership, failure path 를 더 분명하게 만들어서 무기한 대기가 생기지 않게 해야 합니다.

cancellation 이 빠졌다면

shutdown 과 timeout flow 를 명시적으로 만들고 created task 를 추적해야 합니다.

queue 균형이 깨졌다면

producer-consumer coordination, sentinel 전달, backpressure 가정을 다시 봐야 합니다.

하나의 primitive 가 병목이라면

shared coordination pressure 를 줄이거나 task flow 를 다시 설계해야 합니다.

task 에 lifecycle 이 없다면

각 background task 에 owner, stop condition, cleanup path 를 부여해야 합니다.


장애 중에 던져볼 질문

이 질문이 꽤 유용합니다.

이 task 가 끝나려면 정확히 어떤 조건이 만족돼야 하고, 그 조건을 만들어 줄 주체가 지금도 살아 있는가?

이 질문이 missing ownership 이나 missing completion signal 을 빨리 드러내 줍니다.


FAQ

Q. 이게 항상 event-loop 문제인가

아닙니다. 많은 incident 는 loop 자체보다 coordination 이나 cancellation 문제입니다.

Q. 먼저 무엇을 봐야 하나

await 지점, cancellation 경로, producer-consumer balance 입니다.

Q. pending task 가 많으면 무조건 overload 인가

그렇지 않습니다. 하나의 dependency 나 shutdown signal 이 끝나지 않아서 그럴 수도 있습니다.

Q. 로그만으로 충분한가

도움은 되지만, task dump 와 explicit lifecycle tracking 이 있어야 실제 waiting path 가 보이는 경우가 많습니다.


Sources:

먼저 읽어볼 가이드

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