44. 인벤토리 자동 정렬과 슬롯 인덱스 명령의 TOCTOU (C#)

난이도 중 해설 보기 →

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

결함 코드 · C#
// 시나리오: MMORPG 인벤토리 서버
//
// 인벤토리는 슬롯 배열이며, 클라이언트는 "N번 슬롯의 아이템을 사용/장착"처럼
// 슬롯 인덱스를 담아 요청을 보낸다. 동시에 서버는 "자동 정렬(빈칸 제거·종류별
// 재배치)" 기능을 제공한다. 정렬은 슬롯의 아이템 배치를 바꾼다.
//
// 요구사항:
//  - 클라이언트가 지정한 슬롯의 "그" 아이템에 대해서만 사용/장착이 일어나야 한다.
//  - 소비 아이템은 사용 시 수량이 정확히 1 감소하고, 0이 되면 슬롯이 비워진다.
//  - 자동 정렬은 인벤토리 내용(어떤 아이템이 몇 개인지)을 절대 바꾸지 않는다.
//  - 사용 요청과 정렬 요청은 서로 다른 클라이언트/타이머 스레드에서 동시에 들어온다.
//
// 아래는 그 처리기다. (A)(B) 지점을 검토하라.

using System;
using System.Collections.Generic;
using System.Linq;

public sealed class Item
{
    public int ItemId;
    public int Count;
    public bool Consumable;
}

public sealed class Inventory
{
    // 슬롯 배열: 빈 슬롯은 null.
    private readonly Item[] _slots;

    public Inventory(int size) { _slots = new Item[size]; }

    // 클라이언트 요청 스레드에서 호출된다.
    public void UseItem(int slotIndex)
    {
        if (slotIndex < 0 || slotIndex >= _slots.Length)
            throw new ArgumentOutOfRangeException(nameof(slotIndex));

        Item item = _slots[slotIndex];                 // (A)
        if (item == null)
            return; // 빈 슬롯

        if (!item.Consumable)
        {
            Equip(item);
            return;
        }

        item.Count -= 1;
        ApplyConsumeEffect(item);
        if (item.Count <= 0)
            _slots[slotIndex] = null;
    }

    // 타이머/UI 스레드에서 "자동 정렬" 버튼으로 호출된다.
    public void AutoSort()
    {
        // 빈칸을 앞으로 모으고 ItemId 순으로 재배치한다.
        var packed = _slots.Where(s => s != null)
                           .OrderBy(s => s.ItemId)
                           .ToList();                  // (B)
        for (int i = 0; i < _slots.Length; i++)
            _slots[i] = i < packed.Count ? packed[i] : null;
    }

    private void Equip(Item item) { /* 장착 처리 */ }
    private void ApplyConsumeEffect(Item item) { /* 소비 효과 적용 */ }
}
내 리뷰 · C#
내 답안 · 자동 저장

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