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#
내 답안 · 자동 저장

작성 후 위 해설 보기에서 모범 해설과 대조하세요.