49. 완료 카운트다운(Decrement) 종료 판정과 컨텍스트 재사용 경합 (C#)

난이도 최상 해설 보기 →

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

결함 코드 · C#
// 시나리오: 스캐터-개더 집계 컨텍스트의 완료 카운트다운 (C#)
// - 글로벌 검색을 위해 N개 샤드에 하위 요청을 팬아웃한다.
// - 각 하위 요청의 응답 콜백은 결과를 컨텍스트에 담고, 남은 개수(pending)를
//   원자적으로 1 감소시킨다. 0이 되게 한 마지막 콜백이 전체를 완성하고
//   컨텍스트 객체를 풀에 반납한다(할당을 줄이려 GatherContext 를 재사용).
// - 하위 요청/응답은 신뢰성 UDP 기반이라 같은 샤드의 응답이 재전송으로
//   두 번 도착할 수 있다(중복 응답).
//
// 요구사항: 각 개더는 정확히 한 번 완성되고, 컨텍스트는 in-flight 중 반납/재사용
//           되지 않아야 한다.
//
// 과제: 부하 중 간헐적으로 검색 결과가 다른 요청의 결과와 섞이거나, 같은 개더가
//       두 번 완성된다. (A),(B) 를 근거로 원인과 수정안을 설명하라.

using System;
using System.Threading;
using System.Collections.Concurrent;

public sealed class GatherContext
{
    public int Pending;
    public string?[] Results = new string?[16];
    public Action<string?[]>? OnComplete;
}

public sealed class GatherCoordinator
{
    private readonly ConcurrentBag<GatherContext> _pool = new();

    private GatherContext Rent() => _pool.TryTake(out var c) ? c : new GatherContext();
    private void Return(GatherContext c) { c.OnComplete = null; _pool.Add(c); }

    public GatherContext Begin(int shardCount, Action<string?[]> onComplete)
    {
        var ctx = Rent();
        ctx.Pending = shardCount;
        ctx.OnComplete = onComplete;
        return ctx;
    }

    // 샤드 응답 도착 시 호출 (여러 스레드에서, 중복 도착 가능)
    public void OnShardResponse(GatherContext ctx, int shardIndex, string? value)
    {
        ctx.Results[shardIndex] = value;

        if (Interlocked.Decrement(ref ctx.Pending) == 0)   // (A)
        {
            ctx.OnComplete?.Invoke(ctx.Results);
            Return(ctx);                                    // (B)
        }
    }
}
내 리뷰 · C#
내 답안 · 자동 저장

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