36. 강화 결과 완료 콜백과 취소의 경합
난이도 최상 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// 시나리오 (서버 내부): 아이템 강화 요청을 비동기로 처리하는 컴포넌트.
// 강화 요청이 들어오면 백그라운드에서 강화 결과(성공/실패, 결과 아이템)를
// 계산하는 작업(Task)을 시작하고, 그 작업이 끝나면 콜백으로 최종 결과를
// 플레이어 컨텍스트(캐릭터 상태)에 반영한다.
//
// 강화 계산은 시간이 걸릴 수 있어(예: 원거리 조회 포함), 플레이어가 그 사이
// 접속을 끊거나 다른 사유로 요청을 취소(Cancel)할 수 있다. 취소되면 해당
// 요청에 연결된 컨텍스트(결과를 받아 반영할 대상)는 더 이상 유효하지 않은
// 것으로 간주하고 정리(해제)된다.
//
// 요구사항:
// - 취소된 요청의 결과는 어떤 경로로도 최종 상태에 반영되면 안 된다.
// - 취소 처리와 작업의 자연 완료가 동시에 일어나더라도(경합하더라도),
// 이미 정리된 컨텍스트에 완료 콜백이 접근해서는 안 된다.
// - 취소되지 않고 정상 완료된 요청은 결과가 반드시, 정확히 한 번 반영되어야
// 한다.
//
// 과제: (A)(B) 지점을 검토해 위 요구사항이 깨지는 경로를 찾고 수정하라.
using System;
using System.Threading;
using System.Threading.Tasks;
public sealed class EnhancementContext
{
public int CharacterId;
public int ResultItemId;
public bool Applied;
public void ApplyResult(int resultItemId)
{
// 실제로는 캐릭터 인벤토리/스탯을 갱신하는 로직이 들어간다.
ResultItemId = resultItemId;
Applied = true;
}
public void Dispose()
{
// 컨텍스트가 들고 있던 리소스(예: DB 세션 핸들, 캐시 엔트리)를 반환한다.
CharacterId = -1;
}
}
public sealed class EnhancementRequest
{
public EnhancementContext Context; // (A)
public bool IsCancelled;
public Task ComputeTask;
}
public sealed class EnhancementService
{
// 강화 요청을 시작한다. 계산이 끝나면 콜백에서 컨텍스트에 결과를 반영한다.
public EnhancementRequest Start(EnhancementContext context)
{
var request = new EnhancementRequest { Context = context };
request.ComputeTask = Task.Run(() =>
{
// 강화 결과 계산(원거리 조회 등을 흉내낸 지연).
int resultItemId = ComputeEnhancementResult(context.CharacterId);
// 계산이 끝나면 컨텍스트에 결과를 반영한다.
// 이 시점에 Cancel 이 동시에 호출되어 컨텍스트가 이미 정리되었을 수
// 있다는 사실을 이 콜백은 알지 못한다.
request.Context.ApplyResult(resultItemId); // (B)
});
return request;
}
// 요청을 취소한다. 컨텍스트를 정리해 더 이상 쓰이지 않게 한다.
public void Cancel(EnhancementRequest request)
{
request.IsCancelled = true;
request.Context.Dispose();
request.Context = null;
}
private static int ComputeEnhancementResult(int characterId)
{
Thread.Sleep(50); // 계산/조회 지연 흉내.
return characterId * 7 % 100;
}
} 결함 코드 · C++
// ============================================================================
// 시나리오 (서버 내부)
// ----------------------------------------------------------------------------
// 아이템 강화 요청을 비동기로 처리하는 컴포넌트. 강화 요청이 들어오면
// 백그라운드 스레드에서 강화 결과(성공/실패, 결과 아이템)를 계산하고, 계산이
// 끝나면 완료 콜백에서 그 결과를 플레이어 컨텍스트(캐릭터 상태)에 반영한다.
//
// 강화 계산은 시간이 걸릴 수 있어(예: 원거리 조회 포함), 플레이어가 그 사이
// 접속을 끊거나 다른 사유로 요청을 취소(Cancel)할 수 있다. 취소되면 해당
// 요청에 연결된 컨텍스트(결과를 받아 반영할 대상)는 더 이상 유효하지 않은
// 것으로 간주되어 파괴(delete)된다.
//
// 요구사항:
// - 취소된 요청의 결과는 어떤 경로로도 최종 상태에 반영되면 안 된다.
// - 취소 처리와 작업의 자연 완료가 동시에 일어나더라도(경합하더라도),
// 이미 파괴된 컨텍스트에 완료 콜백이 접근해서는 안 된다.
// - 취소되지 않고 정상 완료된 요청은 결과가 반드시, 정확히 한 번 반영되어야
// 한다.
//
// 과제: (A)(B) 지점을 검토해 위 요구사항이 깨지는 경로를 찾고 수정하라.
// ============================================================================
#include <chrono>
#include <thread>
class EnhancementContext
{
public:
int characterId = 0;
int resultItemId = 0;
bool applied = false;
void ApplyResult(int item)
{
// 실제로는 캐릭터 인벤토리/스탯을 갱신하는 로직이 들어간다.
resultItemId = item;
applied = true;
}
};
struct EnhancementRequest
{
EnhancementContext* context; // (A) 요청이 컨텍스트를 raw 포인터로 참조한다.
bool cancelled = false;
std::thread worker;
};
class EnhancementService
{
public:
// 강화 요청을 시작한다. 계산이 끝나면 콜백에서 컨텍스트에 결과를 반영한다.
EnhancementRequest* Start(EnhancementContext* context)
{
auto* request = new EnhancementRequest();
request->context = context;
request->worker = std::thread([request]()
{
// 강화 결과 계산(원거리 조회 등을 흉내낸 지연).
int result = ComputeEnhancementResult(request->context->characterId);
// 계산이 끝나면 컨텍스트에 결과를 반영한다.
// 이 시점에 Cancel 이 다른 스레드에서 동시에 호출되어 컨텍스트가
// 이미 파괴되었을 수 있다는 사실을 이 콜백은 알지 못한다.
request->context->ApplyResult(result); // (B)
});
return request;
}
// 요청을 취소한다. 컨텍스트를 파괴해 더 이상 쓰이지 않게 한다.
void Cancel(EnhancementRequest* request)
{
request->cancelled = true;
delete request->context;
request->context = nullptr;
}
private:
static int ComputeEnhancementResult(int characterId)
{
std::this_thread::sleep_for(std::chrono::milliseconds(50)); // 계산/조회 지연 흉내.
return (characterId * 7) % 100;
}
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.