Go 컨텍스트(Context) 취소, 왜 발생했는지 이제 알 수 있습니다!

by DD
3개월 전
조회수 10

Go 언어에서 컨텍스트(Context) 취소는 디버깅의 어려움을 야기하는 주요 문제 중 하나였음

Go 1.20부터 도입된 `WithCancelCause`를 통해 컨텍스트 취소 원인(Cause)을 명확하게 추적 가능해짐

`WithTimeoutCause` 사용 시, 일반적인 반환 경로에서는 원인 정보가 유실(Information Loss)되는 미묘한 문제 존재

수동 타이머(Manual Timer)를 활용하거나, `WithCancelCause`와 `WithTimeoutCause`를 함께 사용하여 모든 경우의 수를 커버하는 방법 제시

컨텍스트(Context) 취소 문제의 근본 원인

기존 Go 컨텍스트(Context)는 취소 시 `context.Canceled` 또는 `context.DeadlineExceeded`와 같은 모호한 에러(Ambiguous Error)만 반환하여, 개발자가 문제의 근본 원인을 파악하기 어렵게 만들었다. 특히, 클라이언트 연결 끊김, 부모 컨텍스트 만료, 서버 종료 등 다양한 상황에서 동일한 에러가 발생하여 디버깅 시간(Debugging Time)을 증가시키는 요인으로 작용했다. 이러한 문제점은 대규모 시스템에서 더욱 심각하게 나타났다.

WithCancelCause를 활용한 원인 추적

Go 1.20에서 도입된 `WithCancelCause` 함수는 컨텍스트 취소 시 구체적인 원인(Specific Cause)을 설정할 수 있도록 지원한다. 이를 통해 개발자는 각 서비스 호출 실패, 타임아웃, 또는 기타 예외 상황에 대한 세분화된 에러 정보(Detailed Error Information)를 로깅할 수 있게 되었다. 특히, `context.Cause(ctx)` 함수를 사용하여 컨텍스트 취소의 원인을 쉽게 확인할 수 있으며, 이는 문제 해결 시간(Problem-Solving Time)을 단축하는 데 기여한다.

WithTimeoutCause의 함정: defer cancel()

`WithTimeoutCause`는 타임아웃 발생 시 커스텀 에러(Custom Error)를 설정할 수 있지만, 일반적인 반환 경로에서는 `defer cancel()` 호출로 인해 원인 정보가 유실될 수 있다. 즉, 함수가 타임아웃 전에 정상적으로 종료되는 경우, 설정된 원인이 아닌 `context.Canceled`가 기록된다. 이러한 미묘한 차이(Subtle Difference)는 로깅 및 모니터링 시스템에서 정확한 문제 진단을 방해할 수 있다.

수동 타이머(Manual Timer) 패턴과 스택 방식

`WithTimeoutCause`의 문제를 해결하기 위해, 기사에서는 수동 타이머(Manual Timer)를 사용하여 `WithCancelCause`와 함께 사용하는 방법을 제시한다. 이 방법은 모든 반환 경로에서 일관된 원인 정보를 보장하며, 타임아웃(Timeout)명시적 취소(Explicit Cancellation) 모두에 대한 정확한 원인을 기록할 수 있다. 또한, `errors.Is`를 사용하여 `context.DeadlineExceeded`를 확인해야 하는 경우, `WithCancelCause`와 `WithTimeoutCause`를 함께 사용하는 스택 방식(Stacked Approach)을 제안한다.

What canceled my Go context?