34. 스레드풀 워커의 ThreadLocal 컨텍스트 잔존과 요청 간 오염 전파

난이도 최상 해설 보기 →

결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커 (A)(B) 는 주목 위치 힌트다.

결함 코드 · C#
// [시나리오] 요청 처리 스레드풀의 "현재 처리 중인 플레이어" 컨텍스트 (서버 내부)
// 요청 핸들러 곳곳(로깅, 권한 검사, 감사 기록)에서 "지금 이 스레드가 어느 플레이어의
// 요청을 처리하고 있는가"를 알아야 한다. 매 호출마다 playerId 를 파라미터로 넘기는
// 대신, 스레드로컬(ThreadLocal<T>)에 "현재 처리 중인 컨텍스트"를 저장해두고 핸들러
// 내부 아무 곳에서나 꺼내 쓰는 방식을 택했다. 스레드풀 워커는 요청을 하나 처리한 뒤
// 즉시 다음 요청을 처리하도록 재사용된다.
//
// [요구사항]
//   - 한 요청을 처리하는 동안 조회되는 "현재 컨텍스트"는 반드시 그 요청의 플레이어여야
//     한다.
//   - 한 요청 처리가 끝나면, 다음에 그 워커 스레드가 다른 요청을 처리할 때 이전 요청의
//     컨텍스트가 남아 있어서는 안 된다.
//
// [과제] RequestProcessor.HandleRequest 와 CurrentContext 사용부를 검토하고, 스레드풀
//        워커가 재사용될 때 위 요구사항이 어떻게 어긋날 수 있는지 원인과 수정안을
//        제시하라. 표시된 (A)(B) 지점에 주목하라.

using System;
using System.Threading;
using System.Threading.Tasks;

class PlayerContext
{
    public int PlayerId;
    public string PermissionLevel;
}

static class CurrentContext
{
    // 워커 스레드마다 하나씩 유지되는 "현재 처리 중인 컨텍스트"
    public static readonly ThreadLocal<PlayerContext> Value = new ThreadLocal<PlayerContext>();
}

class RequestProcessor
{
    private void AuthorizeAction(string action)
    {
        var ctx = CurrentContext.Value.Value;
        if (ctx == null) throw new InvalidOperationException("컨텍스트 없음");
        // ctx.PermissionLevel 로 권한 검사...
    }

    private void LogAudit(string message)
    {
        var ctx = CurrentContext.Value.Value;
        // ctx.PlayerId 를 로그에 기록...
    }

    private void DoWork(int playerId, string action)
    {
        AuthorizeAction(action);
        // ... 실제 처리 ...
        LogAudit($"{action} 처리됨");
    }

    // 스레드풀 워커에서 요청마다 호출된다 (같은 워커 스레드가 계속 재사용됨).
    public void HandleRequest(int playerId, string permissionLevel, string action)
    {
        // (A)
        CurrentContext.Value.Value = new PlayerContext
        {
            PlayerId = playerId,
            PermissionLevel = permissionLevel
        };

        try
        {
            DoWork(playerId, action);
        }
        catch (Exception)
        {
            // 예외 발생 시 로깅 후 요청 실패 응답
            throw;
        }

        // (B)
    }
}
내 리뷰 · C#
내 답안 · 자동 저장

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