Як реалізувати timeout для CompletableFuture
В Java 9+ є вбудований метод orTimeout():
🟢 Junior Level
В Java 9+ є вбудований метод orTimeout():
CompletableFuture<String> cf = fetchDataAsync();
// Таймаут 5 секунд
cf.orTimeout(5, TimeUnit.SECONDS)
.thenAccept(result -> System.out.println(result))
.exceptionally(ex -> {
if (ex instanceof TimeoutException) {
System.out.println("Timeout!");
}
return "fallback";
});
Для Java 8 — можна реалізувати вручну:
public static <T> CompletableFuture<T> withTimeout(
CompletableFuture<T> cf, long timeout, TimeUnit unit
) {
CompletableFuture<T> timeoutCF = new CompletableFuture<>();
// ⚠️ Увага: цей приклад створює новий ScheduledThreadPool на кожен виклик.
// В production використовуйте один загальний scheduler або не забудьте scheduler.shutdown().
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.schedule(() ->
timeoutCF.completeExceptionally(new TimeoutException()),
timeout, unit
);
return CompletableFuture.anyOf(cf, timeoutCF)
.thenApply(result -> (T) result);
}
🟡 Middle Level
Java 9+ methods
orTimeout:
cf.orTimeout(5, TimeUnit.SECONDS)
.exceptionally(ex -> {
// TimeoutException
return fallback;
});
completeOnTimeout:
// При таймауті — fallback значення
cf.completeOnTimeout("default", 5, TimeUnit.SECONDS)
.thenAccept(result -> System.out.println(result));
Java 8 implementation
public static <T> CompletableFuture<T> withTimeout(
CompletableFuture<T> cf, long timeout, TimeUnit unit
) {
CompletableFuture<T> result = new CompletableFuture<>();
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
// Schedule timeout
ScheduledFuture<?> timeoutFuture = scheduler.schedule(() -> {
result.completeExceptionally(new TimeoutException());
}, timeout, unit);
// Complete normally
cf.whenComplete((value, ex) -> {
timeoutFuture.cancel(false);
if (ex != null) {
result.completeExceptionally(ex);
} else {
result.complete(value);
}
});
return result;
}
Типові помилки
- Не закрити scheduler: ```java // ❌ Scheduler не закритий — memory leak ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
// ✅ Закрити після використання scheduler.shutdown();
---
## 🔴 Senior Level
### Internal Implementation
**orTimeout internals:**
```java
// orTimeout реєструє whenComplete обробник, який при нормальному
// завершенні викликає scheduledFuture.cancel(false), скасовуючи відкладену задачу.
// Якщо таймаут спрацьовує першим — completeExceptionally(TimeoutException)
// завершує CF, і ланцюжок колбеків більше не виконується.
Edge Cases
1. Timeout vs cancellation:
cf.orTimeout(5, TimeUnit.SECONDS);
// Timeout НЕ скасовує виконання задачі!
// Задача продовжує виконуватися у фоні
// Просто CF завершується з TimeoutException
2. Multiple timeouts:
// Останній таймаут перемагає
cf.orTimeout(10, TimeUnit.SECONDS)
.orTimeout(5, TimeUnit.SECONDS); // цей спрацює першим
Production Experience
Resilient service:
@Service
public class ResilientService {
public CompletableFuture<Data> getData(String key) {
return fetchDataAsync(key)
.orTimeout(5, TimeUnit.SECONDS)
.exceptionallyCompose(ex -> {
if (ex instanceof TimeoutException) {
return getFromCacheAsync(key);
}
return CompletableFuture.failedFuture(ex);
});
}
}
Best Practices
// ✅ orTimeout для Java 9+
cf.orTimeout(5, TimeUnit.SECONDS);
// ✅ completeOnTimeout для fallback
cf.completeOnTimeout(defaultValue, 5, TimeUnit.SECONDS);
// ✅ Custom timeout для Java 8
withTimeout(cf, 5, TimeUnit.SECONDS);
// ❌ Забути закрити scheduler
// ❌ Ігнорування TimeoutException
🎯 Шпаргалка для співбесіди
Обов’язково знати:
- orTimeout(timeout, unit) — Java 9+, завершує CF з TimeoutException
- completeOnTimeout(value, timeout, unit) — Java 9+, fallback значення при таймауті
- Таймаут НЕ скасовує задачу — вона продовжує виконуватися у фоні
- Java 8: ScheduledExecutorService + anyOf(cf, timeoutCF)
- Один загальний scheduler для всіх таймаутів, не новий на кожен виклик
Часті уточнюючі питання:
- orTimeout скасовує задачу? — Ні, CF завершується з TimeoutException, задача продовжує працювати
- completeOnTimeout vs orTimeout? — completeOnTimeout повертає fallback, orTimeout кидає виключення
- Java 8 таймаут без ScheduledExecutor? — Немає альтернативи. ScheduledExecutorService — єдиний варіант
- Multiple timeouts на один CF? — Останній (найкоротший) перемагає
Червоні прапорці (НЕ говорити):
- «orTimeout скасовує виконання задачі» — задача продовжує працювати
- «Створюю новий ScheduledThreadPool на кожен timeout» — memory leak, потрібен один загальний
- «TimeoutException не потрібно обробляти» — потрібен fallback або retry
Пов’язані теми:
- [[21. Що робить метод orTimeout() в Java 9+]]
- [[10. Що робить метод anyOf() і в яких випадках він корисний]]
- [[27. Як реалізувати retry логіку за допомогою CompletableFuture]]
- [[16. Як правильно виконати декілька паралельних запитів до мікросервісів]]