context
패키지의 도움을 받아 오래 실행되는 프로세스를 관리해 볼 것이다.Server
함수는 Store
인수를 받은 뒤 http.HandlerFunc
를 반환한다. Store는 다음과 같이 정의되어 있다:store
의 Fetch
메서드를 통해 데이터를 얻은 뒤 응답으로 해당 데이터를 출력한다.Store
의 구현부분이다.Store
가 Fetch
를 끝내지 못하는 경우를 생각해보자.Store
를 중단시킬 방법이 필요하므로 인터페이스를 알맞게 바꿔주자.data
를 가져오는 데에 시간이 걸리도록 하고, 취소 여부를 알 수 있도록 해보자. 또한 해당 객체가 어떻게 호출되는지 지켜볼 것이므로 이름을 SpyStore
로 바꿔주자. 그리고 Store
인터페이스를 구현하도록 Cancel
메서드를 추가해주자.컨텍스트 패키지는 기존 컨텍스트들로 부터 새 컨텍스트들을 파생시키는 함수를 제공하며, 이를 사용할 경우 해당 컨텍스트들은 트리를 형성하게 된다: 어떠한 컨텍스트가 취소될 경우, 그것으로 부터 파생된 모든 컨텍스트들 또한 취소된다.
request
에서 새로운 cancellingCtx
를 파생시킴과 동시에 cancel
함수를 얻게 된다. 다음으로 time.AfterFunc
를 통해 해당 함수가 5 밀리초 후에 호출되도록 설정해보자. 마지막으로 request.WithContext
를 통해 새로 얻은 컨텍스트를 사용해보자.Store
를 취소하여서는 안된다.Store
가 취소되지 않았음을 assert 하도록 해보자.context
에게는 Done()
이라는 메서드가 있으며 이는 컨텍스트가 "완료"되거나 "취소"될 경우 신호를 받는 채널을 반환한다. 우리는 해당 신호를 대기하여 해당 신호가 올 경우 store.Cancel
을 호출하고 싶지만, 만약 Store
가 그 전에 Fetch
를 완료했을 경우에는 그 신호를 무시해주고 싶다.Fetch
를 호출한 뒤 새로 만들어줄 채널인 data
에 결과를 보내준다. 그리고 select
문을 사용하여 두 비동기 프로세스를 경합시킨 뒤 응답을 출력하거나 Cancel
을 수행하자.*testing.T
를 잊지 말고 넘겨주도록 하자.Store
를 취소하는데에 직접 관여하는 것이 적절하다고 생각하나? Store
가 다른 실행 속도가 느린 프로세스들에 의존하는 경우 어떻게 될까? Store.Cancel
이 올바르게 파생 컨텍스트들에게 취소를 전파하도록 해야할 필요가 있다.context
를 사용하는 주요 이유 중의 하나는 일관된 취소를 수행하기 위함이다.서버로 들어오는 요청들에 대해 컨텍스트를 생성하는 것이 좋고 내보내는 함수는 컨텍스트를 인수로 받는 것이 좋다. 또한 두 과정 사이의 함수들을 호출할 때 해당 컨텍스트를 반드시 전파하여야 하며, 선택적으로 해당 컨텍스트를 WithCancel, WithDeadline, WithTimeout, 혹은 WithValue를 이용해 파생시킨 컨텍스트를 사용할 수도 있다. 컨텍스트가 취소될 때 해당 컨텍스트를 상속한 모든 컨텍스트들 또한 취소된다.
구글에서는 고 프로그래머들로 하여금 모든 들어오는 요청과 나가는 요청 함수들의 첫번째 인수를 컨텍스트로 하도록 규정한다. 이는 여러 팀에서 개발된 고 코드들이 서로 잘 작동하도록 한다. 컨텍스트는 간단한 방법을 통해 시간 초과와 취소를 관리할 수 있도록 하며, 보안 자격 증명과 같은 중요한 값들이 고 프로그램내에서 올바르게 넘겨질 수 있도록 한다.
Store
에 context
를 넘겨줌으로써 관여하게 해보자. 이를 통해 Store
는 해당 context
를 그것에 의존하는 것들에 넘겨줄 수 있게 되고, 그 컨텍스트들은 그것들을 취소하는 데에 관여하게 된다.Store
를 통해 전파시키는 것과 Store
가 취소될 경우 보내지는 오류를 처리하는 것이다.Store
인터페이스를 수정하여 새로 관여하는 부분을 담당할 수 있도록 하자.SpyStore
를 수정해주자.context
를 실제로 사용하는 메서드처럼 바꿔보자.data
채널에 결과값을 보내주고 고루틴에서 ctx.Done
을 대기하여 값이 들어올 경우 작업을 중단하게 해보자.select
문을 사용하여 해당 고루틴이 완료되거나 취소되는 것을 기다리도록 하자.context
를 사용하는 함수들과 메서드들을 만들 경우 아래와 비슷한 접근 방식을 사용하게 되므로 꼭 작동 방식을 이해해보도록 하자.httptest.ResponseRecorder
는 이러한 기능을 제공하지 않으므로 스파이를 추가하여 이를 테스트 해보자.SpyResponseWriter
는 http.ResponseWriter
인터페이스를 구현하기에 테스트에 사용할 수 있다.context
를 넘겨주고 이후로 호출된 함수들에게 의존하기 때문이다.context
를 인수로 받는 함수를 작성하고 고루틴, select
문, 채널을 이용하여 해당 컨텍스트를 취소하는 방법http.ResponseWriter
의 스파이를 작성하는 법내 회사(존재하지는 않지만)에서 ctx.Value를 사용하면 해고당한다
context
를 통해 값들을 전해주는 것이 편리하다는 이유로 옹호하곤 한다.context.Values
는 단순히 타입이 지정되지 않은 맵이기 때문에 타입 안전성이 보장되지 않고 실제로는 가지고 있지 않은 값을 처리해줘야 하는 문제점을 가지고 있다. 한 모듈에서 다른 모듈로 보낼 경우 맵 키들의 대응 목록을 만들어줘야 하고, 누군가 코드를 수정하기 시작하면 문제가 발생하기 시작한다.context.Value
를 사용하지 말고 타입이 지정된 인수로 넘겨줘야 한다. 이는 정적으로 해당 부분이 검수되게 하고 모든 사람이 문서를 읽을 수 있도록 한다.context.Value의 내용은 사용자를 위한 것이 아니라 관리자를 위한 것이다. 기대되거나 문서화된 결과 값에 필요한 입력값이 되어서는 절대 안된다.
context
를 모든 곳에서 넘겨줘야하는 것은 탐탁하지 않고 이는 곧 고 언어가 가진 취소를 관리하는 데에 있어서의 부족함을 드러낸다는 것이다. 그는 또한 이러한 문제점이 라이브러리 레벨이 아닌 언어적인 레벨에서 수정되기를 바란다. 이러한 문제점이 해결되기 전까지는 오래 실행되는 프로세스를 관리하는 데에 있어 context
는 필요한 존재이다.