31. 여러 이벤트 경로에서 트리거되는 연결 정리의 중복 실행 (이중 정리)
난이도 중 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// [시나리오] 클라이언트 세션 연결 정리(cleanup) (서버-클라)
// 세션은 다음 세 가지 서로 다른 이벤트 경로로 종료될 수 있다.
// 1) 소켓 수신/송신 중 오류 발생 (에러 콜백)
// 2) heartbeat 타임아웃 감시 타이머가 만료 감지 (타임아웃 콜백)
// 3) 클라이언트가 정상적으로 로그아웃 패킷을 보냄 (명시적 종료 요청)
// 이 세 경로는 서로 다른 스레드(수신 스레드, 타이머 스레드, 워커 스레드)에서
// 독립적으로 트리거될 수 있고, 셋 다 같은 세션의 "연결 정리" 루틴을 호출한다.
//
// [요구사항]
// - 한 세션의 연결 정리(리소스 해제, DB 저장, 다른 시스템에 알림)는 정확히
// 한 번만 수행되어야 한다.
// - 여러 경로가 거의 동시에 트리거되어도 위 요구사항이 지켜져야 한다.
//
// [과제] SessionManager.HandleError, HandleTimeout, HandleLogout 과 공통으로 호출되는
// Cleanup 을 검토하고, 여러 경로가 동시에 트리거될 때 위 요구사항이 어떻게
// 어긋날 수 있는지 원인과 수정안을 제시하라. 표시된 (A)(B) 지점에 주목하라.
using System;
using System.Collections.Generic;
class PlayerSession
{
public int SessionId;
public bool IsClosed;
}
class SessionManager
{
private readonly Dictionary<int, PlayerSession> _sessions = new();
private void SaveToDb(PlayerSession s) { /* 최종 상태 DB 저장 */ }
private void NotifyOtherSystems(PlayerSession s) { /* 길드/파티 등에 퇴장 통지 */ }
private void ReleaseResources(PlayerSession s) { /* 버퍼/소켓 핸들 반납 */ }
// 소켓 에러 콜백 스레드에서 호출
public void HandleError(int sessionId)
{
if (_sessions.TryGetValue(sessionId, out var s))
Cleanup(s);
}
// heartbeat 타이머 스레드에서 호출
public void HandleTimeout(int sessionId)
{
if (_sessions.TryGetValue(sessionId, out var s))
Cleanup(s);
}
// 로그아웃 패킷을 처리하는 워커 스레드에서 호출
public void HandleLogout(int sessionId)
{
if (_sessions.TryGetValue(sessionId, out var s))
Cleanup(s);
}
private void Cleanup(PlayerSession s)
{
// (A)
if (s.IsClosed)
return;
SaveToDb(s);
NotifyOtherSystems(s);
ReleaseResources(s);
// (B)
s.IsClosed = true;
_sessions.Remove(s.SessionId);
}
} 결함 코드 · C++
// [시나리오] 클라이언트 세션 연결 정리(cleanup) (서버-클라)
// 세션은 다음 세 가지 서로 다른 이벤트 경로로 종료될 수 있다.
// 1) 소켓 수신/송신 중 오류 발생 (에러 콜백)
// 2) heartbeat 타임아웃 감시 타이머가 만료 감지 (타임아웃 콜백)
// 3) 클라이언트가 정상적으로 로그아웃 패킷을 보냄 (명시적 종료 요청)
// 이 세 경로는 서로 다른 스레드(수신 스레드, 타이머 스레드, 워커 스레드)에서
// 독립적으로 트리거될 수 있고, 셋 다 같은 세션의 "연결 정리" 루틴을 호출한다.
//
// [요구사항]
// - 한 세션의 연결 정리(리소스 해제, DB 저장, 다른 시스템에 알림)는 정확히
// 한 번만 수행되어야 한다.
// - 여러 경로가 거의 동시에 트리거되어도 위 요구사항이 지켜져야 한다.
//
// [과제] SessionManager::handleError, handleTimeout, handleLogout 과 공통으로 호출되는
// cleanup 을 검토하고, 여러 경로가 동시에 트리거될 때 위 요구사항이 어떻게
// 어긋날 수 있는지 원인과 수정안을 제시하라. 표시된 (A)(B) 지점에 주목하라.
#include <unordered_map>
struct PlayerSession {
int sessionId;
bool isClosed = false;
};
class SessionManager {
std::unordered_map<int, PlayerSession*> sessions_;
void saveToDb(PlayerSession* s) { /* 최종 상태 DB 저장 */ }
void notifyOtherSystems(PlayerSession* s) { /* 길드/파티 등에 퇴장 통지 */ }
void releaseResources(PlayerSession* s) { /* 버퍼/소켓 핸들 반납 */ }
void cleanup(PlayerSession* s) {
// (A)
if (s->isClosed)
return;
saveToDb(s);
notifyOtherSystems(s);
releaseResources(s);
// (B)
s->isClosed = true;
sessions_.erase(s->sessionId);
delete s;
}
public:
// 소켓 에러 콜백 스레드에서 호출
void handleError(int sessionId) {
auto it = sessions_.find(sessionId);
if (it != sessions_.end())
cleanup(it->second);
}
// heartbeat 타이머 스레드에서 호출
void handleTimeout(int sessionId) {
auto it = sessions_.find(sessionId);
if (it != sessions_.end())
cleanup(it->second);
}
// 로그아웃 패킷을 처리하는 워커 스레드에서 호출
void handleLogout(int sessionId) {
auto it = sessions_.find(sessionId);
if (it != sessions_.end())
cleanup(it->second);
}
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.