15

I have some REST APIs that may take a while to execute, and I want to limit their execution duration. Preferably, if 30 seconds passed and the request didn't return, I would like to return a specific HTTP code / data and terminate that request completly.

The current code:

@RestController
@CrossOrigin(origins = {"*"}, maxAge = 4800, allowCredentials = "false")
public class APIController {

@RequestMapping(value = "/api/myapifunc", method = RequestMethod.POST, produces = "application/json")
public ResponseEntity<?> optimize(@RequestParam(value="param1", defaultValue="")) {
    // Code here
}
Tom Shir
  • 462
  • 1
  • 3
  • 14
  • 1
    Some good answers on [this post](https://stackoverflow.com/questions/34852236/spring-boot-rest-api-request-timeout) – Stalemate Of Tuning Jan 07 '19 at 19:49
  • 1
    Why not put proper timeouts and rollbacks on the services you are consuming rather than a blanket 'kill' at controller level without knowing the implications of aborting such requests? – jbx Jan 07 '19 at 19:52
  • @jbx - good question. we are using a third party in that REST service, which can take a long time to execute. I don't have any control on that, so therefore I need to abort it in case it takes too long to execute, to make sure such transactions won't cause high load on the server. – Tom Shir Jan 08 '19 at 11:30
  • @jbs - but your question got me thinking that maybe we should just limit the execution of that specific function and not the entire controller level. Thanks for that comment, upvoted! :) – Tom Shir Jan 08 '19 at 11:35
  • @TomShir cheers. If you are using Spring's `RestTemplate` you can actually configure it to have a timeout. https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/web/client/RestTemplateBuilder.html#setReadTimeout-java.time.Duration- There is also a connect timeout if you need. If the service is generally slow, then you might want to see if you can run it in parallel to whatever else you might be able to do that doesn't need its response. – jbx Jan 08 '19 at 17:29

5 Answers5

4

It looks like you are describing the Circuit Breaker pattern. If you have control over both the client and server code and want to explore Spring Cloud and Netflix Hysterix libraries you can take a look at Getting Started: Circuit Breaker guide.

If you are using Apache Tomcat as your servlet container you can configure Stuck Thread Detection Valve:

This valve allows to detect requests that take a long time to process, which might indicate that the thread that is processing it is stuck. Additionally it can optionally interrupt such threads to try and unblock them.

When such a request is detected, the current stack trace of its thread is written to Tomcat log with a WARN level.

The IDs and names of the stuck threads are available through JMX in the stuckThreadIds and stuckThreadNames attributes. The IDs can be used with the standard Threading JVM MBean (java.lang:type=Threading) to retrieve other information about each stuck thread.

Community
  • 1
  • 1
Karol Dowbecki
  • 43,645
  • 9
  • 78
  • 111
3

With Spring Boot 2.3 / Tomcat 9, you can set a timeout for ALL incoming HTTP requests to complete by installing a Tomcat StuckThreadDetectionValve. Here's the Spring configuration code you'll need (it's Kotlin):

import org.apache.catalina.valves.StuckThreadDetectionValve
import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory
import org.springframework.boot.web.server.WebServerFactoryCustomizer
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration
class RequestTimeoutConfiguration(
    @Value("\${app.tomcat.stuck-thread-detection.request-timeout.seconds}")
    private val stuckThreadTimeoutSeconds: Int
) {

    @Bean
    fun stuckThreadDetectionValve() =
        StuckThreadDetectionValve().apply {
            threshold = stuckThreadTimeoutSeconds
            interruptThreadThreshold = stuckThreadTimeoutSeconds
        }

    @Bean
    fun stuckThreadDetectionWebServerFactoryCustomizer(valve: StuckThreadDetectionValve) =
        WebServerFactoryCustomizer { factory: TomcatServletWebServerFactory ->
            factory.addContextValves(valve)
        }
}

Then you just need the property in application.properties to control it:

app.tomcat.stuck-thread-detection.request-timeout.seconds=130
Graham Lea
  • 5,797
  • 3
  • 40
  • 55
  • this doesn't work for the case where we have request piled up and those are waiting for connection. I doesn't cater to the wait time. – Rituparna Bhattacharyya Aug 05 '21 at 07:35
  • Thanks for the solution @Graham-Lea but for this I noticed that the thread still continues to run, thought it gets dis-connected from the main caller. Any reference for what to do, if I need to completely kill the thread itself, so that resources are consumed at all? – gxyd Nov 24 '22 at 13:09
  • @gxyd The `StuckThreadDetectionValve` will interrupt the thread. If it is doing something interruptable, e.g. waiting on I/O for a database, it should throw an `InterruptedException` and, unless that's caught somewhere, the thread should unwind. If your thread is doing CPU-bound work, it will not automatically be interrupted. In that case, you may want to check `Thread.isInterrupted()` periodically so you can abort the work when the valve sends the interrupt. – Graham Lea Nov 26 '22 at 03:00
0
@RequestMapping(value = "/api/myapifunc", method = RequestMethod.POST, produces = 
"application/json")
public ResponseEntity<?> optimize(@RequestParam(value="param1", defaultValue="")) {
 return new Callable<String>() {
    @Override
    public String call() throws Exception {
        Thread.sleep(3000); //this will cause a timeout
        return "foobar";
    }
  };
}

Future you can use or annotation @Timed @Transactional(timeout = 3000)

-1

You can use Future timeout:

final Future<Object> submit = service.submit(new Callable<Object>() {
    @Override
    public Object call() throws Exception {
        ......YOUR CODE
        return "";
    }
});
try {
    submit.get(3, TimeUnit.SECONDS);
} catch (Exception e) {
    log.error("fail",e);
}
alan9uo
  • 1,011
  • 1
  • 11
  • 17
-3

You can set this property configuration

 server.connection-timeout=30000 

in your application.properties. Based on official documentation says:

server.connection-timeout= # Time that connectors wait for another HTTP request before closing the connection. When not set, the connector's container-specific default is used. Use a value of -1 to indicate no (that is, an infinite) timeout.

Jonathan JOhx
  • 5,784
  • 2
  • 17
  • 33