29. 트랜잭션을 종료하지 않고 커넥션을 풀에 반납(상태 상속 오염) · C#
난이도 상 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// 시나리오: 커넥션 풀에서 빌린 커넥션을 트랜잭션이 열린 채 반납 (C#)
//
// 서버는 DB 커넥션 풀에서 커넥션을 빌려(Rent) 트랜잭션을 열고 작업한 뒤,
// 커밋/롤백하고 커넥션을 풀에 반납(Return)한다. 반납된 커넥션은 곧바로
// 다른 요청이 다시 빌려 쓴다.
//
// 아래 GrantReward 는 보상 지급을 트랜잭션으로 처리한다. 여러 요청이
// 풀을 공유하며 동시에/연달아 이 코드를 실행한다.
//
// 요구사항:
// - 반납되는 커넥션은 진행 중인 트랜잭션이 없는 깨끗한 상태여야 한다.
// - 다음 사용자가 빌린 커넥션은 이전 사용자의 트랜잭션/세션 상태를 이어받지 않아야 한다.
//
// 과제:
// 1) (A)(B) 를 중심으로, 반납된 커넥션이 다음 사용자에게 어떤 영향을 주는지 설명하라.
// 2) 두 가지 이상의 오작동 시나리오(가드절 조기 반환, 예외)를 제시하라.
// 3) 수정안을 제시하라.
using System;
public interface IPooledConn
{
void Begin();
void Commit();
void Rollback();
bool InTransaction { get; }
void Exec(string sql);
}
public interface IConnPool
{
IPooledConn Rent();
void Return(IPooledConn conn);
}
public class RewardService
{
private readonly IConnPool _pool;
public RewardService(IConnPool pool) { _pool = pool; }
public void GrantReward(long userId, int amount)
{
IPooledConn conn = _pool.Rent();
try
{
conn.Begin(); // (A)
if (amount <= 0)
return; // (B) 조기 반환
conn.Exec($"UPDATE wallet SET gold = gold + {amount} WHERE user_id = {userId}");
conn.Exec($"INSERT INTO reward_log(user_id, amount) VALUES ({userId}, {amount})");
conn.Commit();
}
finally
{
_pool.Return(conn); // 항상 반납
}
}
} 결함 코드 · C++
// 시나리오: 커넥션 풀에서 빌린 커넥션을 트랜잭션이 열린 채 반납 (C++ 게임서버)
//
// 서버는 DB 커넥션 풀에서 커넥션을 빌려 트랜잭션을 열고 작업한 뒤, 커밋/롤백하고
// 반납한다. 반납된 커넥션은 곧 다른 요청이 다시 빌려 쓴다.
//
// grantReward 는 보상 지급을 트랜잭션으로 처리한다. RAII 로 커넥션 반납은 보장하며,
// 커밋은 정상 경로에서 수행한다.
//
// 요구사항:
// - 반납되는 커넥션은 진행 중 트랜잭션이 없는 깨끗한 상태여야 한다.
// - 다음 사용자가 빌린 커넥션은 이전 트랜잭션/세션 상태를 이어받지 않아야 한다.
//
// 과제:
// 1) (A)(B) 를 중심으로 반납 커넥션이 다음 사용자에게 어떤 영향을 주는지 설명하라.
// 2) 조기 반환/예외 경로의 오작동을 제시하라.
// 3) 수정안을 제시하라. g++ -std=c++17 로 컴파일된다고 가정한다.
#include <string>
#include <stdexcept>
struct PooledConn {
bool inTransaction = false;
void begin() { inTransaction = true; }
void commit() { inTransaction = false; }
void rollback() { inTransaction = false; }
void exec(const std::string& sql) {
if (sql.find("FAIL") != std::string::npos) throw std::runtime_error("db error");
}
};
struct ConnPool {
PooledConn* rent();
void ret(PooledConn* c); // 풀로 반납
};
// 반납만 보장하는 RAII 가드
struct ConnGuard {
ConnPool& pool; PooledConn* conn;
ConnGuard(ConnPool& p, PooledConn* c) : pool(p), conn(c) {}
~ConnGuard() { pool.ret(conn); } // 스코프 종료 시 항상 반납
};
class RewardService {
public:
explicit RewardService(ConnPool& pool) : pool_(pool) {}
void grantReward(long userId, int amount) {
PooledConn* conn = pool_.rent();
ConnGuard guard(pool_, conn); // 반납은 RAII 로 보장
conn->begin(); // (A)
if (amount <= 0)
return; // (B) 조기 반환
conn->exec("UPDATE wallet SET gold = gold + ? WHERE user_id = ?");
conn->exec("INSERT INTO reward_log ..."); // (실패 시 예외 발생 가능)
conn->commit();
}
private:
ConnPool& pool_;
};
int main() { return 0; } 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.