WSARecv가 즉시 성공해서 버퍼를 정리했는데 IOCP 완료 패킷이 다시 왔습니다. 왜 이중 처리가 생기며 어떤 경로가 정리를 맡아야 하나요?
잠깐, 답변을 보기 전에
먼저 내 말로 설명해 볼까요?
답변과 점검 포인트 펼치기답변 접기
구두 답변
정의를 외우기보다, 원리와 조건을 연결해 설명해 보세요.
IOCP에 연결된 소켓에서 중첩 WSARecv를 제출할 때 즉시 성공했다는 반환은 작업이 빨리 완료됐다는 뜻이지, 항상 완료 포트 통지가 없다는 뜻은 아닙니다. 성공 완료 통지를 생략하는 설정을 사용하지 않았다면 즉시 완료도 완료 패킷으로 도착할 수 있으므로 제출 함수의 반환 직후 버퍼를 해제하면 이중 처리나 use-after-free가 생깁니다. 정상 완료, WSA_IO_PENDING, 제출 실패를 하나의 소유권 규칙으로 나누겠습니다.
제출 결과와 완료 경로
작업 컨텍스트와 버퍼 수명을 먼저 확보한 뒤 WSARecv(..., &ov)를 호출합니다. 반환이 0이면 데이터가 즉시 준비됐다는 뜻이고, IOCP 연결과 완료 통지 설정에 따라 완료 포트에서 그 결과를 처리할 수 있습니다. SOCKET_ERROR와 WSA_IO_PENDING이면 제출은 성공해 커널이 나중에 완료시키므로 동일하게 완료 경로를 기다립니다. 그 밖의 오류만 제출 자체 실패로 보고 완료 패킷이 없다는 계약 아래 정리합니다. 이를 완료 통지 소유권(completion notification ownership)으로 명시하겠습니다.
의사 흐름은 다음과 같습니다.
작업·버퍼 수명 확보
ret = WSARecv(socket, ..., &ov)
ret == 0 또는 (ret == SOCKET_ERROR이고 WSAGetLastError() == WSA_IO_PENDING)
-> 완료 포트가 결과와 정리를 담당
그 밖의 즉시 오류 -> 제출 실패 경로가 작업을 정리
즉시 성공한 뒤 제출 스레드가 결과를 직접 처리하고 완료 워커도 같은 결과를 처리하면 두 번 파싱하거나 두 번 free합니다. 반대로 완료 통지를 생략하는 옵션을 적용하면 성공한 작업을 제출 스레드가 처리할 수 있지만, pending·실패·다른 I/O와의 분기가 복잡해집니다. 어떤 핸들에서 성공 통지를 생략하는지와 사용자 정의 규약을 명확히 하지 않으면 일부 작업만 두 경로를 타게 됩니다.
짧은 경쟁 창과 검증
완료 워커는 WSARecv가 반환하기 전에 실행될 수도 있습니다. 따라서 제출 후에 참조를 확보하거나, 반환값을 기록하기 전에 컨텍스트를 반환하는 순서는 안전하지 않습니다. 작업의 상태를 제출 전 예약하고, 완료 경로가 참조를 한 번만 내려놓도록 설계하겠습니다. 수신 버퍼를 연결 컨텍스트 내부에 둘 때도 다음 수신 제출 전 이전 완료가 끝났는지 보장해야 합니다.
처음에는 즉시 성공과 pending 모두 같은 완료 포트 경로로 처리해 소유권을 단순화하고, 통지 생략 최적화는 측정 후 선택하겠습니다. 테스트는 루프백처럼 즉시 성공이 잦은 환경, 강제로 pending을 만드는 환경, 즉시 오류, 취소 경합, 완료 워커의 재진입을 포함합니다. 기본 통지 모드에서 즉시 성공과 pending으로 수락된 작업의 합이 정상·실패 완료 패킷의 합과 맞는지, 각 작업의 결과 적용과 메모리 해제가 정확히 한 번인지, WSA_IO_PENDING을 실패로 세지 않는지를 검증 기준으로 삼겠습니다.
즉시 완료와 pending은 제출 함수의 관찰 결과이고, 완료 포트 패킷의 도착은 별도의 통지 계약입니다. 따라서 작업 컨텍스트를 먼저 예약하고, 제출 결과를 기록하는 동안 완료 워커가 작업을 끝낼 수 있다는 재진입까지 고려해야 합니다. 통지 생략 모드를 쓰려면 핸들에 적용된 모드와 각 API의 성공·실패 경로를 문서화하고, 성공한 작업을 제출 스레드가 처리할 때 pending 작업과 동일한 카운터·참조 규칙을 맞추겠습니다. 최적화 전후에 작업당 한 번의 바이트 소비와 한 번의 반환을 검증하겠습니다.
작업 수명과 결과 적용 수명을 나누는 것도 중요합니다. 연결이 논리적으로 닫힌 뒤 즉시 성공 완료가 도착하면 버퍼를 읽어 작업을 종결할 수는 있지만, 그 데이터를 새 세션 상태에 적용하지는 않습니다. 완료 경로의 단일 소유자와 연결 세대 검사를 두면 즉시 완료와 지연 완료가 같은 상위 불변식을 따르게 됩니다.
득점 포인트
이해가 드러나는 지점을 확인해 보세요.
- 즉시 성공·pending·제출 실패를 완료 통지 계약과 함께 분리한다.
- 기본 IOCP 통지에서 즉시 성공 뒤에도 완료 패킷이 올 수 있음을 보존한다.
- 통지 생략 옵션의 이점과 분기·수명 관리 복잡성을 비교한다.
- 제출 전 수명 확보와 정확히 한 번의 정리 경로를 제시한다.
감점 포인트
놓치기 쉬운 조건과 오해를 짚어 보세요.
- WSARecv가 0을 반환하면 항상 완료 패킷이 없다고 말한다.
WSA_IO_PENDING을 일반 제출 실패로 처리해 작업을 조기 해제한다.- 즉시 성공 경로와 완료 워커가 같은 버퍼를 각각 정리하게 둔다.
더 파고들 거리
하나 더 질문하며 이해의 범위를 넓혀 보세요.
- FILE_SKIP_COMPLETION_PORT_ON_SUCCESS를 적용합니다. 즉시 성공과 pending 완료의 정리 경로는 어떻게 달라지나요?
- 완료 worker가 WSARecv 호출 반환보다 먼저 실행됩니다. 작업 참조 획득과 제출 실패 rollback은 어떤 순서를 지켜야 하나요?
- 통지 생략 성공과 pending 실패를 공통 작업 상태 머신으로 어떻게 표현할까요?
득점·감점 포인트는 학습용 자기 점검 기준이며, 특정 회사의 실제 채점표가 아닙니다.
원리부터 공부하고 싶다면
답변을 보충하는 개념·예제·슈도코드를 읽어 보세요.