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

  1. Fix or scale the upstream service causing persistent timeouts; check its address and health.
  2. Increase the HttpClient read/connect timeout settings in the gateway's httpclient configuration if the endpoint legitimately takes long.
  3. Adjust the retry strategy's retry count/backoff to tolerate transient slowness.
  4. 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

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.

Related errors


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)