WSARecv, WSASend)과 완료 통지(GQCS) 사이에는 시간이 있습니다.Session* 접근 -> use-after-free"등록된 I/O가 1개라도 남아 있으면 Session은 살아 있어야 한다."
IocpEvent)에 owner(shared_ptr<Session>)를 붙여 수명을 고정합니다.bool Session::RegisterRecv()
{
_recvEvent.owner = shared_from_this(); // +1
DWORD flags = 0;
DWORD bytes = 0;
int32 ret = WSARecv(_socket, &_recvEvent.wsaBuf, 1, &bytes, &flags,
&_recvEvent.overlapped, nullptr);
if (ret == SOCKET_ERROR && WSAGetLastError() != WSA_IO_PENDING)
{
_recvEvent.owner.reset(); // 실패 시 즉시 해제
return false;
}
return true;
}
void Session::ProcessRecv(RecvEvent* ev, int32 numOfBytes)
{
// ... 처리 ...
ev->owner.reset(); // 완료 처리 경로에서 해제
}
GQCS FALSE && ov != nullptr인 실패 완료도 "완료 이벤트"이므로 ref 해제 대상입니다.make_shared<Session>() 같은 단일 control block으로 시작합니다.shared_from_this()를 사용합니다.Session* raw = this;
std::shared_ptr<Session> wrong(raw); // 금지: 별도 control block 생성 위험
shared_from_this() 호출은 안전하지 않을 수 있습니다.shared_ptr에 소유된 이후 시점에서만 호출하세요.owner가 Session을 shared_ptr로 참조owner.reset() 누락 시 self-reference가 남아 세션이 해제되지 않음| 방법 | 장점 | 주의 |
|---|---|---|
완료 시 owner.reset() 강제 | 단순하고 직관적 | 모든 경로에서 누락 없이 수행 |
weak_ptr + 필요 시 lock | 순환 참조 위험 감소 | lock 실패 경로 처리 필요 |
별도 pendingIoCount refcount | 명시적 수명 모델 | 구현 난이도 증가 |
owner 해제 누락을 막기 위해 처리 함수를 템플릿화하거나 공통 후처리 헬퍼를 두는 것이 좋습니다.ERROR_OPERATION_ABORTEDERROR_NETNAME_DELETED이 값들은 종료 과정에서 예상 가능한 경로일 수 있습니다.
Disconnecting 상태 전환 (신규 I/O 등록 금지)shared_ptr는 수명 문제 해결 도구이지, 동시성 문제 전체 해결 도구가 아닙니다.| 오해 | 바로잡기 |
|---|---|
shared_ptr 쓰면 모든 문제가 해결된다 | 수명만 해결, 상태 동기화는 별도 설계 필요 |
| owner는 성공 경로에서만 해제하면 된다 | 실패 완료/취소 경로에서도 반드시 해제 |
| Completion Key만 안전하면 충분하다 | Event owner와 종료 순서가 함께 맞아야 안전 |
GQCS FALSE && ov != nullptr에서 owner를 해제하지 않으면 어떤 문제가 생기는가?