본문으로 건너뛰기

[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_Lengthob_size를 직접 반환하므로 overhead가 극히 작다.

정리

+2/-7이라는 매우 작은 변경이지만, 이벤트 루프의 핵심 경로에서 발생할 수 있는 race condition을 원천적으로 제거한다. "별도 카운터를 수동 관리하는 대신 source of truth를 직접 조회하라"는 원칙의 교과서적인 적용 사례다. 성능 크리티컬한 이벤트 루프 코드에서도 deque의 len()이 O(1)이기 때문에 이 접근이 가능하다.

참고 자료

⚠️ 알림: 이 분석은 AI가 실제 코드 diff를 기반으로 작성했습니다.

댓글

관련 포스트

PR Analysis 의 다른글