Huny's Dev Blog
huny.dev

Go HTTP 요청 취소가 TTS 작업을 멈추지 않는 이유: context 전파

Go HTTP 서버에서 클라이언트가 연결을 끊어도 TTS 작업이 계속되는 이유를 정리한다. r.Context와 context.WithTimeout을 작업 함수까지 전달하는 실행 예제, 취소 검증 방법, 실제 엔진 연동 시 주의점을 설명한다.

Hun Jang
Hun Jang Oct 26, 2026

HTTP 연결이 끝났다고 서버의 TTS 작업까지 자동으로 멈추지는 않는다. 요청에 종속된 작업을 중단하려면 r.Context()에서 파생한 context를 작업 함수까지 전달하고, 작업 함수가 취소 신호에 응답해야 한다. 이 글은 Go 표준 net/http 서버에서 요청 취소와 처리 시간 제한을 함께 적용하는 방법을 다룬다.

클라이언트 연결 종료에 따라 불필요한 음성합성 작업을 중단하는 개념을 표현한 일러스트. 실제 시스템 구성도는 아니다.

연결은 끊겼는데 CPU는 계속 사용되는 이유

예를 들어 20초 분량의 음성을 생성하는 API에서 사용자가 재생을 취소했다고 하자. 서버가 합성을 끝까지 수행한다면 듣지 않을 음성을 만드는 데 연산 자원을 사용하게 된다. 여기서 중요한 것은 두 단계다. 서버가 요청 취소를 감지해야 하고, 합성 작업도 그 신호를 받아 종료할 수 있어야 한다.

Go의 Context는 작업에 취소를 알리는 수단이다. 신호를 전달해도 이미 실행 중인 함수나 goroutine이 강제로 종료되지는 않는다. 따라서 처리 함수에 ctx 매개변수를 추가하는 것만으로는 충분하지 않다. Go의 Context 설명에서도 작업 함수가 Done() 신호를 확인하고 반환하는 패턴을 설명한다.

r.Context는 언제 취소되는가

서버가 받은 요청의 context는 클라이언트 연결 종료, HTTP/2 요청 취소, ServeHTTP 반환에 따라 취소된다. 브라우저 화면을 닫는 시점과 서버가 연결 종료를 감지하는 시점은 같다고 가정하지 않는 것이 좋다. 프록시가 있다면 서버가 실제로 관찰하는 연결도 확인해야 한다. 공식 API 문서

요청에서 시작한 작업을 context.Background()로 실행하면 요청 context와의 취소 연결이 사라진다. 사용자가 응답을 기다리는 동기 API라면 일반적으로 r.Context()를 기준으로 작업 수명을 정한다.

요청 취소와 타임아웃을 함께 적용하기

아래는 약 2초가 걸리는 작업을 흉내낸 독립 실행 예제다. 실제 음성을 생성하지 않으며, Ticker는 취소에 응답하는 작업 단계를 표현하기 위한 모형이다. HTTP 처리 시간의 예시 상한은 3초다.

context.WithTimeout(r.Context(), ...)는 요청의 취소 신호를 이어받으면서 추가 시간 제한을 설정한다. defer cancel()은 작업이 제한 시간 전에 끝난 경우에도 파생 context의 자원을 정리하도록 한다. 취소와 시간 제한 적용 가이드

취소된 요청에는 로그를 남기고 반환한다. 클라이언트가 아직 연결돼 있지만 이 예제의 처리 시간 제한이 만료된 경우에는 504를 반환하도록 구성했다. 이미 스트리밍 응답 헤더나 본문을 전송한 서비스라면 이처럼 상태 코드를 새로 정할 수 없으므로 별도의 스트림 종료 처리가 필요하다.

로컬에서 동작 확인하기

코드를 main.go로 저장하고 실행한다.

다른 터미널에서 정상 요청을 보내면 약 2초 후 simulation complete가 반환된다.

클라이언트 대기를 0.3초로 제한하면 curl은 타임아웃으로 종료된다. 서버가 연결 종료를 감지하면 request canceled: context canceled 로그를 남기고 모의 작업을 중단한다. 이 로그는 취소를 관찰한 예시이며, 실제 엔진의 GPU 작업 종료를 증명하지는 않는다.

실제 TTS 엔진에 적용할 때 확인할 경계

외부 HTTP 엔진을 호출한다면 http.NewRequestWithContext(ctx, ...)처럼 취소 가능한 요청을 사용한다. 다만 호출 측의 대기가 취소됐다고 원격 엔진 내부 작업까지 종료됐다는 뜻은 아니다. 원격 서비스에도 중단 처리가 있어야 한다.

프로세스 내부의 C/C++ 라이브러리나 GPU 추론도 마찬가지다. Go context만 전달해서 중단할 수 없는 엔진이라면 엔진이 제공하는 취소 기능이나 청크 사이의 중단 지점에 연결해야 한다. 중단 기능이 없다면 호출자가 기다리지 않는 것과 연산 자원을 회수하는 것을 구분해 관찰해야 한다.

요청이 끝나도 반드시 완료해야 하는 배치 작업이라면 요청 context 대신 별도의 작업 수명과 제한 시간을 설계한다. 이 글의 예제는 사용자가 응답을 기다리는 요청 종속 작업을 대상으로 한다.

적용 전 확인할 사항

  • 요청 context가 중간 함수에서 Background() 등으로 바뀌지 않는지 확인한다.
  • 실제 작업 함수가 취소를 확인하고 반환하는지 확인한다.
  • 외부 엔진의 중단까지 전달되는지 로그나 계측으로 확인한다.
  • 스트리밍 응답이 시작된 뒤의 오류 처리 경로를 별도로 확인한다.

검증 범위: Go 1.24.7 / Linux amd64에서 정상 응답, 부모 context 취소, 타임아웃, 실제 HTTP 클라이언트 취소의 네 가지 자동 검증을 통과했다. 실제 TTS 엔진·GPU·프록시 및 HTTP/2 환경의 연동 검증은 포함하지 않는다.

참고 문서

English version

Go HTTP Request Cancellation for TTS: Propagating ContextGo HTTP Request Cancellation for TTS: Propagating Context

추천 글