golang에서 타임아웃 핸들링하기 (feat. aws-sdk-go v1)

Matthew Woo·2024년 9월 1일
0

golang

목록 보기
1/1
post-thumbnail

As-Is

현재 golang을 사용하면서 타임아웃의 경우 아래와 같이 에러 핸들링을 하고 있다.

// timeout.go
func IsTimeout(err error) bool {
	if timeout, ok := err.(interface{ Timeout() bool }); ok || errors.As(err, &timeout) {
		if timeout.Timeout() {
			return true
		}
	}

	return errors.Is(err, context.Canceled)
}

Problem

timeout 에러의 경우 err에 Timeout() method 를 갖고 있는게 일반적이지만 aws-sdk-go 의 경우 Timeout() method 를 사용하지 않아 이를 사용하는 라이브러리에서 발생하는 에러의 경우 위 메소드로 타임아웃 체크가 되지 않는다.

context canceled 이라고 나오지만 errors.Is(err, context.Canceled) 에도 잡히지 않았다

How to handle

// Override the error with a context canceled error, if that was canceled.
	ctx := r.Context()
	select {
	case <-ctx.Done():
		r.Error = awserr.New(request.CanceledErrorCode,
			"request context canceled", ctx.Err())
		r.Retryable = aws.Bool(false)
	default:
	}
    
// ref: https://github.com/aws/aws-sdk-go/blob/02c1f723b528251a45068001bee8b56d904d7484/aws/corehandlers/handlers.go#L170-L172

aws-sdk-go 내부 코드를 보면 context cancel 이 발생하면 새로운 error new로 생성하면서 error value 값에 override한다. context나 Timeout()으로 핸들링하는 구조가 아니기 때문이다. (코드)

aws-sdk-go 에서 에러는 별도 인터페이스 를 사용해서 timeout 에러를 확인 후 핸들링이 필요하다.

// https://github.com/aws/aws-sdk-go/blob/02c1f723b528251a45068001bee8b56d904d7484/aws/request/request.go#L23-L43
const (
	// ErrCodeResponseTimeout is the connection timeout error that is received
	// during body reads.
	ErrCodeResponseTimeout = "ResponseTimeout"

	// CanceledErrorCode is the error code that will be returned by an
	// API request that was canceled. Requests given a aws.Context may
	// return this error when canceled.
	CanceledErrorCode = "RequestCanceled"
)

지금 핸들링 하려는 에러는 여기 코드 에 있었다.

Solve


import "github.com/aws/aws-sdk-go/aws/request"

// aws-sdk-go v1 에서 err 에 Timeout() method가 없어서 사용. v2로 전환 시 Timeout() method 사용
func isErrorTimeout(err error) bool {
	if v, ok := err.(interface{ Code() string }); ok {
		code := v.Code()
		if code == request.CanceledErrorCode || code == request.ErrCodeResponseTimeout {
			return true
		}
	}
	return false
}

이렇게 원하는 CanceledErrorCodeErrCodeResponseTimeout 를 확인하는 isErrorTimeout 함수를 aws-sdk-go를 사용하는 인프라레이어에 생성했다.

아래와 같이 사용한다.

if err != nil {
		if isErrorTimeout(err) {
			return nil, EnsureTimeoutError(err)
		}

이렇게 하면 어느 레이어에서나 본 글 가장 위 IsTimeout 메소드로 타임아웃을 체크할 수 있다.

EnsureTimeoutError 는 에 IsTimeout 에러로 핸들링 할 수 있게 IsTimeout()를 구현해둔 에러를 래핑하는 메소드다.

// timeout.go
type timeoutError struct {
	err error
}

func (e *timeoutError) Error() string {
	return e.err.Error()
}

func (e *timeoutError) Unwrap() error {
	return e.err
}

func (e *timeoutError) Timeout() bool {
	return true
}

func IsTimeout(err error) bool { ... }

// EnsureTimeoutError returns a timeout error if the given error is a timeout error.
func EnsureTimeoutError(err error) error {
	if err == nil {
		return nil
	}

	if IsTimeout(err) {
		return err
	}

	return &timeoutError{err: err}
}

aws-sdk-go-v2 의 경우 코드를 보면 Timeout() 을 지원한다. 고로 위의 내용들이 필요없다.

직접 aws-sdk-go 사용하는 코드들의 경우 v2를 사용하면 되지만 사용하는 일부 라이브러리들이 여전히 v1을 사용하고 있거나 v2로 전환한지 얼마되지 않아 위 핸들링 과정이 필요했다.

profile
지속가능하고 안정적인 시스템을 만들고자 합니다.

0개의 댓글