11. 계정 정지/강제 종료(킥) vs 진행 중인 정상 처리

난이도 중 해설 보기 →

결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커 (A)(B) 는 주목 위치 힌트다.

결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 계정 정지/강제 종료(킥) 명령 vs 진행 중인 정상 처리
// ----------------------------------------------------------------------------
// 시나리오:
//   - 운영툴/어드민이 어떤 플레이어를 "정지(ban) + 즉시 강제 종료(kick)" 할 수 있다.
//     이 명령은 별도의 어드민 스레드에서 SessionManager 로 들어온다.
//   - 동시에 그 플레이어의 게임 패킷은 IO/로직 워커 스레드에서 정상 처리 중일 수 있다
//     (예: 거래 체결, 보상 지급, 진행도 저장 같은 다단계 작업).
//   - 강제 종료는 소켓을 닫고 세션을 레지스트리에서 제거한다.
//
// 요구사항:
//   - 정지된 플레이어는 정지 시점 이후의 행동(아이템 획득/거래 체결 등)을
//     완료할 수 없어야 한다(부정 이득 차단).
//   - 강제 종료 중에도 서버는 죽으면 안 되고, 세션 자원(소켓 등)은 정확히
//     한 번만 정리되어야 한다(이중 정리/누수 금지).
//   - 진행 중이던 정상 작업이 "절반만 적용된 상태"로 남으면 안 된다.
//
// 과제:
//   이 코드의 잠재적 문제를 모두 찾고, 왜/어떻게 발생하는지 설명하고,
//   수정안과 더 나은 설계를 제시하라.
//   (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================

using System;
using System.Collections.Generic;
using System.Net.Sockets;

public class Player
{
    public long Id;
    public bool Banned;     // (A) 정지 플래그
    public void ApplyReward(int amount) { /* 골드/아이템 지급 (생략) */ }
    public void SaveProgress() { /* DB 저장 (생략) */ }
}

public class GameSession
{
    public long Id;
    public Player Player;
    public Socket Socket;
    public bool Closed;     // (B) 종료 여부

    public GameSession(long id, Player p, Socket s) { Id = id; Player = p; Socket = s; }

    // 워커 스레드: 정상 게임 패킷 처리(다단계 작업의 예)
    public void HandleRewardPacket(int amount)
    {
        if (Player.Banned) return;          // (C) 진입 시 1회 검사

        // ... 검증/계산 등 시간이 걸리는 작업 ...
        Player.ApplyReward(amount);         // (D) 보상 지급
        Player.SaveProgress();              // (E) 진행도 저장
    }

    public void Close()
    {
        Closed = true;
        Socket.Close();                     // (F) 소켓 닫기/Dispose
    }
}

public class SessionManager
{
    private readonly Dictionary<long, GameSession> _sessions = new();

    public void Add(GameSession s) { _sessions[s.Id] = s; }

    public GameSession Find(long sessionId)
        => _sessions.TryGetValue(sessionId, out var s) ? s : null;

    // 어드민 스레드: 정지 + 강제 종료
    public void BanAndKick(long sessionId)
    {
        GameSession s = Find(sessionId);
        if (s == null) return;

        s.Player.Banned = true;             // (G) 정지 표시
        s.Close();                          // (H) 강제 종료
        _sessions.Remove(sessionId);        // (I) 레지스트리에서 제거
    }
}
내 리뷰 · C#
내 답안 · 자동 저장

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