spring-graphql icon indicating copy to clipboard operation
spring-graphql copied to clipboard

Support timeout for GraphQL request handling and propagate cancellation to controller methods

Open tarun3300 opened this issue 2 years ago • 23 comments

Hi,

We would like to setup custom timeout for the async GraphQL query like we used to do in the kickstart GraphQL as below.

#Graphql query timeout settings graphql.servlet.async.timeout=5s

tarun3300 avatar Jul 21 '22 14:07 tarun3300

Is your goal to control malicious queries, as mentioned in this issue? A simple timeout is a very basic mechanism that can impact other scenarios as well such as a slow network that have nothing to do with that concern.

GraphQL Java provides Query Complexity and Query Depth instrumentations. We think that's a better mechanism to control malicious queries. There is a Boot issue spring-projects/spring-graphql#469 for an enhancement in that area, and we can consider improved support here as well if there are concrete suggestions.

That said if you really wanted, you can apply a timeout through an interceptor. For example:

@Bean
WebGraphQlInterceptor interceptor() {
	return (request, chain) -> chain.next(request)
			.timeout(Duration.ofSeconds(2), Mono.error(new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE)));
}

This ends the response and should also cancel GraphQL request processing. However, note there is currently an issue that prevents it from working. You can watch https://github.com/reactor/reactor-core/issues/3138, and when that's fixed, it should work as expected.

rstoyanchev avatar Aug 04 '22 15:08 rstoyanchev

If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.

spring-projects-issues avatar Aug 11 '22 15:08 spring-projects-issues

Closing due to lack of requested feedback. If you would like us to look at this issue, please provide the requested information and we will re-open the issue.

spring-projects-issues avatar Aug 18 '22 15:08 spring-projects-issues

@rstoyanchev Imho, special property for global request timeout is the first thing anyone would try to lookup. Reactor way is very non-obvious. Could it be reasonable to built in and autoconfigure such an interceptor based on corresponding property?

turboezh avatar Oct 25 '23 16:10 turboezh

Hello @rstoyanchev, is there any update on this. I have tried above solution but it is not working

amruta98 avatar Feb 16 '24 15:02 amruta98

My apologies, indeed the above doesn't work when data is fetched synchronously because blocking occurs as part of the initial call while composing the reactive chain.

I've confirmed the following works:

public class RequestTimeoutInterceptor implements WebGraphQlInterceptor {

	private final Duration timeout;

	public RequestTimeoutInterceptor(Duration timeout) {
		this.timeout = timeout;
	}

	@Override
	public Mono<WebGraphQlResponse> intercept(WebGraphQlRequest request, Chain chain) {
		return chain.next(request)
				.subscribeOn(Schedulers.boundedElastic())
				.timeout(this.timeout, Mono.error(new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE)));
	}

}

I am re-opening this in order to make such an interceptor available. Using an interceptor has the advantage of targeting GraphQL request handling vs any other HTTP request.

rstoyanchev avatar Feb 16 '24 17:02 rstoyanchev

Hi @rstoyanchev, Thanks for reopening this issue. I tried the interceptor option but it didn't work. The query that I am working on has following structure:

query getData {
  appData {
    name
    appConfig{
      name
      id
    }
  }
}

Now appConfig resolver's batch loader is taking too much time to fetch data because of which whole query gets aborted after 30 seconds with AsyncRequestTimeoutException.

amruta98 avatar Feb 20 '24 06:02 amruta98

@amruta98 can you double check that the interceptor is registered and is getting invoked, e.g. by adding a log message or putting a breakpoint? It should be as simple as declaring it as a bean but just to be sure.

rstoyanchev avatar Feb 20 '24 06:02 rstoyanchev

@rstoyanchev I checked it by adding logs and by debugging. It is getting invoked but not working as expected

amruta98 avatar Feb 20 '24 13:02 amruta98

If you can create an isolated sample, I will have a look.

rstoyanchev avatar Feb 20 '24 14:02 rstoyanchev

@rstoyanchev Actually I already had Interceptor in my project for passing custom context and I modified that with timeout related code. Please check below code

@Component
@EnableAutoConfiguration
public class DemoInterceptor implements  WebGraphQlInterceptor{
 @Override
    public Mono<WebGraphQlResponse> intercept(WebGraphQlRequest request, WebGraphQlInterceptor.Chain chain) {
       
       request.configureExecutionInput((executionInput, builder) ->
                builder.graphQLContext(context -> {
                            CustomContext customContext = createCustomContext(request);
                            context.put("customContext", customContext);
                        }).build());
        return chain.next(request).subscribeOn(Schedulers.boundedElastic())
				.timeout(Duration.ofMillis(60000), Mono.error(new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE)));
    }
}

amruta98 avatar Feb 21 '24 12:02 amruta98

@amruta98 What do you mean? Is your custom interceptor working? If it's not, can you share a minimal sample?

bclozel avatar Feb 22 '24 09:02 bclozel

Hi @bclozel , My custom interceptor is working, I am able to fetch custom context in my api also, but the changes related to timeout are not working as expected. .subscribeOn(Schedulers.boundedElastic()) .timeout(Duration.ofMillis(60000), Mono.error(new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE)))

amruta98 avatar Feb 22 '24 09:02 amruta98

ok then please provide a minimal sample.

bclozel avatar Feb 22 '24 09:02 bclozel

@amruta98 as mentioned previously, I have tested the above. There must be some other reason why it doesn't work for you. Are you able to provide a small sample, e.g. via https://start.spring.io that demonstrates the issue?

rstoyanchev avatar Apr 11 '24 12:04 rstoyanchev