“repr 한 줄”이 락이 되는 순간: free-threaded에서 functools가 PyDict_Next를 critical section으로 감싼 이유 meta_description: free-threaded 환경에서 dict 순회는 ‘그냥 읽기’가 아니라 동시성 계약의 일부가 된다. gh-145446(PR #145487)는 functools 내부에서 PyDict_Next로 kwargs 등을 순회하는 루프를 critical section으로 감싸고, 에러 처리/정리 순서를 바꿔 경계면을 명확히 했다. 어떤 루프가 왜 바뀌었는지, 실무에서 확장 모듈/FFI의 dict 순회를 어떻게 짜야 덜 흔들리는지 정리한다. meta_keywords: functools, free-threaded, critical section, CPython, PyDict_Next, kwargs, partial, repr, dict iteration, error handling, cleanup, lock, GIL-less, 동시성, data race, refcount, C extension, FFI, 안정성 meta_robots: index,follow free-threaded 쪽 변경을 보다 보면, 가끔 “이게 왜 여기서?” 싶은 지점이 나온다. 이번 gh-145446 / PR #145487이 그렇다. 사람들이 보통 위험하다고 생각하는 건 네이티브 확장이나 I/O 같은 데다. 그런데 패치가 들어간 곳은 functools 다. 더 정확히는 kwargs 같은 dict를 순회하는 루프. 이게 재미있는 이유는, 이 경로가 대부분의 서비스에서 ‘겉보기 무해한 곳’으로 쓰이기 때문이다. 로그에서 객체를 찍는 repr 캐시 키를 만들기 위해 kwargs를 정렬/나열하는 코드 에러 메시지에 함수/인자를 조금 더 친절하게 담는 코드 이런 건 기능이 아니라 운영이다. 운영 코드가 동시성과 만나는 순간, “그냥 출력”이 “잠깐의 락”이 된다. 이번 PR은 “큰...
“용량은 그냥 숫자”라고 믿었던 대가: free-threaded에서 list.__sizeof__ 레이스가 생기는 지점 meta_description: free-threaded 빌드에서 list.__sizeof__가 리스트의 capacity 정보를 읽는 순간, 다른 스레드의 리사이즈와 맞물려 데이터 레이스가 날 수 있다. gh-145036(PR #145365)이 어떤 값을 어떻게 읽도록 바꿨는지, 그리고 실무에서 관찰/측정 코드를 어떻게 다뤄야 하는지 정리한다. meta_keywords: free-threaded, CPython, list, __sizeof__, capacity, allocated, data race, atomic, TSAN, thread sanitizer, list_resize, ob_item, memory model, 관찰코드, 측정, 디버깅, 동시성, GIL-less meta_robots: index,follow 리스트의 __sizeof__() 는 대개 “큰 의미 없는 관찰”로 취급된다. 로그에 한 번 찍고, 메모리 사용량 대충 추정하고, 병목을 찾아보는 정도. 나도 그렇게 써왔다. 그런데 free-threaded(실험적 GIL-less 방향)로 가면, 이런 관찰 코드도 동시성의 일부가 된다. “안전하게 실패하는 관찰”이 아니라 “관찰 자체가 경쟁에 끼어드는 순간”이 생긴다. 이번 gh-145036 / PR #145365는 그 지점을 건드린다. 요지는 과장하면 간단하다. 다른 스레드가 리스트의 내부 버퍼를 키우거나 줄이는 동안(capacity가 바뀌는 동안) 한 스레드가 list.__sizeof__() 에서 capacity 관련 값을 읽으면 그 읽기가 레이스로 잡힐 수 있다 여기서 중요한 건 “이 버전은 안전/위험” 같은 결론이 아니다. PR이 고친 건 특정 경로에서의 읽기 방식이고, 그게 왜 문제였는지를 이해하면 실무에서 얻을 힌트가 생긴다. 특히 TSAN으로 free-threaded를 돌리며 경...