นากกายสีมิ้นต์หยุดรอสายพานงานที่นาฬิกาทรายหมด แต่เครื่องด้านหลังยังหมุนอยู่

Java CompletableFuture.orTimeout: ตั้งเส้นตายได้ แต่ไม่ได้หยุดงานเบื้องหลังให้

พี่โชว์Engineering

orTimeout ช่วยไม่ให้ caller รอ CompletableFuture ไปตลอด แต่ไม่ได้รับประกันว่างานต้นทางหยุดครับ ถ้า task กำลัง query ฐานข้อมูลหรือเรียก HTTP มันอาจทำต่อ กิน thread/connection และสร้าง side effect หลัง caller เลิกสนใจแล้ว

WHY: deadline ของผลลัพธ์กับ cancellation คนละเรื่อง

Java 17 API ระบุว่า future จะจบแบบ exception ด้วย TimeoutException หากไม่เสร็จก่อนเวลา เมธอดคืน future ตัวเดิม ไม่ได้สร้าง execution sandbox และไม่มีข้อมูลว่าจะ interrupt resource ภายในอย่างไร

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class Main {
    public static void main(String[] args) {
        String fast = CompletableFuture.completedFuture("ok")
                .orTimeout(1, TimeUnit.SECONDS)
                .join();
        assert fast.equals("ok");

        CompletableFuture<String> never = new CompletableFuture<>();
        try {
            never.orTimeout(20, TimeUnit.MILLISECONDS).join();
            throw new AssertionError("timeout expected");
        } catch (CompletionException error) {
            assert error.getCause() instanceof TimeoutException;
        }
    }
}

Timeout ต้องไหลถึง resource

ตั้ง connect/read timeout ที่ HTTP client หรือ query timeout ที่ฐานข้อมูลด้วย หาก API รองรับ cancellation ให้ผูก token/interrupt และออกแบบ handler ให้ idempotent กรณี completion แข่งกับ timeout อย่า retry อัตโนมัติเมื่อไม่รู้ว่างานแรก commit แล้วหรือยัง โดยเฉพาะคำสั่งชำระเงินและสร้าง order

completeOnTimeout(defaultValue, ...) เหมาะเมื่อ fallback เป็นค่าที่ contract ยอมรับ แต่เสี่ยงซ่อน outage หาก metric ไม่แยกค่าจริงกับค่า fallback ส่วน exceptionally จัดการ exception ทุกชนิด ไม่ใช่เฉพาะ timeout จึงควร inspect cause ก่อนแปลงเป็นค่า default

ใน test ใช้เวลาสั้นได้เพื่อยืนยัน contract แต่ production test ที่พึ่ง wall clock มากเกินไปอาจ flaky แยก unit test ของ state transition ออกจาก integration test ของ client timeout และให้ integration มีเผื่อเวลาที่เหมาะสม

Recap

  • orTimeout จำกัดเวลารอผล ไม่ได้หยุด resource อัตโนมัติ
  • ตั้ง timeout/cancellation ที่ client ต้นทางด้วย
  • retry เฉพาะ operation ที่ idempotent หรือมี idempotency key
  • แยก metric ของ timeout, fallback และ late completion