GN⁺: C stdlib의 비스레드 안전성과 안전한 Rust의 실패
(edgedb.com)-
ARM64에서만 발생한 충돌
- EdgeDB의 네트워크 I/O 코드를 Python에서 Rust로 포팅하는 과정에서 ARM64 CI 러너에서 테스트가 간헐적으로 실패하는 문제가 발생함.
- 처음에는 데드락으로 보였으나, 실제로는 프로세스가 충돌하여 테스트 러너가 이를 감지하지 못한 것임.
-
초기 이론
- ARM64에서만 문제가 발생하는 이유를 이해하기 위해 메모리 모델의 차이를 고려함.
- Intel의 메모리 모델은 엄격한 반면, ARM은 더 약한 메모리 모델을 가짐.
-
CI 머신에서의 디버깅
- AWS에서 ARM64 러너에 직접 연결하여 문제를 조사함.
- 프로세스가 충돌하여 코어 덤프를 남겼고, 이를 조사하여 문제의 원인을 파악함.
-
실제 원인: setenv와 getenv
- setenv는 멀티스레드 환경에서 안전하지 않으며, getenv와의 상호작용에서 충돌을 일으킬 수 있음.
- 환경 변수의 재할당이 문제의 원인으로 밝혀짐.
-
openssl_probe와의 연결
- openssl-probe가 SSL_CERT_FILE과 SSL_CERT_DIR 환경 변수를 설정하면서 문제가 발생함.
- Rust의 rust-native-tls가 이러한 환경 변수를 설정하는 과정에서 충돌이 발생함.
-
ARM64 Linux에서만 발생한 이유
- 여러 조건이 맞아떨어져야만 충돌이 발생하며, 환경 변수의 수와 I/O 실패 등이 그 조건에 해당함.
-
해결책
- reqwest의 rust-native-tls/openssl 백엔드에서 rustls로 전환하기로 결정함.
- Rust 프로젝트는 환경 설정 함수를 비안전하게 만들 계획이며, glibc 프로젝트는 getenv의 스레드 안전성을 향상시킴.
Hacker News 의견
-
Rust의 다음 에디션에서 환경 설정자를 안전하지 않게 만들 예정임. 이는 충돌을 일으키는 크레이트에 영향을 줄 수 있음
- Rust 표준 라이브러리에서
set_var
와remove_var
는 2024년 에디션에서unsafe {}
블록을 사용해야 함 - 현재 문서에는 안전성 문제를 언급하고 있지만, 원래 이러한 함수들을 안전하게 만든 것은 실수였음
- Rust 표준 라이브러리에서
-
glibc에 대한 패치가
getenv
를 더 안전하게 만들었지만, C는 여전히 환경에 직접 접근을 허용하여 완전히 안전하지는 않음- C 표준 라이브러리 유지보수자들이
setenv
를 멀티스레드 안전하게 만들기를 꺼려하지만, 최소한 새로운 스레드 안전 API가 정의되어야 함 - Musl의 유지보수자가 이 문제를 해결할 수 없다고 확신하지 않음
- C 표준 라이브러리 유지보수자들이
-
리눅스에서 환경 관련 버그를 겪는 것은 일종의 통과의례처럼 여겨짐
- Linus와 커널은 POSIX 버그를 해결하는 데 실용적이지만, glibc는 여전히 뒤처져 있음
-
getenv_r()
를 제공하고setenv()
와 동기화하며 컴파일/링크 시 경고를 제공하는 것이 문제 해결에 도움이 되었을 것임
-
환경 변수를 사용한 설정은 "12-factor app" 운동의 일부였지만, 이는 어리석은 방법이라고 생각함
- 환경 변수 대신 YAML과 같은 설정 파일을 사용하는 것이 더 나은 방법이라고 생각함
-
Amazon AWS에서 실행되는 CI 머신은 실제 루트 사용자를 제공하여 장점이 있음
- 클라우드와 컨테이너 없이 로컬에서 코드 빌드 및 디버그 능력을 잃은 것 같음
-
비직관적인 버그를 파헤치는 훌륭한 기사임
- 이러한 상세한 문제 해결 보고서는 직접 해보는 것과 가장 가까운 경험을 제공함
-
env::set_var
는 이제 안전하지 않음- 단일 스레드 프로그램에서는 안전하게 호출할 수 있음
- Windows에서는 단일 및 멀티 스레드 프로그램 모두에서 항상 안전함
- 다른 운영 체제의 멀티 스레드 프로그램에서는
set_var
또는remove_var
를 사용하지 않는 것이 유일한 안전한 선택임
-
setproctitle
이 특정 코드베이스에서 작동하지 않았던 경험을 상기시킴-
numpy
를 임포트한 후setproctitle
이 작동하지 않았고, 이는numpy
초기화 시getenv
또는setenv
호출로 인해 **environ의 주소가 변경되었기 때문임
-