프로그래머의 일상: race-condition, 글쓰기, …

Race condition

5월 말에 예비군 훈련을 다녀오고나니, 내가 작성한 C++/Win32 기반의 서버 런타임에 문제가 발생했더라. 정확한 요인은 모르겠지만, 메모리 사용량이 팍 뛰었고, 32bit 프로세스의 한계로 사망 -_-

다행히 ipkn이 작성한 부하 테스트 툴로 원인을 좁힐 수 있었다. 해당 프로세스에 미리 할당하는 소켓 수 이상으로,

  1. 연결을 계속해서 맺고
  2. 과다한 트래픽을 흘려보내고,
  3. 다시 끊고

이 동작을 수 시간 반복하면 서버가 크래시하더라.

이미 수 개월 동안의 라이브 서비스 동안 별 문제 없어서 안정화 끝났다라고 생각했는데 그게 아니었던 모양.

처음 찾은 버그는 이런 거였다. 대략 소켓 래퍼 객체의 참조 카운트를 가지고 최종적으로 할당된 자원을 재활용하는데, 단 하나의 시나리오에서 이 패킷 버퍼와 소켓의 참조 카운트 해제하는 순서가 반대로 되어있더라. 그래서 대략 k 시간의 코드 리뷰 후에 잡았음. 그나마 패킷 버퍼 쪽에서 문제가 되는 코어 덤프가 있었으니 다행.

이 상태로 다시 부하 테스트 시작. 또 죽는다? 다행히도 디버그 빌드에서도 재현이 된다. 문제는 heap corruption이라는 것? 정확히 1 bytes가 0로 셋팅되더라.1 그래서 서버 런타임 전체에서 1바이트 세팅하는 코드를 전부 뒤졌다. 그 결과, 일부 per-thread 타이머 객체가,

  • 해당 스레드에서 타이머 객체가 종료되어 자원 해제
  • 다른 스레드에서 해당 타이머 객체 invalidate

하는 경우가 있을 수 있더라. 역시나 참조 카운터 문제 -_-; 선형화처리 코드는 들어가 있지만, 해당 객체가 살아있다는 보장이 없는 상황이라, 대부분 잘 돌지만 이렇게 레이스가 일어나면 얼마든지 맛이 갈 수 있더라;

이 두 가지 문제 해결하고나니 대략 (눈에 보이는) 버그는 잡은 거 같다. 적어도 부하 테스트는 잘 버티고 있다.

근데 애초에 문제가 되었던, 갑자기 메모리 사용량 1G 점프하던건 뭐였을까? 십중팔구는 처리가 늦어지고, 그에 따라 센드 큐가 늘어나고, 그에 따라 처리가 더 늦어지고 하는 악순환이 아니었을까 하는게 내 추측.

만약 크래시 덤프도 없고, 디버그 모드에서 재현 못했으면 어쨌을지를 생각하니 좀 끔찍하긴 하구나.


  1. Win32 DEBUG heap이 마킹해주는 덕에 이건 파악할 수 있었다. 정말 다행임. ↩︎