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#
내 답안 · 자동 저장

작성 후 위 해설 보기에서 모범 해설과 대조하세요.