27. 전역 락을 쥔 채 하는 블로킹 I/O와 재진입 콜백 (락 호위 / 순회 중 변경)

난이도 상 해설 보기 →

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

결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 전역 락을 쥔 채 수행하는 블로킹 전송과 실패 시 연결 정리
// ----------------------------------------------------------------------------
// 시나리오 (동시성/메모리 - 서버 내부):
//   - WorldManager 는 전역 락(_lock)으로 세션 목록과 월드 상태를 보호한다.
//   - Broadcast 는 모든 세션에 같은 패킷을 보낸다(존 알림, 채팅, 틱 스냅샷 등).
//   - SendBlocking 은 소켓 송신이며, 수신이 느리거나 송신 버퍼가 가득 찬 클라이언트에서는
//     한참 블로킹될 수 있다.
//   - 전송이 실패하면 그 세션을 Disconnect 로 정리(목록에서 제거 + 소켓 닫기)한다.
//   - AddSession/Disconnect 는 다른 스레드에서도 호출된다(접속/해제).
//
// 상황:
//   - 동접이 많은 상태에서 Broadcast 가 자주 일어난다.
//   - 일부 클라이언트가 느리고(백프레셔), 그중 하나는 전송 중 끊겨 예외를 던진다.
//
// 과제:
//   (A)에서 전역 락을 잡은 채 (B)의 블로킹 전송을 도는 동안, 다른 스레드의 AddSession/
//   Disconnect/다른 Broadcast 가 어떻게 되는지 분석하라. 또 (B)가 실패해 (C)의 Disconnect
//   가 (D)에서 같은 락을 다시 잡고 목록을 바꿀 때, Broadcast 의 순회와 락 상태가 어떻게
//   되는지 설명하라. 수정안과 더 나은 설계를 제시하라.
//   (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================

using System.Collections.Generic;

public sealed class Session
{
    public long Id;
    public void SendBlocking(byte[] packet) { /* 소켓 송신: 느린 클라에서 블로킹/예외 가능 */ }
    public void Close() { /* 소켓 닫기 */ }
}

public sealed class WorldManager
{
    private readonly object _lock = new();
    private readonly List<Session> _sessions = new();

    public void AddSession(Session s)
    {
        lock (_lock) { _sessions.Add(s); }
    }

    public void Broadcast(byte[] packet)
    {
        lock (_lock)                                  // (A) 전역 락 획득
        {
            foreach (var s in _sessions)
            {
                try
                {
                    s.SendBlocking(packet);           // (B) 락 보유 중 블로킹 전송
                }
                catch
                {
                    Disconnect(s);                    // (C) 실패한 세션 정리
                }
            }
        }
    }

    public void Disconnect(Session s)
    {
        lock (_lock)                                  // (D) 같은 락 재획득 후 목록 변경
        {
            _sessions.Remove(s);
            s.Close();
        }
    }
}
내 리뷰 · C#
내 답안 · 자동 저장

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