Go channel request-response 패턴에서 내가 저지른 hang 버그 3가지
개발 중인 애플리케이션 내부에서 DR 환경 테스트 중 특정 조건에서 HTTP 요청이 30초 타임아웃까지 hang되는 버그가 발견됐어요. 처음엔 “Go channel이 원래 이런가?” 싶었는데, 파보니 Go나 채널 자체의 문제가 전혀 아니었어요. 채널을 다루는 제 코드에 있던 사소한 실수 세 가지가 원인이었어요.
버그 1: 응답이 등록보다 먼저 도착하는 race
DATA를 전송하고 DAOK(응답 확인) 채널을 등록하는 순서가 문제였어요.
1 | |
waitForResponse 내부에서 채널을 만들고 map에 등록하는데, 서버가 Send 직후 DAOK/ERCD를 응답하면 등록 전이라 notify가 유실되고, 결국 daokTimeout(최대 30초)까지 대기해요.
수정: Send 전에 채널을 등록해요.
1 | |
버그 2: ERCD seq 불일치로 대기자를 못 깨우는 문제
서버에서 ERCD 13(DATA 내용 오류) 같은 일부 에러를 DATA와 다른 seq(혹은 0) 로 응답해요. 기존 코드는 notifyPendingResponse(frame.Sequence(), cf)로 seq를 키로 대기자를 찾는데, seq가 달라서 대기자를 찾지 못하고 넘어가요. 결과적으로 RequestData()의 DAOK 대기자가 timeout까지 hang돼요.
수정: ERCD seq로 대기자를 못 찾으면 현재 in-flight DATA seq로 폴백해요.
1 | |
inFlightDataSeq는 atomic.Uint32로 관리해요. DATA 전송 시작 시 저장하고, 완료(성공/실패) 시 0으로 초기화해요.
버그 3: HTTP ctx 미전파로 인한 불필요한 대기
HTTP 요청이 클라이언트 타임아웃으로 이미 끊겼는데, DAOK 대기가 최대 30초 더 진행되던 문제예요. ctx를 RequestData까지 전파하고, awaitPending에서 ctx.Done()을 함께 감시해요.
1 | |
정리
| 버그 | 원인 | 수정 |
|---|---|---|
| notify 유실 | Send 후 채널 등록 | Send 전에 registerPending |
| ERCD hang | seq 불일치로 대기자 못 찾음 | inFlightDataSeq 폴백 |
| 불필요한 30초 대기 | ctx 미전파 | awaitPending에 ctx.Done() 추가 |
세 버그 모두 언어 탓이 아니라 채널을 쓰는 쪽에서 흔히 저지를 수 있는 구현 실수였어요. Go channel 기반 request-response 패턴에서 “등록은 항상 전송 전에”, “대기 키는 응답 경로와 반드시 일치”, “ctx는 끝까지 전파” — 이 세 가지만 점검해도 비슷한 hang은 피할 수 있어요.
오늘의 코딩 메모 끝~
eod