34. ArrayPool<T> · Memory<T> · IMemoryOwner<T> 버퍼 풀링의 내부 동작

난이도 중 해설 보기 →

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

결함 코드 · C#
// 주제: ArrayPool<T> · Memory<T> · IMemoryOwner<T> 버퍼 풀링의 내부 동작
//
// 게임 서버는 패킷 수신/직렬화에서 초당 수만 개의 임시 버퍼를 만든다. GC 압박을
// 줄이려 ArrayPool<T> 로 배열을 대여/반납한다. 그런데 풀의 계약을 오해하면
// 미묘한 버그가 생긴다.
//
// 과제:
//  1) 아래 Rent 호출들에서 buffer.Length 는 각각 요청한 크기와 같을까?
//     ArrayPool 이 배열을 어떻게 버킷팅해서 관리하는지 설명하라.
//  2) ProcessPacket 은 rent한 배열을 어떻게 쓰고 반납하는가? 이 코드에 숨은
//     세 가지 함정(정확한 길이 사용, 반납 후 사용, 민감 데이터 클리어)을 짚어라.
//  3) Span<T> 대신 Memory<T>/IMemoryOwner<T> 가 필요한 경우는 언제인가?
//     (특히 async 경계를 넘길 때) 이유를 설명하라.
//
// (동작을 예측한 뒤 설명은 answer.md 를 참조하라. 실행 환경이 있으면 확인하라.)

using System;
using System.Buffers;
using System.Threading.Tasks;

public static class PoolDemo
{
    public static void ShowRentSizes()
    {
        var pool = ArrayPool<byte>.Shared;
        foreach (int req in new[] { 100, 1000, 4096, 5000 })
        {
            byte[] buf = pool.Rent(req);
            Console.WriteLine($"requested {req} -> buffer.Length {buf.Length}");
            pool.Return(buf);
        }
    }

    // 수신 소켓에서 payloadLen 바이트를 읽어 처리한다.
    public static void ProcessPacket(int payloadLen)
    {
        byte[] buf = ArrayPool<byte>.Shared.Rent(payloadLen);
        FillFromSocket(buf, payloadLen);

        // 체크섬 계산 (전체 배열을 순회한다)
        long sum = 0;
        for (int i = 0; i < buf.Length; i++)   // (A)
            sum += buf[i];

        ArrayPool<byte>.Shared.Return(buf);    // (B)
        Console.WriteLine($"checksum={sum}, first={buf[0]}");  // (C)
    }

    private static void FillFromSocket(byte[] dst, int n) { for (int i = 0; i < n; i++) dst[i] = 1; }
}
내 리뷰 · C#
내 답안 · 자동 저장

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