1. C# Heartbeat / 유휴 타임아웃 세션 정리

난이도 하 해설 보기 →

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

결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - Heartbeat / 유휴 타임아웃 세션 정리
// ----------------------------------------------------------------------------
// 시나리오:
//   - 실시간 게임 서버. 모든 세션은 주기적으로 Ping 패킷을 보낸다.
//   - 일정 시간(IdleTimeout) 동안 Ping이 없으면 "죽은 연결"로 보고 끊는다.
//   - 별도 스레드(타이머)가 1초마다 전체 세션을 훑어 만료된 세션을 끊는다.
//   - 네트워크 수신 스레드는 Ping을 받으면 LastPing 시각을 갱신한다.
//
// 요구사항:
//   - 살아있는 정상 유저를 절대 오인 끊김(false disconnect) 시키면 안 된다.
//   - 진짜 죽은 연결은 IdleTimeout 안에 정리되어야 한다.
//   - 수신 스레드와 타이머 스레드가 동시에 같은 세션을 만진다.
//
// 과제:
//   이 코드의 잠재적 문제를 모두 찾고, 왜 문제인지 설명하고,
//   수정안과 더 나은 설계를 제시하라.
//   (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================

using System;
using System.Collections.Generic;
using System.Threading;

public class Session
{
    public int Id;
    public bool Connected = true;

    // (A) 마지막 Ping을 받은 시각. 수신 스레드가 쓰고 타이머 스레드가 읽는다.
    public DateTime LastPing = DateTime.Now;

    public void Close(string reason)
    {
        Connected = false;
        // 소켓 닫기 등 ... (생략)
        Console.WriteLine($"[Session {Id}] closed: {reason}");
    }
}

public class HeartbeatManager
{
    private readonly Dictionary<int, Session> _sessions = new Dictionary<int, Session>();
    private readonly TimeSpan _idleTimeout = TimeSpan.FromSeconds(30);
    private Timer _timer;

    public void Start()
    {
        // 1초마다 SweepDeadSessions 호출
        _timer = new Timer(_ => SweepDeadSessions(), null, 1000, 1000);
    }

    public void AddSession(Session s)
    {
        _sessions[s.Id] = s;   // (B)
    }

    // 네트워크 수신 스레드에서 Ping 패킷 수신 시 호출
    public void OnPingReceived(int sessionId)
    {
        if (_sessions.TryGetValue(sessionId, out var s))
        {
            s.LastPing = DateTime.Now;   // (A) 시각 갱신
        }
    }

    // (C) 타이머 스레드: 만료 세션을 찾아 끊는다
    private void SweepDeadSessions()
    {
        var now = DateTime.Now;
        foreach (var kv in _sessions)
        {
            var s = kv.Value;
            if (now - s.LastPing > _idleTimeout)
            {
                s.Close("idle timeout");
                _sessions.Remove(kv.Key);   // (D)
            }
        }
    }
}
내 리뷰 · C#
내 답안 · 자동 저장

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