<aside> ✅

"옮겼다"와 "같은 동작인가"는 다른 이야기다. 컴파일이 통과하고 서버가 뜨는 것까지는 앞 문서들에서 다뤘고, 여기는 그다음이다 — 옮긴 것이 원래와 같게 도는지를 무엇으로 확인했는가.

찾아낸 것을 숨기지 않는다. 도구가 경고를 뱉은 것, 판정 방법이 틀렸던 것, 나중에 다시 훑어 잡은 것까지 그대로 적는다. 검증의 값어치는 초록불이 아니라 무엇을 어떻게 의심했는가에 있다.

</aside>


1. 락프리 자료구조 — 양 OS 결과가 같은가

이식의 중심이 락프리였으니(→ 2. 락프리를 리눅스로), 검증의 중심도 여기다. 원래 있던 테스트 6종을 양 OS에서 돌려 결과를 맞대봤다.

테스트 인자 Windows Linux
repro 8 64 10 rc=0 위반 미검출 rc=0 위반 미검출
cons rc=0 완주 rc=0 완주
fl 8 10 rc=0 위반 미검출 rc=0 위반 미검출
tls 4 4 10 rc=0 위반 미검출 rc=0 위반 미검출
tlsleak 20 4 5 rc=0 누수 확인(알려진 한계) rc=0 누수 확인(알려진 한계)
qbuf 8 10 rc=0 무결(버퍼 보존) rc=0 무결(버퍼 보존)

<aside> 🔑

tlsleak양쪽 모두 "누수 확인"인 것이 중요하다. 리눅스에서 새로 생긴 문제가 아니라 원래 알려진 한계라는 뜻이다.

"리눅스에서 이상하다"를 판정하려면 Windows 쪽 결과가 있어야 한다. 대조 없이 리눅스만 보면 원래 그랬던 것도 이식 탓으로 보인다.

</aside>

경합을 억지로 만들어서도 돌렸다

일반 실행에서 통과하는 것으로는 부족하다. 락프리 자료구조의 문제는 타이밍이 나쁠 때만 드러난다.

그래서 LF_RACE_HOOK 증폭 빌드 — CAS 주변에 지연을 넣어 경합 창을 억지로 벌리는 빌드 — 로 6종을 다시 돌렸다. 전부 rc=0(repro 30초 / cons / fl 20초 / tls 20초 / qbuf 15초 / tlsleak).

<aside> 🕳️

여기서 하마터면 속을 뻔했다 — 증폭이 실제로 걸렸는지부터 확인해야 한다.

증폭 빌드가 통과했다는 것만으로는 아무 의미가 없다. 매크로가 안 걸렸으면 그냥 일반 빌드를 돌린 것이고, 그래도 초록불이 뜬다.

테스트가 스스로 찍는 라벨로 확인했다 — 일반은 RACE_HOOK OFF - 대조군, 증폭은 RACE_HOOK ON. 이 줄이 정본이다.

</aside>

<aside> ⚠️

같은 함정이 qbuf에도 있다. 이 테스트는 검증 조건이 한 번도 발생하지 않으면 rc=1("테스트 무효")을 낸다. rc=0이라는 것이 곧 "유효한 실행이었다"는 뜻이라, 그냥 통과가 아니라 그 구분까지 봐야 한다.

</aside>

2. TSan과 ASan — 보고를 어떻게 읽었나 ★

리눅스에는 Windows에 없던 도구가 있다. 스레드 경합 검출기(TSan)와 메모리 검사기(ASan)를 돌려봤다.

TSan은 모든 테스트에서 경합을 보고했다. 그냥 넘길 수 없는 결과다.

이식 탓인지부터 갈랐다

의심할 곳이 분명했다 — 16바이트 CAS를 옛 __sync로 쓴 것(→ 2. 락프리를 리눅스로 3장). TSan은 __atomic은 이해하지만 __sync는 잘 못 읽는다고 알려져 있다.

그래서 __atomic으로 바꾼 판을 따로 만들어 대조했다.

테스트 __sync __atomic
fl 4 5 race 5 race 5
cons race 29 race 30

수가 그대로다. TSan이 이해하는 쪽으로 바꿔도 안 줄었으니 __sync는 원인이 아니다.