apache/shenyu · error · ShenyuTimeoutException
Request timeout, the maximum number of retry times has been…
Error message
Request timeout, the maximum number of retry times has been exceeded
What it means
DefaultRetryStrategy retries a failed upstream call with exponential backoff, filtering only timeouts/connect/read-timeout/IllegalStateException causes. When all retry attempts are exhausted it throws ShenyuTimeoutException, which is immediately mapped to HTTP 408/504 ResponseStatusException for the client.
Solutions
- Fix or scale the upstream service causing persistent timeouts; check its address and health.
- Increase the HttpClient read/connect timeout settings in the gateway's httpclient configuration if the endpoint legitimately takes long.
- Adjust the retry strategy's retry count/backoff to tolerate transient slowness.
- Return 504 to the client deliberately and add client-side handling of REQUEST_TIMEOUT/504 statuses, which is what this maps to.
Example fix
// before (shenyu httpclient config)
readTimeout: 3000
// after
readTimeout: 10000
connector: {readTimeout: 10000, writeTimeout: 10000} Defensive patterns
Strategy: retry
Try / catch
client.send(request)
.onErrorResume(ResponseStatusException.class, e -> {
if (e.getStatusCode() == HttpStatus.REQUEST_TIMEOUT || e.getStatusCode() == HttpStatus.GATEWAY_TIMEOUT) {
return Mono.just(fallbackResponse());
}
return Mono.error(e);
}); Prevention
- Size read/connect timeouts to the endpoint's real worst-case latency.
- Health-check upstreams so persistent timeouts are caught before clients see them.
- Retry only idempotent requests.
When it happens
Trigger: The upstream request keeps timing out (TimeoutException, ConnectTimeoutException, ReadTimeoutException) or IllegalStateException across every retry attempt, so onRetryExhaustedThrow fires in execute().
Common situations: Slow or hung backend service, wrong upstream address causing connect timeouts, read timeouts from long-running endpoints exceeding the client read timeout, network partition between gateway and backend.
Understand the failure class
Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- Retry limit exceeded
- doHeartbeat Failed to execute,wait to retry.
- apollo client getItemValue time out
- Tool execution timeout or error
- Caught exception while waiting for rpc
AI-assisted analysis of apache/shenyu@567142e072 (2026-09-12).
Data as JSON: /api/errors/b34347078fee1d93.
Report an issue: GitHub.
Appendix: source
Thrown at shenyu-plugin/shenyu-plugin-httpclient/src/main/java/org/apache/shenyu/plugin/httpclient/DefaultRetryStrategy.java:75
private final AbstractHttpClientPlugin<R> httpClientPlugin;
public DefaultRetryStrategy(final AbstractHttpClientPlugin<R> httpClientPlugin) {
this.httpClientPlugin = httpClientPlugin;
}
@Override
public Mono<R> execute(final Mono<R> clientResponse, final ServerWebExchange exchange, final Duration duration, final int retryTimes) {
final String retryStrategy = (String) Optional.ofNullable(exchange.getAttribute(Constants.RETRY_STRATEGY)).orElseGet(() -> "current");
if (RetryEnum.CURRENT.getName().equals(retryStrategy)) {
//old version of DividePlugin and SpringCloudPlugin will run on this
RetryBackoffSpec retryBackoffSpec = Retry.backoff(retryTimes, Duration.ofMillis(20L))
.maxBackoff(Duration.ofSeconds(20L))
.transientErrors(true)
.jitter(0.5d)
.filter(t -> t instanceof java.util.concurrent.TimeoutException || t instanceof io.netty.channel.ConnectTimeoutException
|| t instanceof io.netty.handler.timeout.ReadTimeoutException || t instanceof IllegalStateException)
.onRetryExhaustedThrow((retryBackoffSpecErr, retrySignal) -> {
throw new ShenyuTimeoutException("Request timeout, the maximum number of retry times has been exceeded");
});
return clientResponse.retryWhen(retryBackoffSpec)
.onErrorMap(ShenyuTimeoutException.class, th -> new ResponseStatusException(HttpStatus.REQUEST_TIMEOUT, th.getMessage(), th))
.onErrorMap(java.util.concurrent.TimeoutException.class, th -> new ResponseStatusException(HttpStatus.GATEWAY_TIMEOUT, th.getMessage(), th));
}
final Set<URI> exclude = new HashSet<>(Collections.singletonList(Objects.requireNonNull(exchange.getAttribute(Constants.HTTP_URI))));
return resend(clientResponse, exchange, duration, exclude, retryTimes)
.onErrorMap(ShenyuException.class, th -> new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE,
"CANNOT_FIND_HEALTHY_UPSTREAM_URL_AFTER_FAILOVER", th))
.onErrorMap(java.util.concurrent.TimeoutException.class, th -> new ResponseStatusException(HttpStatus.GATEWAY_TIMEOUT, th.getMessage(), th));
}
private Mono<R> resend(final Mono<R> clientResponse,
final ServerWebExchange exchange,
final Duration duration,
final Set<URI> exclude,
final int retryTimes) {
Mono<R> result = clientResponse;View on GitHub (pinned to 567142e072)