{"record":{"id":"b34347078fee1d93","repo":"apache/shenyu","slug":"request-timeout-the-maximum-number-of-retry-times-has-been","errorCode":null,"errorMessage":"Request timeout, the maximum number of retry times has been exceeded","messagePattern":"Request timeout, the maximum number of retry times has been exceeded","errorType":"http","errorClass":"ShenyuTimeoutException","httpStatus":408,"severity":"error","filePath":"shenyu-plugin/shenyu-plugin-httpclient/src/main/java/org/apache/shenyu/plugin/httpclient/DefaultRetryStrategy.java","lineNumber":75,"sourceCode":"    private final AbstractHttpClientPlugin<R> httpClientPlugin;\n\n    public DefaultRetryStrategy(final AbstractHttpClientPlugin<R> httpClientPlugin) {\n        this.httpClientPlugin = httpClientPlugin;\n    }\n\n    @Override\n    public Mono<R> execute(final Mono<R> clientResponse, final ServerWebExchange exchange, final Duration duration, final int retryTimes) {\n        final String retryStrategy = (String) Optional.ofNullable(exchange.getAttribute(Constants.RETRY_STRATEGY)).orElseGet(() -> \"current\");\n        if (RetryEnum.CURRENT.getName().equals(retryStrategy)) {\n            //old version of DividePlugin and SpringCloudPlugin will run on this\n            RetryBackoffSpec retryBackoffSpec = Retry.backoff(retryTimes, Duration.ofMillis(20L))\n                    .maxBackoff(Duration.ofSeconds(20L))\n                    .transientErrors(true)\n                    .jitter(0.5d)\n                    .filter(t -> t instanceof java.util.concurrent.TimeoutException || t instanceof io.netty.channel.ConnectTimeoutException\n                            || t instanceof io.netty.handler.timeout.ReadTimeoutException || t instanceof IllegalStateException)\n                    .onRetryExhaustedThrow((retryBackoffSpecErr, retrySignal) -> {\n                        throw new ShenyuTimeoutException(\"Request timeout, the maximum number of retry times has been exceeded\");\n                    });\n            return clientResponse.retryWhen(retryBackoffSpec)\n                    .onErrorMap(ShenyuTimeoutException.class, th -> new ResponseStatusException(HttpStatus.REQUEST_TIMEOUT, th.getMessage(), th))\n                    .onErrorMap(java.util.concurrent.TimeoutException.class, th -> new ResponseStatusException(HttpStatus.GATEWAY_TIMEOUT, th.getMessage(), th));\n        }\n        final Set<URI> exclude = new HashSet<>(Collections.singletonList(Objects.requireNonNull(exchange.getAttribute(Constants.HTTP_URI))));\n        return resend(clientResponse, exchange, duration, exclude, retryTimes)\n                .onErrorMap(ShenyuException.class, th -> new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE,\n                        \"CANNOT_FIND_HEALTHY_UPSTREAM_URL_AFTER_FAILOVER\", th))\n                .onErrorMap(java.util.concurrent.TimeoutException.class, th -> new ResponseStatusException(HttpStatus.GATEWAY_TIMEOUT, th.getMessage(), th));\n    }\n\n    private Mono<R> resend(final Mono<R> clientResponse,\n                           final ServerWebExchange exchange,\n                           final Duration duration,\n                           final Set<URI> exclude,\n                           final int retryTimes) {\n        Mono<R> result = clientResponse;","sourceCodeStart":57,"sourceCodeEnd":93,"githubUrl":"https://github.com/apache/shenyu/blob/567142e07261b3e615ae8850b30f4421f455cc5d/shenyu-plugin/shenyu-plugin-httpclient/src/main/java/org/apache/shenyu/plugin/httpclient/DefaultRetryStrategy.java#L57-L93","documentation":"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.","triggerScenarios":"The upstream request keeps timing out (TimeoutException, ConnectTimeoutException, ReadTimeoutException) or IllegalStateException across every retry attempt, so onRetryExhaustedThrow fires in execute().","commonSituations":"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.","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."],"exampleFix":"// before (shenyu httpclient config)\nreadTimeout: 3000\n// after\nreadTimeout: 10000\nconnector: {readTimeout: 10000, writeTimeout: 10000}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"client.send(request)\n  .onErrorResume(ResponseStatusException.class, e -> {\n    if (e.getStatusCode() == HttpStatus.REQUEST_TIMEOUT || e.getStatusCode() == HttpStatus.GATEWAY_TIMEOUT) {\n      return Mono.just(fallbackResponse());\n    }\n    return Mono.error(e);\n  });","preventionTips":["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."],"tags":["timeout","retry","httpclient"],"backgroundTag":"request-timeout","analyzedSha":"567142e07261b3e615ae8850b30f4421f455cc5d","analyzedAt":"2026-09-12T10:08:21.293Z","contentChangedAt":"2026-09-12T10:08:21.293Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}