3. C# 중복 접속 / 동시 로그인 처리 (기존 세션 킥)
난이도 상 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 중복 접속 / 동시 로그인 처리 (기존 세션 킥)
// ----------------------------------------------------------------------------
// 시나리오:
// - 한 계정(accountId)당 동시에 하나의 세션만 허용한다.
// - 같은 계정으로 새 로그인이 들어오면 기존 세션을 강제 종료(킥)하고
// 새 세션을 등록한다("나중 로그인 우선" 정책).
// - 로그인 요청은 여러 IO 스레드에서 동시에 들어온다. 같은 계정으로
// 두 디바이스가 거의 동시에 접속할 수 있다(멀티 디바이스, 재접속 폭주).
//
// 요구사항:
// - 어떤 race 에서도 계정당 최종적으로 정확히 하나의 활성 세션만 남아야 한다.
// - "둘 다 살아남음"(중복) 도, "둘 다 끊김"(서비스 거부) 도 발생하면 안 된다.
// - 킥당한 세션에는 KickNotice 를 보내고 깨끗이 정리해야 한다.
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 왜 문제인지 설명하고,
// 수정안과 더 나은 설계를 제시하라.
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
public class Session
{
public long AccountId;
public string SessionKey = Guid.NewGuid().ToString();
public volatile bool Alive = true;
public Task SendKickNoticeAsync() => Task.CompletedTask; // 킥 통지 전송(생략)
public void Disconnect()
{
Alive = false;
// 소켓 종료 등 ...
}
}
public class LoginManager
{
// accountId -> 현재 활성 세션
private readonly Dictionary<long, Session> _online = new Dictionary<long, Session>();
private readonly object _lock = new object();
// 여러 IO 스레드에서 동시 호출됨
public async Task<Session> LoginAsync(long accountId)
{
// 1) DB 인증 (네트워크 I/O, 수십 ms 소요)
await AuthenticateAsync(accountId);
var newSession = new Session { AccountId = accountId };
// 2) 기존 세션이 있으면 킥
Session old = null;
lock (_lock)
{
_online.TryGetValue(accountId, out old); // (A)
}
if (old != null)
{
await old.SendKickNoticeAsync(); // (B) 락 밖에서 킥 통지 + await
old.Disconnect();
}
// 3) 새 세션 등록
lock (_lock)
{
_online[accountId] = newSession; // (C)
}
return newSession;
}
// 세션이 스스로 끊길 때(클라가 정상 종료) 호출
public void OnSessionClosed(Session s)
{
lock (_lock)
{
_online.Remove(s.AccountId); // (D)
}
}
private Task AuthenticateAsync(long accountId) => Task.Delay(20);
} 결함 코드 · C++
// ============================================================================
// [코드리뷰 문제] C++ - 중복 접속 / 동시 로그인 처리 (기존 세션 킥)
// ----------------------------------------------------------------------------
// 시나리오:
// - 한 계정(accountId)당 동시에 하나의 세션만 허용한다.
// - 같은 계정으로 새 로그인이 들어오면 기존 세션을 강제 종료(킥)하고
// 새 세션을 등록한다("나중 로그인 우선" 정책).
// - 로그인 요청은 여러 IO 스레드에서 동시에 들어온다. 같은 계정으로
// 두 디바이스가 거의 동시에 접속할 수 있다(멀티 디바이스, 재접속 폭주).
//
// 요구사항:
// - 어떤 race 에서도 계정당 최종적으로 정확히 하나의 활성 세션만 남아야 한다.
// - "둘 다 살아남음"(중복) 도, "둘 다 끊김"(서비스 거부) 도 발생하면 안 된다.
// - 킥당한 세션에는 KickNotice 를 보내고 깨끗이 정리해야 한다.
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 왜 문제인지 설명하고,
// 수정안과 더 나은 설계를 제시하라.
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
#include <unordered_map>
#include <mutex>
#include <future>
#include <cstdint>
#include <string>
class Session {
public:
int64_t accountId;
std::string sessionKey; // 고유 키(생략: 난수)
bool alive = true;
void SendKickNotice() { /* 킥 통지 전송(생략) */ }
void Disconnect() {
alive = false;
// 소켓 종료 등 ...
}
};
class LoginManager {
// accountId -> 현재 활성 세션 (raw 포인터로 보관)
std::unordered_map<int64_t, Session*> online_;
std::mutex mtx_;
public:
// 여러 IO 스레드에서 동시 호출됨
Session* Login(int64_t accountId) {
// 1) DB 인증 (네트워크 I/O, 수십 ms 소요) — future 로 블로킹 대기
AuthenticateAsync(accountId).get(); // (A) future.get() 블로킹
Session* newSession = new Session();
newSession->accountId = accountId;
// 2) 기존 세션이 있으면 킥
Session* old = nullptr;
{
std::lock_guard<std::mutex> lk(mtx_);
auto it = online_.find(accountId);
if (it != online_.end()) old = it->second; // (B)
}
if (old != nullptr) {
old->SendKickNotice(); // (C) 락 밖에서 킥 통지
old->Disconnect();
delete old; // (D) 옛 세션 메모리 해제
}
// 3) 새 세션 등록
{
std::lock_guard<std::mutex> lk(mtx_);
online_[accountId] = newSession; // (E)
}
return newSession;
}
// 세션이 스스로 끊길 때(클라가 정상 종료) 호출
void OnSessionClosed(Session* s) {
std::lock_guard<std::mutex> lk(mtx_);
online_.erase(s->accountId); // (F)
delete s;
}
private:
std::future<void> AuthenticateAsync(int64_t accountId) {
return std::async(std::launch::async, [] { /* DB 인증 ~20ms */ });
}
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.