15. 서버-서버 RPC 상관관계 ID 매칭과 늦은 응답 오배달 (C#)

난이도 상 해설 보기 →

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

결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 서버-서버 RPC 상관관계 ID 매칭과 늦은 응답 오배달
// ----------------------------------------------------------------------------
// 시나리오 (프로토콜 / 서버-서버):
//   - 게이트웨이(또는 존 서버)가 다른 서버(인증/인벤토리 서비스)로 RPC 요청을
//     보낸다. 하나의 TCP 연결을 여러 요청이 공유(multiplex)하므로, 각 요청에
//     상관관계 ID(correlationId)를 붙이고 응답에 같은 ID 가 실려 온다.
//   - 송신 측은 ID -> 대기중 요청 매핑을 두고, 응답이 오면 그 ID 로 찾아 깨운다.
//   - 각 요청에는 타임아웃이 있다. 응답이 늦으면 타임아웃으로 실패 처리한다.
//   - 네트워크 특성상 타임아웃 처리 "후"에 그 요청의 응답이 뒤늦게 올 수 있다.
//   - 송신 스레드(요청 발행)와 수신 스레드(응답 디스패치)는 서로 다른 스레드다.
//
// 요구사항:
//   - 응답은 반드시 그 응답을 유발한 "바로 그 요청"에게만 전달돼야 한다.
//   - 동시에 수천 건의 요청이 떠 있어도 ID 충돌/오배달이 없어야 한다.
//   - 타임아웃된 요청에 늦게 도착한 응답이 다른 요청을 오염시키면 안 된다.
//
// 과제:
//   이 코드의 잠재적 문제를 모두 찾고, 왜/어떻게 오배달·충돌·예외가 발생하는지
//   설명하고, 수정안과 더 나은 설계를 제시하라.
//   (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================

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

public class RpcClient
{
    private ushort _nextId = 0;
    // correlationId -> 대기 요청
    private readonly Dictionary<ushort, TaskCompletionSource<byte[]>> _pending
        = new Dictionary<ushort, TaskCompletionSource<byte[]>>();

    // 요청 전송. 응답 페이로드를 담은 Task 반환. timeoutMs 후 타임아웃.
    public Task<byte[]> CallAsync(byte[] body, int timeoutMs)
    {
        // (A) 상관관계 ID 발급
        ushort id = _nextId++;

        var tcs = new TaskCompletionSource<byte[]>();
        _pending[id] = tcs;                         // ID -> 대기 요청 등록

        SendFrame(id, body);                        // 네트워크로 전송(생략)

        // (B) 타임아웃 예약: 시간이 지나면 실패로 완료
        var timer = new Timer(_ =>
        {
            tcs.SetException(new TimeoutException("rpc timeout"));
            // 타임아웃 처리 끝
        }, null, timeoutMs, Timeout.Infinite);

        return tcs.Task;
    }

    // 수신 스레드: 응답 프레임이 오면 호출(correlationId, payload)
    public void OnResponse(ushort id, byte[] payload)
    {
        // (C) ID 로 대기 요청을 찾아 완료
        if (!_pending.TryGetValue(id, out var tcs))
            return;                                 // 모르는 ID 면 무시

        tcs.SetResult(payload);                     // 요청 깨우기
        _pending.Remove(id);
    }

    private void SendFrame(ushort id, byte[] body) { /* 생략 */ }
}
내 리뷰 · C#
내 답안 · 자동 저장

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