[pydantic-ai] RunUsage.tool_calls race condition 수정 revert — asyncio.Lock 제거
PR 링크: pydantic/pydantic-ai#3174 상태: Merged | 변경: +4 / -48
들어가며
PR #3133에서 병렬 tool 실행 시 usage.tool_calls 카운터가 과소 집계되는 race condition을 asyncio.Lock으로 수정했습니다. 그러나 이 접근은 불필요한 복잡성을 추가하고, 더 근본적인 아키텍처 변경(PR #2978)이 진행 중이었기 때문에 revert되었습니다.
핵심 코드 분석
asyncio.Lock 제거
PR #3133에서 추가된 코드:
@cached_property
def _usage_lock(self) -> asyncio.Lock:
"""Lock to prevent race conditions when incrementing usage.tool_calls."""
return asyncio.Lock()
async def _call_function_tool(self, ...):
tool_result = await self._call_tool(call, allow_partial, wrap_validation_errors)
async with self._usage_lock:
usage.tool_calls += 1
Revert 후:
async def _call_function_tool(self, ...):
tool_result = await self._call_tool(call, allow_partial, wrap_validation_errors)
usage.tool_calls += 1
_finish_handling 동기화 복원
# PR #3133에서 async로 변경된 것을 다시 동기 함수로 복원
def _finish_handling(self, ctx, response):
...
왜 이게 좋은가
asyncio.Lock은 단일 스레드 이벤트 루프에서+=연산의 race condition을 방지하는 데 과도한 수단입니다. CPython에서 정수 증가는 실제로 원자적이지 않지만, 더 나은 해결책(PR #2978의 사전 검증 방식)이 준비 중이었습니다.- Lock 기반 접근은 tool 실행 순서에 영향을 줄 수 있어, 성능 측면에서도 바람직하지 않습니다.
- 35줄의 테스트 코드(
test_race_condition_parallel_tool_calls)도 함께 제거되었습니다.
정리
- Lock보다 구조적 해결을 선호하라: race condition은 Lock으로 패치하기보다, 카운터 갱신 지점을 재설계하는 것이 더 깔끔합니다.
- revert는 유효한 전략이다: 더 나은 수정이 진행 중이면, 임시 패치를 revert하고 근본 수정을 적용하는 것이 코드 이력을 깔끔하게 유지합니다.
참고 자료
- pydantic/pydantic-ai#3174 — Revert PR
- pydantic/pydantic-ai#3133 — 원본 수정 PR
- pydantic/pydantic-ai#2978 — 근본 수정 PR
⚠️ 알림: 이 분석은 AI가 실제 코드 diff를 기반으로 작성했습니다.
관련 포스트
PR Analysis 의 다른글
- 이전글 [pydantic-ai] RunUsage.tool_calls 병렬 실행 시 과소 집계 버그 수정 (asyncio.Lock)
- 현재글 : [pydantic-ai] RunUsage.tool_calls race condition 수정 revert — asyncio.Lock 제거
- 다음글 [Loki] 쿼리 엔진에 Parallelize 힌트 노드 추가
댓글