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++
// ============================================================================
// [코드리뷰 문제] C++ - 서버-서버 RPC 상관관계 ID 매칭과 늦은 응답 오배달
// ----------------------------------------------------------------------------
// 시나리오 (프로토콜 / 서버-서버):
// - 게이트웨이(또는 존 서버)가 다른 서버(인증/인벤토리 서비스)로 RPC 요청을
// 보낸다. 하나의 TCP 연결을 여러 요청이 공유(multiplex)하므로, 각 요청에
// 상관관계 ID(correlationId)를 붙이고 응답에 같은 ID 가 실려 온다.
// - 송신 측은 ID -> 대기중 요청 매핑을 두고, 응답이 오면 그 ID 로 찾아
// 해당 요청을 깨운다(완료시킨다).
// - 각 요청에는 타임아웃이 있다. 응답이 늦으면 타임아웃으로 실패 처리한다.
// - 네트워크 특성상 타임아웃 처리 "후"에 그 요청의 응답이 뒤늦게 도착할 수 있다.
// - 송신 스레드(요청 발행)와 수신 스레드(응답 디스패치)는 서로 다른 스레드다.
//
// 요구사항:
// - 응답은 반드시 그 응답을 유발한 "바로 그 요청"에게만 전달돼야 한다
// (다른 요청에 잘못 매칭되면 안 된다).
// - 동시에 수천 건의 요청이 떠 있어도 ID 충돌/오배달이 없어야 한다.
// - 타임아웃된 요청에 늦게 도착한 응답이 다른 요청을 오염시키면 안 된다.
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 왜/어떻게 오배달·충돌·크래시가
// 발생하는지(동시 인터리빙 포함) 설명하고, 수정안과 더 나은 설계를 제시하라.
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
#include <cstdint>
#include <vector>
#include <unordered_map>
#include <future>
#include <chrono>
#include <functional>
#include <stdexcept>
struct PendingRequest {
std::promise<std::vector<uint8_t>> prom;
};
class RpcClient {
public:
// 요청 전송. 응답 페이로드를 담은 future 반환. timeoutMs 후 타임아웃.
std::future<std::vector<uint8_t>> Call(const std::vector<uint8_t>& body, int timeoutMs) {
// (A) 상관관계 ID 발급
uint16_t id = nextId_++;
auto* pr = new PendingRequest();
auto fut = pr->prom.get_future();
pending_[id] = pr; // ID -> 대기 요청 등록
SendFrame(id, body); // 네트워크로 전송(생략)
// (B) 타임아웃 예약: 시간이 지나면 실패로 완료
ScheduleAfter(timeoutMs, [this, id, pr]() {
pr->prom.set_exception(
std::make_exception_ptr(std::runtime_error("rpc timeout")));
// 타임아웃 처리 끝
});
return fut;
}
// 수신 스레드: 응답 프레임이 오면 호출(correlationId, payload)
void OnResponse(uint16_t id, std::vector<uint8_t> payload) {
// (C) ID 로 대기 요청을 찾아 완료
auto it = pending_.find(id);
if (it == pending_.end())
return; // 모르는 ID 면 무시
PendingRequest* pr = it->second;
pr->prom.set_value(std::move(payload)); // 요청 깨우기
pending_.erase(it);
delete pr;
}
private:
void SendFrame(uint16_t id, const std::vector<uint8_t>& body);
void ScheduleAfter(int ms, std::function<void()> fn);
uint16_t nextId_ = 0;
std::unordered_map<uint16_t, PendingRequest*> pending_;
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.