40. 디스패치 스레드 블로킹과 교차 세션 상호 데드락 (C#)
난이도 상 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// [시나리오] 세션 디스패치 루프에서의 교차 세션 요청 (서버-클라 / 서버 내부)
//
// 서버는 소수의 IO/디스패치 스레드가 여러 세션의 수신 패킷을 처리한다.
// 한 세션의 패킷 핸들러 안에서, 상대 플레이어 세션에 "질의"(예: 거래 제안
// 확인, 결투 신청 응답)를 보내고 그 응답을 받아 처리해야 한다.
//
// SessionActor 는 세션별 처리를 순차화하기 위해 요청을 큐에 넣고 Task 로
// 응답을 돌려준다(액터/메일박스 모델).
//
// [요구사항]
// - 한 세션의 처리 중 다른 세션에 질의하더라도 디스패치 스레드가 막혀
// 다른 세션 처리가 정지되어서는 안 된다.
// - 두 세션이 서로에게 질의해도 교착되지 않아야 한다.
//
// [과제]
// 아래 HandleTradeProposal 을 리뷰하라. (A)(B) 지점의 동작이 디스패치
// 스레드와 세션 간 상호작용에 어떤 문제를 일으키는지 재현과 함께 설명하고
// 수정안을 제시하라.
using System.Threading.Tasks;
public class TradeHandler
{
private readonly SessionRegistry _sessions;
// 디스패치 스레드가 세션 A 의 수신 패킷으로 이 메서드를 호출한다.
public void HandleTradeProposal(SessionActor from, long targetId, TradeOffer offer)
{
SessionActor target = _sessions.Get(targetId);
if (target == null) { from.Send(new TradeRejected("상대 없음")); return; }
// (A) 상대 세션에 확인 질의를 보내고 응답을 동기로 기다림
bool accepted = target.AskConfirmTrade(from.PlayerId, offer).Result;
// (B)
if (accepted)
{
_sessions.ExecuteTrade(from, target, offer);
from.Send(new TradeAccepted());
}
else
{
from.Send(new TradeRejected("상대가 거절"));
}
}
}
public class SessionActor
{
public long PlayerId { get; }
// 세션 처리 스레드(디스패치 스레드)에서 실행되는 확인 로직
public Task<bool> AskConfirmTrade(long fromId, TradeOffer offer) { /* ... */ return Task.FromResult(true); }
public void Send(object packet) { /* ... */ }
} 결함 코드 · C++
// [시나리오] 세션 디스패치 루프에서의 교차 세션 요청 (서버 내부 / 서버-클라)
//
// 소수의 IO/디스패치 스레드가 여러 세션의 수신 패킷을 처리한다. 각 세션은
// 요청을 자신의 큐에 넣어 순차 처리하는 액터다(SessionActor). 한 세션의
// 패킷 핸들러 안에서 상대 세션에 질의(거래 확인 등)를 보내고 응답을 받아야 한다.
//
// [요구사항]
// - 한 세션 처리 중 다른 세션에 질의해도 디스패치 스레드가 막혀 다른 세션
// 처리가 정지되면 안 된다.
// - 두 세션이 서로 질의해도 교착되지 않아야 한다.
//
// [과제]
// 아래 HandleTradeProposal 을 리뷰하라. (A)(B) 지점의 동작이 디스패치
// 스레드와 세션 간 상호작용에 어떤 문제를 일으키는지 재현과 함께 설명하고
// 수정안을 제시하라.
#include <future>
#include <cstdint>
#include <string>
struct TradeOffer { /* ... */ };
struct SessionActor {
int64_t playerId;
// 상대 세션 처리 큐에 확인 작업을 넣고 future 로 결과를 돌려준다.
// 이 작업은 상대 세션을 담당하는 (같은 풀의) 디스패치 스레드에서 실행된다.
std::future<bool> AskConfirmTrade(int64_t fromId, const TradeOffer& offer);
void Send(const std::string& packet);
};
struct SessionRegistry {
SessionActor* Get(int64_t id);
void ExecuteTrade(SessionActor* a, SessionActor* b, const TradeOffer& offer);
};
class TradeHandler {
SessionRegistry* sessions_;
public:
// 디스패치 스레드가 세션 from 의 수신 패킷으로 호출한다.
void HandleTradeProposal(SessionActor* from, int64_t targetId, const TradeOffer& offer) {
SessionActor* target = sessions_->Get(targetId);
if (!target) { from->Send("TradeRejected:상대 없음"); return; }
// (A) 상대 세션에 확인 질의 후 응답을 동기로 대기
std::future<bool> f = target->AskConfirmTrade(from->playerId, offer);
bool accepted = f.get(); // 블로킹
// (B)
if (accepted) {
sessions_->ExecuteTrade(from, target, offer);
from->Send("TradeAccepted");
} else {
from->Send("TradeRejected:상대가 거절");
}
}
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.