54. 재적용 버프의 조기 만료 (stale 일회성 타이머 + 무조건 제거)
난이도 중 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// 시나리오: MMORPG 전투 버프(재적용 가능) 시스템 — C# 서버
// - 스킬 명중 시 대상에게 "공격력 증가" 버프를 부여한다. 기본 지속시간 10초.
// - 지속시간 안에 같은 버프를 다시 명중시키면 "재적용(refresh)"되어 만료 시각이
// 현재로부터 다시 10초 뒤로 갱신된다(지속시간 초기화).
// - 버프 부여/재적용은 전투 워커 스레드에서, 실제 제거는 스케줄러(타이머) 스레드가
// 예약된 시각에 콜백으로 수행한다.
//
// 요구사항:
// 1) 재적용하면 버프는 "갱신된" 만료 시각까지 유지되어야 한다.
// 2) 실제로 만료 시각이 지난 버프만 제거되어야 한다.
//
// 과제: 재적용을 했는데도 버프가 예정보다 일찍 사라지는 경우가 있다.
// 또한 재적용/제거가 겹칠 때 상태가 어긋날 수 있다.
// (A),(B) 지점을 근거로 원인과 수정안을 설명하라.
using System;
using System.Collections.Generic;
public interface IScheduler
{
// delayMs 후에 action 을 타이머 스레드에서 한 번 호출한다.
void ScheduleAfter(long delayMs, Action action);
}
public sealed class BuffSystem
{
public sealed class Buff
{
public long ExpireAtMs;
public int Power = 10;
}
private const long DurationMs = 10_000;
private readonly Dictionary<int, Buff> _buffs = new Dictionary<int, Buff>();
private readonly IScheduler _scheduler;
public BuffSystem(IScheduler scheduler) { _scheduler = scheduler; }
// 전투 워커 스레드에서 호출: 버프 부여 또는 재적용
public void ApplyOrRefresh(int playerId, long nowMs)
{
if (!_buffs.TryGetValue(playerId, out var buff))
{
buff = new Buff();
_buffs[playerId] = buff;
}
buff.ExpireAtMs = nowMs + DurationMs; // (A)
// 이 버프를 지속시간 뒤에 제거하도록 일회성 콜백 예약
_scheduler.ScheduleAfter(DurationMs, () => RemoveExpired(playerId));
}
// 타이머 스레드에서 호출: 예약된 시각에 실행됨
public void RemoveExpired(int playerId)
{
_buffs.Remove(playerId); // (B)
}
// 전투 계산에서 조회
public int GetPower(int playerId)
=> _buffs.TryGetValue(playerId, out var b) ? b.Power : 0;
} 결함 코드 · C++
// 시나리오: MMORPG 전투 버프(재적용 가능) 시스템 — C++ 서버
// - 스킬 명중 시 대상에게 "공격력 증가" 버프를 부여(기본 지속시간 10초).
// - 지속시간 내 재명중 시 "재적용(refresh)"되어 만료 시각이 갱신된다.
// - 부여/재적용은 전투 워커에서, 실제 제거는 스케줄러(타이머) 스레드가 예약 콜백으로 수행.
//
// 요구사항:
// 1) 재적용하면 갱신된 만료 시각까지 버프가 유지되어야 한다.
// 2) 실제로 만료된 버프만 제거되어야 한다.
//
// 과제: 재적용을 했는데도 버프가 일찍 사라지고, 제거 이후 전투 계산이
// 버프를 참조하면 문제가 생길 수 있다. (A),(B) 지점을 근거로
// 원인과 수정안을 설명하라. (컴파일 가능해야 함)
#include <cstdio>
#include <cstdint>
#include <functional>
#include <unordered_map>
struct IScheduler {
virtual void ScheduleAfter(int64_t delayMs, std::function<void()> action) = 0;
virtual ~IScheduler() = default;
};
struct Buff {
int64_t expire_at_ms = 0;
int power = 10;
};
class BuffSystem {
public:
explicit BuffSystem(IScheduler& s) : sched_(s) {}
// 전투 워커: 부여/재적용
void ApplyOrRefresh(int player_id, int64_t now_ms) {
Buff* buff = nullptr;
auto it = buffs_.find(player_id);
if (it == buffs_.end()) {
buff = new Buff();
buffs_[player_id] = buff;
} else {
buff = it->second;
}
buff->expire_at_ms = now_ms + kDurationMs; // (A)
sched_.ScheduleAfter(kDurationMs, [this, player_id] { RemoveExpired(player_id); });
}
// 타이머 스레드: 예약된 시각에 실행
void RemoveExpired(int player_id) {
auto it = buffs_.find(player_id);
if (it != buffs_.end()) {
delete it->second; // (B)
buffs_.erase(it);
}
}
int GetPower(int player_id) const {
auto it = buffs_.find(player_id);
return it == buffs_.end() ? 0 : it->second->power;
}
private:
static constexpr int64_t kDurationMs = 10000;
IScheduler& sched_;
std::unordered_map<int, Buff*> buffs_;
};
struct ImmediateScheduler : IScheduler {
void ScheduleAfter(int64_t, std::function<void()>) override { /* 데모: 즉시 실행 안 함 */ }
};
int main() {
ImmediateScheduler s;
BuffSystem bs(s);
bs.ApplyOrRefresh(1, 0);
std::printf("power=%d\n", bs.GetPower(1));
return 0;
} 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.