WEB/Spring

스프링 핵심원리 고급편 2) ThreadLocal

Tony Lim 2022. 6. 5. 16:32
728x90
public class FieldLogTrace implements LogTrace {

    private static final String START_PREFIX = "-->";
    private static final String COMPLETE_PREFIX = "<--";
    private static final String EX_PREFIX = "<X-";

    private TraceId traceIdHolder; //traceId 동기화, 동시성 이슈 발생

    @Override
    public TraceStatus begin(String message) {
        syncTraceId();
        TraceId traceId = traceIdHolder;
        Long startTimeMs = System.currentTimeMillis();
        log.info("[{}] {}{}", traceId.getId(), addSpace(START_PREFIX, traceId.getLevel()), message);
        return new TraceStatus(traceId, startTimeMs, message);
    }

    private void syncTraceId() {
        if (traceIdHolder == null) {
            traceIdHolder = new TraceId();
        } else {
            traceIdHolder = traceIdHolder.createNextId();
        }
    }

상태를 parameter로 넘겨주는 방식에서 필드에 주입하는 방식으로 변경되었다.

begin시에 traceIdHolder에 level 을 +1 씩 계속 증가시키고 나중에 complete 시에 level 을 -1씩 감소시키는 방향으로 구성되었다.

 

@Configuration
public class LogTraceConfig {

    @Bean
    public LogTrace logTrace() {
        return new ThreadLocalLogTrace();
    }
}

싱글톤으로 스프링에서 스캔해서 사용하게 된다.

public class OrderControllerV3 {

    private final OrderServiceV3 orderService;
    private final LogTrace trace;

    @GetMapping("/v3/request")
    public String request(String itemId) {

        TraceStatus status = null;
        try {
            status = trace.begin("OrderController.request()");
            orderService.orderItem(itemId);
            trace.end(status);
            return "ok";
        } catch (Exception e) {
            trace.exception(status, e);
            throw e;//예외를 꼭 다시 던져주어야 한다.
        }
    }
}

하지만 요청이 연속으로 들어올 경우 singleton으로 사용되는 LogTrace안의 필드 정보가 변경이 되어 의도한대로 로그를 찍을수 없게 된다.

해당 필드는 여러 스레드가 접근이 가능한 critical section이다. 동시성 문제가 발생하지 않게 불변하게 하거나 락을 잡아줘야한다.

 

 다른 해법으로 ThreadLocal이라는 다른 상태 저장소를 사용한다.

private ThreadLocal<TraceId> traceIdHolder = new ThreadLocal<>();

이제 여러 스레드가 다 각각의 상태를 가질 수 있게 되었다.

요청이 다 끝나고 나서 스레드가 스레드풀로 들어가서 스레드가 죽지않고 threadlocal이 남아있게되어 메모리 누수가 생길 수 있다. 항상 remove를 호출하여 메모리를 놓아주자

안놓아주면 아래와 같은 문제가 생길 수 있다.

전혀 다른 요청(사용자) 인데 아까 반납되었던 Thread-A를 사용하게되면 미쳐 지워지지 않은 ThreadLocal을 참조해버릴 수 있는 보안 이슈가 생기게 된다.

 

 

728x90