5. 예외 경로의 커넥션/트랜잭션 누수 (풀 고갈 / 잠금 장기 보유)
난이도 하 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 캐릭터 저장 시 커넥션/트랜잭션 풀 반납
// ----------------------------------------------------------------------------
// 시나리오 (영속화/DB):
// - 캐릭터 저장은 커넥션 풀에서 커넥션을 빌려, 트랜잭션 안에서 스탯·인벤토리·퀘스트를
// 여러 UPDATE 로 반영한 뒤 커밋한다.
// - 커넥션 풀은 크기가 한정돼 있고, 동시 저장 요청이 많다(로그아웃/자동저장/존 이동).
// - 각 UPDATE 는 제약 위반, 데드락 피해자 선정, 타임아웃 등으로 예외를 던질 수 있다.
//
// 상황:
// - 일부 저장에서 중간 UPDATE 가 예외를 던진다(예: 일시적 DB 데드락/타임아웃).
// - 이런 저장이 간헐적으로 반복된다.
//
// 과제:
// 중간에 예외가 났을 때 이 메서드가 커넥션과 트랜잭션을 어떻게 처리하는지 추적하라.
// (A)에서 빌린 커넥션과 (B)에서 연 트랜잭션이 예외 경로에서 어떻게 되는지, 그리고
// 그것이 반복될 때 커넥션 풀과 DB 측 잠금에 무슨 일이 생기는지 설명하라. 수정안과
// 더 나은 설계를 제시하라.
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
public interface IDbConnection
{
IDbTransaction BeginTransaction();
void Execute(IDbTransaction tx, string sql, object args);
void Close();
}
public interface IDbTransaction { void Commit(); void Rollback(); }
public interface IConnectionPool
{
IDbConnection Rent(); // 풀에서 대여(없으면 대기/타임아웃)
void Return(IDbConnection c); // 풀로 반납
}
public sealed class CharacterRepository
{
private readonly IConnectionPool _pool;
public CharacterRepository(IConnectionPool pool) { _pool = pool; }
public void SaveCharacter(Character c)
{
IDbConnection conn = _pool.Rent(); // (A) 커넥션 대여
IDbTransaction tx = conn.BeginTransaction(); // (B) 트랜잭션 시작
conn.Execute(tx, "UPDATE stats SET ... WHERE id=@id", c.Stats); // (C) 예외 가능
conn.Execute(tx, "UPDATE inventory SET ... WHERE id=@id", c.Inventory); // (C) 예외 가능
conn.Execute(tx, "UPDATE quests SET ... WHERE id=@id", c.Quests); // (C) 예외 가능
tx.Commit(); // (D) 커밋
_pool.Return(conn); // (E) 반납
}
}
public sealed class Character
{
public long Id;
public object Stats = new();
public object Inventory = new();
public object Quests = new();
} 결함 코드 · C++
// ============================================================================
// [코드리뷰 문제] C++ - 캐릭터 저장 시 커넥션/트랜잭션 풀 반납
// ----------------------------------------------------------------------------
// 시나리오 (영속화/DB):
// - 캐릭터 저장은 커넥션 풀에서 커넥션을 빌려, 트랜잭션 안에서 스탯·인벤토리·퀘스트를
// 여러 UPDATE 로 반영한 뒤 커밋한다.
// - 커넥션 풀은 크기가 한정돼 있고, 동시 저장 요청이 많다(로그아웃/자동저장/존 이동).
// - 각 execute 는 제약 위반, 데드락 피해자 선정, 타임아웃 등으로 예외를 던질 수 있다.
//
// 상황:
// - 일부 저장에서 중간 execute 가 예외를 던진다(예: 일시적 DB 데드락/타임아웃).
// - 이런 저장이 간헐적으로 반복된다.
//
// 과제:
// 중간에 예외가 났을 때 이 함수가 커넥션과 트랜잭션을 어떻게 처리하는지 추적하라.
// (A)에서 빌린 커넥션과 (B)에서 연 트랜잭션이 예외 경로에서 어떻게 되는지, 그리고
// 그것이 반복될 때 커넥션 풀과 DB 측 잠금에 무슨 일이 생기는지 설명하라. 수정안과
// 더 나은 설계를 제시하라.
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
#include <string>
struct Character { long id = 0; };
struct DbTransaction {
void commit();
void rollback();
};
struct DbConnection {
DbTransaction* beginTransaction();
void execute(DbTransaction* tx, const std::string& sql, const Character& c); // throw 가능
};
struct ConnectionPool {
DbConnection* rent(); // 풀에서 대여(없으면 대기/타임아웃)
void giveBack(DbConnection*); // 풀로 반납
};
class CharacterRepository {
public:
explicit CharacterRepository(ConnectionPool& pool) : pool_(pool) {}
void saveCharacter(const Character& c) {
DbConnection* conn = pool_.rent(); // (A) 커넥션 대여
DbTransaction* tx = conn->beginTransaction(); // (B) 트랜잭션 시작
conn->execute(tx, "UPDATE stats SET ...", c); // (C) 예외 가능
conn->execute(tx, "UPDATE inventory SET ...", c); // (C) 예외 가능
conn->execute(tx, "UPDATE quests SET ...", c); // (C) 예외 가능
tx->commit(); // (D) 커밋
pool_.giveBack(conn); // (E) 반납
}
private:
ConnectionPool& pool_;
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.