Context in golang

Context

golang을 써봤으면 다들 한 번쯤 사용하게 되는것이 바로 context.Context 이다.

go에서는 context를 어떻게 사용하고 어떻게 설계했을지 살펴보자.

공식문서를 읽으면서 이해도를 높여보자.

Package context defines the Context type, which carries deadlines, cancellation signals, and other request-scoped values across API boundaries and between processes.

context 패키지는 API 경계를 넘어 프로세스 간에 deadline, 취소 signal 및 기타 요청 범위 값을 전달하는 컨텍스트 유형을 정의합니다.

컨텍스트는 우리 말로 문맥, 맥락을 뜻한다. REST 요청이건, 소켓 요청을하건, 요청-응답의 흐름 안에서 유지해야 할 상태를 context를 통해 공유한다.

Incoming requests to a server should create a Context, and outgoing calls to servers should accept a Context. 

서버로 들어오는 요청은 컨텍스트를 생성하고 서버로 나가는 호출은 컨텍스트를 수락해야 합니다. 

The chain of function calls between them must propagate the Context, optionally replacing it with a derived Context created using WithCancel, WithDeadline, WithTimeout, or WithValue. 

이들 사이의 함수 호출 체인은 컨텍스트를 전파해야 하며 선택적으로 WithCancel, WithDadline, WithTimeout 또는 WithValue를 사용하여 만든 파생 컨텍스트로 대체해야 합니다. 

When a Context is canceled, all Contexts derived from it are also canceled.

컨텍스트가 취소되면 컨텍스트에서 파생된 모든 컨텍스트도 취소됩니다.

즉, 요청이 들어오면 컨텍스트를 생성하고, 요청이 끝나면 컨텍스트를 종료시키겠다는 뜻이다.

위에서 얘기했던 요청-응답의 흐름을 중점으로 보자. 모든 API는 요청과 응답이 있다. 하나의 요청 안에서도 분기해서 많은 로직을 수행할 수 있다. 그렇게되면 하나의 요청-응답 사이에도 수많은 요청2-응답2, 요청3-응답3… 이 생기게 된다.

하나의 요청-응답이 하나의 lifecycle을 가진다고 했을때 root로 부터 context를 파생시켜가면 많은 API의 lifecycle을 관리할 수가 있다. WithCancel, WithDeadline, WithTimeout, WithValue를 이용해서 파생시키고, 관리하면 될 것이다.

The WithCancel, WithDeadline, and WithTimeout functions take a Context (the parent) and return a derived Context (the child) and a CancelFunc. 

WithCancel, WithDedline, WithTimeout 기능은 컨텍스트(부모)를 선택하고 파생 컨텍스트(자녀)와 CancelFunc를 반환합니다.

Calling the CancelFunc cancels the child and its children, removes the parent's reference to the child, and stops any associated timers. 

CancelFunc를 호출하면 본인과 자녀가 취소되고, 자녀에 대한 부모의 참조가 제거되며, 연관된 타이머가 중지됩니다.

Failing to call the CancelFunc leaks the child and its children until the parent is canceled or the timer fires. 

CancelFunc를 호출하지 못하면 부모가 취소되거나 타이머가 실행될 때까지 자녀 컨텍스트들에 누수가 발생됩니다.

The go vet tool checks that CancelFuncs are used on all control-flow paths.

go vet 는 CancelFuncs가 모든 제어 흐름 경로에서 사용되는지 확인합니다.

cancel() 함수를 실행하면 자식들이 전부 취소될까?

func childCtx(ctx context.Context, cancelFunc context.CancelFunc) {
	defer cancelFunc()
	time.Sleep(3 * time.Second)
	log.Println("childCtx done", ctx.Err())
}

func parentsCtx(c context.Context) {
	ctx, cancel := context.WithCancel(c)
	defer cancel()
	go childCtx(ctx, cancel)

	time.Sleep(time.Second * 1)
	log.Println("parentsCtx done")
}


// 2023/09/09 23:00:30 parentsCtx done
// 2023/09/09 23:00:32 childCtx done context canceled

실제로 동작 자체를 멈추는 것은 아니고, context가 취소된다. 자식 함수가 바로 종료되지는 않는다.

The WithCancelCause function returns a CancelCauseFunc, which takes an error and records it as the cancellation cause. Calling Cause on the canceled context or any of its children retrieves the cause. If no cause is specified, Cause(ctx) returns the same value as ctx.Err().

WithCancelCause 함수는 CancelCauseFunc를 반환하며, 이 함수는 오류를 가져와 취소 원인으로 기록합니다. 취소된 컨텍스트 또는 해당 하위 컨텍스트에서 원인을 호출하면 원인을 검색합니다. 원인이 지정되지 않으면 Cause(ctx)는 ctx.Err()와 동일한 값을 반환합니다.

컨텍스트 자체를 취소 처리하고 에러를 기록한다.

Programs that use Contexts should follow these rules to keep interfaces consistent across packages and enable static analysis tools to check context propagation:

컨텍스트를 사용하는 프로그램은 패키지 간 인터페이스를 일관되게 유지하고 정적 분석 도구가 컨텍스트 전파를 확인할 수 있도록 다음 규칙을 따라야 합니다:

Do not store Contexts inside a struct type; instead, pass a Context explicitly to each function that needs it. The Context should be the first parameter, typically named ctx:

구조체 유형 안에 컨텍스트를 저장하지 말고 컨텍스트를 필요한 각 함수에 명시적으로 전달합니다. 컨텍스트는 일반적으로 ctx로 명명되는 첫 번째 매개 변수여야 합니다:

func DoSomething(ctx context.Context, arg Arg) error {
	// ... use ctx ...
}

Do not pass a nil Context, even if a function permits it. Pass context.TODO if you are unsure about which Context to use.

함수가 허용하더라도 nil 컨텍스트를 전달하지 마십시오. 사용할 컨텍스트가 확실하지 않으면 context.TODO를 전달합니다.

Use context Values only for request-scoped data that transits processes and APIs, not for passing optional parameters to functions.

context Values는 프로세스 및 API를 전송하는 요청 범위 데이터에만 사용하며, 옵션 파라미터를 함수에 전달하는 경우에는 사용하지 않습니다.

The same Context may be passed to functions running in different goroutines; Contexts are safe for simultaneous use by multiple goroutines.

동일한 컨텍스트를 다른 경로에서 실행 중인 함수에 전달할 수 있습니다. 컨텍스트는 여러 goroutine에서 동시에 사용하기에 안전합니다.

컨텍스트를 사용하는 예제에 대한 것이다. context.Context 자체가 인터페이스다. 따라서 본인이 구현한 자체 Context를 사용하더라도 이러한 사항에 대해서 지키며 개발을 하면 좋을 것이다.

go 언어의 철학중에 이런 말이 있다.

메모리 공유를 통해 커뮤니케이션 하지 말고, 커뮤니케이션을 통해 메모리를 공유하라.

서로 다른 프로세스, API, 함수 등에서 메모리를 공유하게 하는 것이 아니라, 컨텍스트를 통해 커뮤니케이션을 형성하면 이러한 철학을 달성할 수 있을것이다.

Last updated