54. 재적용 버프의 조기 만료 (stale 일회성 타이머 + 무조건 제거)

난이도 중 해설 보기 →

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

결함 코드 · C#
// 시나리오: MMORPG 전투 버프(재적용 가능) 시스템 — C# 서버
// - 스킬 명중 시 대상에게 "공격력 증가" 버프를 부여한다. 기본 지속시간 10초.
// - 지속시간 안에 같은 버프를 다시 명중시키면 "재적용(refresh)"되어 만료 시각이
//   현재로부터 다시 10초 뒤로 갱신된다(지속시간 초기화).
// - 버프 부여/재적용은 전투 워커 스레드에서, 실제 제거는 스케줄러(타이머) 스레드가
//   예약된 시각에 콜백으로 수행한다.
//
// 요구사항:
//   1) 재적용하면 버프는 "갱신된" 만료 시각까지 유지되어야 한다.
//   2) 실제로 만료 시각이 지난 버프만 제거되어야 한다.
//
// 과제: 재적용을 했는데도 버프가 예정보다 일찍 사라지는 경우가 있다.
//       또한 재적용/제거가 겹칠 때 상태가 어긋날 수 있다.
//       (A),(B) 지점을 근거로 원인과 수정안을 설명하라.

using System;
using System.Collections.Generic;

public interface IScheduler
{
    // delayMs 후에 action 을 타이머 스레드에서 한 번 호출한다.
    void ScheduleAfter(long delayMs, Action action);
}

public sealed class BuffSystem
{
    public sealed class Buff
    {
        public long ExpireAtMs;
        public int Power = 10;
    }

    private const long DurationMs = 10_000;
    private readonly Dictionary<int, Buff> _buffs = new Dictionary<int, Buff>();
    private readonly IScheduler _scheduler;

    public BuffSystem(IScheduler scheduler) { _scheduler = scheduler; }

    // 전투 워커 스레드에서 호출: 버프 부여 또는 재적용
    public void ApplyOrRefresh(int playerId, long nowMs)
    {
        if (!_buffs.TryGetValue(playerId, out var buff))
        {
            buff = new Buff();
            _buffs[playerId] = buff;
        }
        buff.ExpireAtMs = nowMs + DurationMs;              // (A)

        // 이 버프를 지속시간 뒤에 제거하도록 일회성 콜백 예약
        _scheduler.ScheduleAfter(DurationMs, () => RemoveExpired(playerId));
    }

    // 타이머 스레드에서 호출: 예약된 시각에 실행됨
    public void RemoveExpired(int playerId)
    {
        _buffs.Remove(playerId);                           // (B)
    }

    // 전투 계산에서 조회
    public int GetPower(int playerId)
        => _buffs.TryGetValue(playerId, out var b) ? b.Power : 0;
}
내 리뷰 · C#
내 답안 · 자동 저장

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