timeout 뒤에도 작업이 남는 문제
외부 API 호출에 orTimeout(200, MILLISECONDS)를 붙였더니 사용자는 빠르게 오류를 받지만 executor thread와 외부 요청은 계속 사용됩니다. timeout 응답을 만들었다는 사실과 실행 중인 작업을 멈췄다는 사실을 같게 보면 resource 사용량을 잘못 판단하게 됩니다.
CompletableFuture.orTimeout은 제한 시간 안에 완료되지 않은 같은 future를 TimeoutException으로 exceptional completion시키지만, 실행 중인 supplier를 자동으로 중단시키지는 않습니다.
완료 상태와 실행 수명 분석
orTimeout은 새 wrapper가 아니라 같은 CompletableFuture를 반환합니다. 제한 시간이 먼저 오면 그 future의 결과는 exceptional completion으로 결정되고 join()은 cause가 TimeoutException인 CompletionException을 던집니다. 그러나 supplier를 실행하는 thread에 interrupt를 보내거나 HTTP 요청을 취소한다는 계약은 없습니다.
CompletableFuture<String> future = CompletableFuture
.supplyAsync(() -> slowRemoteCall(), executor)
.orTimeout(200, TimeUnit.MILLISECONDS);
try {
future.join();
} catch (CompletionException error) {
if (error.getCause() instanceof TimeoutException) {
metrics.increment("price.timeout");
}
}
실제 resource까지 제한하는 해결 방법
HTTP client, JDBC driver처럼 하위 API가 제공하는 connect·read·query timeout을 함께 설정합니다. 직접 만든 작업이라면 interrupt나 cancellation token을 정기적으로 확인하고, 취소 시 socket·connection을 닫는 경계를 명시합니다. 같은 future에 cancel(true)를 호출해도 CompletableFuture 문서상 mayInterruptIfRunning은 실행 제어에 영향을 주지 않으므로 executor queue와 active thread가 기준선으로 돌아오는지 별도로 확인해야 합니다.
공식 문서
JDK version과 실제 하위 client의 cancellation 계약을 함께 확인합니다.