인프런 커뮤니티 질문&답변

블티님의 프로필 이미지

작성한 질문수

[C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버

세션 레퍼런스 카운트 관련 질문입니다.

24.10.15 02:00 작성

·

16

0

 

void Session::ProcessConnect()
{
	_connectEvent.owner = nullptr; // (1)

	_connected.store(true);        // (2)

	// 세션 등록
	GetService()->AddSession(GetSessionRef());

	// 컨텐츠 코드에서 재정의
	OnConnected();

	// 수신 등록
	RegisterRecv();
}

5장 Session#3 강의를 듣고 따라해보다가 클라이언트 프로그램이 위 함수를 실행하다가 터지는 문제가 발생했습니다.

 

디버깅을 해보니

(1)에서 _connectEvent.owner의 strong ref가 1에서 0으로 줄어들면서 세션(this)이 삭제되고

(2)에서 this를 참조하면서 문제가 생긴 것이었습니다.

 

bool IocpCore::Dispatch(uint32 timeoutMs)
{
	DWORD numOfBytes = 0;
	ULONG_PTR key = 0;	
	IocpEvent* iocpEvent = nullptr;

	if (::GetQueuedCompletionStatus(_iocpHandle, OUT &numOfBytes, OUT &key, OUT reinterpret_cast<LPOVERLAPPED*>(&iocpEvent), timeoutMs))
	{
                // (A): 강사님 코드
                // strong ref: 1
		IocpObjectRef iocpObject = iocpEvent->owner;
                // strong ref: 2
		iocpObject->Dispatch(iocpEvent, numOfBytes);
                
                // (B): 제 코드
                // strong ref: 1
                //iocpEvent->owner->Dispatch(iocpEvent, numOfBytes);
	}
	else
	{
		int32 errCode = ::WSAGetLastError();
		switch (errCode)
		{
		case WAIT_TIMEOUT:
			return false;
		default:
			// TODO : 로그 찍기
			IocpObjectRef iocpObject = iocpEvent->owner;
			iocpObject->Dispatch(iocpEvent, numOfBytes);
			break;
		}
	}

	return true;
}

강사님 코드와 제 코드에서 어떤 부분이 다른지 확인해 본 결과 위와 같은 차이가 있었습니다.

 

강사님은 지역 변수로 스마트 포인터를 하나 만들어서 레퍼런스 카운트가 2인 상태로 세션->Dispatch 함수를 호출하는 반면, 저는 레퍼런스 카운트가 1인 상태로 세션->Dispatch 함수를 호출하는 차이가 있었습니다.

 



[질문 1] 혹시 강사님은 코드를 짜실 때 (B)와 같이 코드를 짜면 문제가 생긴다는 걸 인지하고 (A)와 같이 짜신 걸까요? 레퍼런스 카운트를 어느 정도까지 고려하면서 프로그래밍을 하는 것이 좋은지 궁금합니다.

 

[질문 2] 아래와 같이 코드를 수정해서 문제를 해결해도 별 문제가 없을까요?

// 원본 코드
void Session::ProcessConnect()
{
        // RELEASE_REF
	_connectEvent.owner = nullptr; 

	_connected.store(true);

	// 세션 등록
	GetService()->AddSession(GetSessionRef());

	// 컨텐츠 코드에서 재정의
	OnConnected();

	// 수신 등록
	RegisterRecv();
}
// 수정된 코드
void Session::ProcessConnect()
{
	_connected.store(true);

	// 세션 등록
	GetService()->AddSession(GetSessionRef());

        // RELEASE_REF
        _connectEvent.owner = nullptr;

	// 컨텐츠 코드에서 재정의
	OnConnected();

	// 수신 등록
	RegisterRecv();
}

답변 2

1

Rookiss님의 프로필 이미지
Rookiss
지식공유자

2024. 10. 15. 11:11

네 그런 이유가 맡긴 하나 처음 작업할 때 미래를 완전히 예측해서 작업하는 것은 아니고,
문제가 생기면 수정을 하면 되는 것입니다. 지금 하신것처럼요.
그리고 [이렇게 해도 될까요] 시리즈의 질문은 정말 필요한 질문이 아닙니다.
일단 해보시고 정상 작동 여부를 관찰해보세요.
언젠가 문제가 일어난다면 그 또한 좋은 경험치가 될 것입니다.

0

인프런 AI 인턴님의 프로필 이미지

2024. 10. 15. 02:06

안녕하세요, 인프런 AI 인턴입니다. 블티님, 세션 레퍼런스 카운트 관련하여 질문을 주셨군요. 레퍼런스 카운트는 특히 멀티스레드 환경에서는 매우 중요한 개념입니다. 이를 잘못 관리하면 프로그램이 예상치 못한 시점에 객체를 파괴하여 에러를 발생시킬 수 있습니다.

[질문 1에 대한 답변]
강사님이 코드에서 레퍼런스 카운트를 고려하여 지역 변수 IocpObjectRef로 소유권을 강하게 잡고 있는 이유는 객체가 Dispatch 함수 동안 안전하게 참조될 수 있도록 하기 위함입니다. 레퍼런스 카운트를 늘리면 해당 객체가 더 이상 사용되지 않을 때까지 파괴되지 않음을 보장할 수 있습니다. (B)와 같은 방법으로 코드를 작성할 때, 해당 참조가 유효해야 하는 동안 레퍼런스 카운트가 1로 유지되면 외부의 다른 동작으로 인해 객체가 파괴될 위험이 있습니다.

[질문 2에 대한 답변]
수정된 코드에서 _connectEvent.owner = nullptr;GetService()->AddSession(GetSessionRef()); 이후에 호출하는 방식으로 변경하였기 때문에, 코드의 문제는 해결될 것으로 보입니다. 이는 세션이 실제로 사용되고 난 후에 참조를 해제하도록 하여 객체의 안전성을 높이는 작업입니다. 이런 방식으로 레퍼런스 카운트를 정확히 관리하는 것이 좋습니다. 그러나, 이 역시 컴파일러의 최적화나 환경에 따라 다를 수 있으니 테스트를 충분히 거치는 것을 추천드립니다.

추가적인 관련 내용을 제공하는 게시물은 없지만, 아래 관련 정보를 확인해 보시면 도움이 될 수 있습니다.
- session의 생명주기 관련 질문입니다.

저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다. 현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏 추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.

블티님의 프로필 이미지

작성한 질문수

질문하기