[uvloop] _ready_len 레이스 컨디션 수정
PR 링크: MagicStack/uvloop#721 상태: Merged | 변경: +2 / -7
들어가며
uvloop은 Python asyncio의 고성능 이벤트 루프 구현체로, libuv를 백엔드로 사용한다. 이벤트 루프의 _ready 큐(deque)에 실행 대기 중인 callback handle이 쌓이는데, 기존에는 _ready_len이라는 별도 카운터 변수를 수동으로 관리하여 큐 길이를 추적했다. 문제는 _ready_len의 증감이 _ready deque의 실제 상태와 항상 동기화되지 않아 race condition이 발생할 수 있다는 점이다.
핵심 코드 분석
1. 카운터 변수 제거 (loop.pxd)
Cython 선언 파일에서 _ready_len 필드 자체를 삭제한다.
Before:
cdef class Loop:
object _ready
set _queued_streams, _executing_streams
Py_ssize_t _ready_len
After:
cdef class Loop:
object _ready
set _queued_streams, _executing_streams
2. len() 직접 호출로 대체 (loop.pyx)
_ready_len을 참조하던 모든 지점을 len(self._ready)로 교체한다.
Before:
cdef _on_wake(self):
if ((self._ready_len > 0 or self._stopping) and
not self.handler_idle.running):
self.handler_idle.start()
After:
cdef _on_wake(self):
if ((len(self._ready) > 0 or self._stopping) and
not self.handler_idle.running):
self.handler_idle.start()
3. 수동 증감 코드 제거
_ready_len을 증가시키던 코드와 초기화하던 코드가 모두 제거된다.
Before:
cdef inline _append_ready_handle(self, Handle handle):
self._check_closed()
self._ready.append(handle)
self._ready_len += 1
After:
cdef inline _append_ready_handle(self, Handle handle):
self._check_closed()
self._ready.append(handle)
동일하게, 이벤트 루프의 _run_once 메서드에서도 _ready_len을 별도로 갱신하던 코드가 제거된다.
Before:
self._ready_len = len(self._ready)
if self._ready_len == 0 and self.handler_idle.running:
self.handler_idle.stop()
After:
if len(self._ready) == 0 and self.handler_idle.running:
self.handler_idle.stop()
close() 메서드에서 _ready_len = 0 초기화도 삭제된다.
왜 이게 좋은가
- Race condition 제거:
_ready_len은_ready.append()이후에 증가하고,_run_once()에서는 루프 시작 시 한 번만 갱신되었다. 그 사이에 다른 callback이 추가되면 실제 큐 길이와 카운터가 불일치할 수 있었다.len()은 deque의 실제 상태를 항상 정확하게 반환한다. - 코드 단순화: 5곳에서 관리하던 별도 카운터가 사라져 코드가 간결해졌다.
- 성능 영향 미미: Python deque의
len()은 O(1) 연산이므로 캐시된 카운터와 성능 차이가 사실상 없다. Cython 환경에서도 CPython의PyObject_Length가ob_size를 직접 반환하므로 overhead가 극히 작다.
정리
+2/-7이라는 매우 작은 변경이지만, 이벤트 루프의 핵심 경로에서 발생할 수 있는 race condition을 원천적으로 제거한다. "별도 카운터를 수동 관리하는 대신 source of truth를 직접 조회하라"는 원칙의 교과서적인 적용 사례다. 성능 크리티컬한 이벤트 루프 코드에서도 deque의 len()이 O(1)이기 때문에 이 접근이 가능하다.
참고 자료
- MagicStack/uvloop#721 — 원본 PR
- uvloop GitHub — uvloop 저장소
- CPython deque 구현 — deque의 len이 O(1)인 이유
⚠️ 알림: 이 분석은 AI가 실제 코드 diff를 기반으로 작성했습니다.
관련 포스트
PR Analysis 의 다른글
- 이전글 [llm-compressor] Memoryless Observers - 메모리 효율적 가중치 관찰자
- 현재글 : [uvloop] _ready_len 레이스 컨디션 수정
- 다음글 [vllm] Draft Model 기반 Speculative Decoding 지원
댓글