본문으로 건너뛰기

[PR #3698] RawBlock put-task refs dispatch 실패 시 롤백 fix

[!tldr] 업무 관점 takeaway 우리 팀(Nayeon) correctness fix PR. batched_submit_put_task에서 run_coroutine_threadsafe dispatch 실패(loop shutdown 경합) 시 선점한 ref_count + _put_tasks 항목이 영구 누수되던 버그를 수정. shutdown race 한정이라 드물지만 발생 시 결과가 메모리 pin + 이후 동일 키 put 영구 차단 + close() 타임아웃이라 단독 fix PR이 정당하다.

  • PR: #3698 · 브랜치: fix/rawblock-put-dispatch-ref-rollback
  • 상태: OPEN (dev 대상) · base: dev efdabca0
  • 영향 파일: lmcache/v1/storage_backend/plugins/rust_raw_block_backend.py, tests/v1/storage_backend/test_rust_raw_block_backend.py
  • 작성일: 2026-06-16

무엇이 문제였나

batched_submit_put_task는 동기 메서드 — 키마다:

  1. obj.ref_count_up() + _put_tasks.add(key) 로 자원/슬롯 선점
  2. 코루틴을 run_coroutine_threadsafe(coro, loop)로 다른 스레드의 asyncio 이벤트 루프에 dispatch

실패 케이스: backend.close()로 루프가 내려가는 중 put이 들어오면 run_coroutine_threadsafeRuntimeError를 raise → 코루틴이 시작조차 안 되므로 finally 블록이 실행되지 않음 → 직전에 선점한 ref_count_up + _put_tasks 항목이 영구 누수:

누수 항목결과
obj ref_count +1 잔류MemoryObj pin → 메모리 회수 불가
_put_tasks key 잔류이후 동일 key의 put 영구 차단
close() 폴링누수가 안 사라져 timeout까지 끌림

수정 내용 — cleanup 경로 이분

경로담당 항목실행 위치
경로 A: 코루틴 finallydispatch 성공한 항목이벤트 루프 스레드 (나중에)
경로 B: except 롤백dispatch 못 한 tail pending[scheduled_count:]호출자 스레드 (즉시)

scheduled_count가 경계선 — 책임이 겹치지도 빠지지도 않게 나눈다.

io_uring 배치 경로 (코루틴 1개뿐): dispatch 성공 시 scheduled_count=len(pending) (전부 경로 A), 실패 시 scheduled_count=0 (전부 경로 B).


severity & 배경

  • 드물다: close()로 루프가 내려가는 시점과 put dispatch가 정확히 겹치는 shutdown race 한정. 정상 운영 중엔 미발생.
  • 그러나 진짜 버그: 발생 시 결과가 영구 누수 + 종료 지연. 변경 범위는 단일 함수·작은 스코프, on-disk format·공개 시그니처 미변경.
  • 전신(L2 브랜치): 원래 L2 브랜치가 dedup 일괄화 + dispatch 배칭을 시도했으나, 벤치(L2 §9)에서 NVMe 구간 regression으로 배칭·dedup 모두 폐기하고 dev 원본 fan-out으로 복원. PR #3698에 남은 건 dispatch 실패 시 자원 누수 롤백(원본 리뷰의 F1, +F3 흡수) 한 가지.

리뷰 메모 (Gemini 2026-06-16)

  • medium 1건: except Exceptionexcept BaseException 제안 (KeyboardInterrupt/SystemExit 중에도 cleanup 보장)
  • 원론상 타당하나 실효성 낮음: 실제 타깃 실패 모드 "loop shutting down"은 RuntimeError(=Exception 하위)라 이미 커버. BaseException-only 케이스는 메인 스레드에서만 전달되는데 이 메서드는 worker 스레드 경로. plugins/ 컨벤션은 Exception.
  • 적용 시 3곳(L420/L431/L437) 일관 변경 필요. 미적용 + 근거 답글도 무방.

관련 페이지

  • [[raw_block-put_many-batching-PR]] — 전신 L2 배치 PR (dedup+배칭 폐기 경위)
  • [[raw_block-PR-Landscape]] — PR 추적