[PR #3698] RawBlock put-task refs dispatch 실패 시 롤백 fix
[!tldr] 업무 관점 takeaway 우리 팀(Nayeon) correctness fix PR.
batched_submit_put_task에서run_coroutine_threadsafedispatch 실패(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는 동기 메서드 — 키마다:
obj.ref_count_up()+_put_tasks.add(key)로 자원/슬롯 선점- 코루틴을
run_coroutine_threadsafe(coro, loop)로 다른 스레드의 asyncio 이벤트 루프에 dispatch
실패 케이스: backend.close()로 루프가 내려가는 중 put이 들어오면 run_coroutine_threadsafe가 RuntimeError를 raise → 코루틴이 시작조차 안 되므로 finally 블록이 실행되지 않음 → 직전에 선점한 ref_count_up + _put_tasks 항목이 영구 누수:
| 누수 항목 | 결과 |
|---|---|
| obj ref_count +1 잔류 | MemoryObj pin → 메모리 회수 불가 |
_put_tasks key 잔류 | 이후 동일 key의 put 영구 차단 |
close() 폴링 | 누수가 안 사라져 timeout까지 끌림 |
수정 내용 — cleanup 경로 이분
| 경로 | 담당 항목 | 실행 위치 |
|---|---|---|
경로 A: 코루틴 finally | dispatch 성공한 항목 | 이벤트 루프 스레드 (나중에) |
| 경로 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 Exception→except 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 추적