24. epoll 엣지 트리거 vs 레벨 트리거와 논블로킹 EAGAIN 드레인
난이도 상내 답안
모범답안
난이도: 상
1. 레벨 트리거(LT) vs 엣지 트리거(ET)
- LT(기본): 조건이 참인 동안 계속 보고한다. 수신 버퍼에 읽지 않은 데이터가 남아 있으면 다음
epoll_wait에서도 EPOLLIN이 또 나온다. 한 번에 다 안 읽어도 다음 루프에서 마저 읽으면 된다. - ET: 상태가 바뀌는 순간(엣지) 딱 한 번만 보고한다. "비어 있음 → 데이터 도착"의 전이에서 EPOLLIN이 한 번 나오고, 남은 데이터가 있어도 새 데이터가 더 도착하기 전까지는 다시 보고하지 않는다.
2. ET에서 EAGAIN까지 드레인해야 하는 이유
ET는 전이 시 1회만 통지하므로, 통지를 받았을 때 커널 수신 버퍼를 완전히 비워야(drain) 한다. recv를 한 번만 호출하고 남은 데이터를 두면, 그 데이터에 대한 추가 통지가 오지 않아 영영 읽히지 않고 매달린다(연결이 "멈춘 것처럼" 보이는 버그). 올바른 패턴:
for (;;) {
ssize_t n = recv(fd, buf, sizeof(buf), 0);
if (n > 0) { process(buf, n); continue; } // 계속 읽기
if (n == 0) { close_connection(fd); break; } // 상대가 정상 종료(FIN)
if (n < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) break; // 버퍼 다 비움 → 정상
if (errno == EINTR) continue; // 시그널 인터럽트 → 재시도
close_connection(fd); break; // 실제 오류
}
}
즉 EAGAIN이 "이제 더 읽을 게 없다"는 종료 신호이고, 그때까지 루프를 돈다.
3. recv/send 반환값 해석
- n > 0: 실제로 읽은/보낸 바이트 수.
send는 요청보다 적게 보낼 수 있으므로(부분 전송) 남은 만큼 다시 시도해야 한다. - n == 0 (recv): 상대가 연결을 정상 종료(FIN) 했다. 연결을 정리한다. (send의 0은 보통 0바이트 요청이므로 다르게 취급.)
- -1, errno == EAGAIN/EWOULDBLOCK: 지금은 더 읽을/쓸 수 없음. 오류가 아니다. ET에선 드레인 종료 신호, LT에선 다음 통지를 기다린다.
- -1, errno == EINTR: 시그널로 중단됨. 재시도한다(오류 아님).
- 그 외 -1: 실제 오류(ECONNRESET 등) → 연결 종료.
4. ET에서의 쓰기(EPOLLOUT)
송신 버퍼는 대개 여유가 있으므로 EPOLLOUT이 거의 항상 참이다. 이를 상시 등록해두면 매 루프마다 쓸모없이 깨어나 CPU를 태운다(busy loop). 올바른 방식:
- 평소엔 EPOLLOUT을 등록하지 않고,
send를 시도한다. send가 EAGAIN을 반환하면(커널 송신 버퍼가 참) 그때 남은 데이터를 앱 송신 큐에 쌓고 EPOLLOUT을 등록한다.- EPOLLOUT 통지가 오면 큐를 다시 flush 하고, 큐가 비면 EPOLLOUT을 해제한다.
이렇게 "보낼 게 막혔을 때만 EPOLLOUT을 켠다"가 표준 관용구다.
5. LT vs ET 트레이드오프와 선택
- LT: 구현이 쉽고 실수에 관대(한 번에 다 안 읽어도 됨). 대신 데이터가 남아 있는 동안 반복 통지되어 시스템콜/웨이크업이 많을 수 있고, 한 fd가 계속 준비 상태면 다른 fd가 밀리는 형평성 이슈를 앱이 관리해야 한다.
- ET: 통지가 적어 고성능·저오버헤드. 대신 반드시 EAGAIN까지 드레인해야 하고, 부분 읽기/쓰기·starvation(한 fd를 너무 오래 드레인해 다른 fd 굶김)을 앱이 신경 써야 한다(fd당 처리량 상한을 두고 라운드로빈).
- 게임서버 선택: 수천~수만 동접의 고성능 IO 루프는 ET + 논블로킹 + 완전 드레인 + 송신 큐/EPOLLOUT 온디맨드가 정석이다(높은 처리량, 적은 웨이크업). 다만 구현 복잡도가 높으므로, 규모가 작거나 단순함이 중요하면 LT로 시작해도 무방하다. (윈도우는 epoll 대신 IOCP를 쓰며 completion 기반이라 모델 자체가 다르다.)
면접 포인트
- ET의 "엣지" 의미와 드레인 필수성, 한 번만 읽으면 매다는 버그.
- 논블로킹 반환값(0/EAGAIN/EINTR/오류)의 정확한 처리.
- EPOLLOUT 온디맨드 등록 관용구와 ET starvation 방지(fd당 처리량 상한).