42. 소비 아이템 공유 쿨다운 그룹의 동시 사용 우회 (C#)

난이도 중 해설 보기 →

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

결함 코드 · C#
// 시나리오: MMORPG 전투 서버 — 소비 아이템(포션) 사용 처리 (C#)
//
// 회복/버프 포션은 "쿨다운 그룹"으로 묶인다. 예를 들어 모든 HP 포션은
// 하나의 그룹(예: 그룹 7)을 공유하고, 그 그룹은 10초 쿨다운을 가진다.
// 서로 다른 종류의 HP 포션이라도 같은 그룹이면 쿨다운을 공유해야 한다.
//
// 요구사항:
//  - 같은 쿨다운 그룹은 쿨다운이 지나기 전에는 다시 사용할 수 없다.
//  - 아이템 개수는 사용할 때마다 정확히 1개씩만 줄어야 한다(음수 불가).
//  - 한 플레이어가 여러 클라이언트/여러 패킷으로 동시에 사용 요청을 보내도
//    위 규칙이 지켜져야 한다.
//
// 아래는 사용 요청 처리기다. 수신 스레드 풀에서 같은 플레이어의 요청이
// 동시에 들어올 수 있다. 표시된 지점 (A)(B) 를 중심으로 검토하라.

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

public sealed class ConsumableService
{
    private sealed class Player
    {
        public Dictionary<int, int> ItemCount = new();       // itemId -> 개수
        public Dictionary<int, DateTime> GroupLastUse = new(); // groupId -> 마지막 사용 시각
    }

    private readonly Dictionary<long, Player> _players = new();
    private readonly Dictionary<int, int> _itemGroup;      // itemId -> groupId
    private readonly Dictionary<int, double> _groupCooldown; // groupId -> 초

    public ConsumableService(Dictionary<int, int> itemGroup, Dictionary<int, double> groupCooldown)
    {
        _itemGroup = itemGroup;
        _groupCooldown = groupCooldown;
    }

    public async Task<bool> UseItemAsync(long playerId, int itemId)
    {
        Player p = _players[playerId];
        int group = _itemGroup[itemId];
        double cd = _groupCooldown[group];

        // (A) 쿨다운 검사
        if (p.GroupLastUse.TryGetValue(group, out DateTime last))
        {
            if ((DateTime.UtcNow - last).TotalSeconds < cd)
                return false; // 아직 쿨다운 중
        }

        if (!p.ItemCount.TryGetValue(itemId, out int cnt) || cnt <= 0)
            return false; // 재고 없음

        // 효과 적용(회복/버프)은 DB·버프 매니저 호출을 포함해 약간의 지연이 있다.
        await ApplyEffectAsync(playerId, itemId);

        // (B) 재고 차감 + 쿨다운 갱신
        p.ItemCount[itemId] = cnt - 1;
        p.GroupLastUse[group] = DateTime.UtcNow;
        return true;
    }

    private Task ApplyEffectAsync(long playerId, int itemId) => Task.Delay(5);
}
내 리뷰 · C#
내 답안 · 자동 저장

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