Skip to content

Commit fe55058

Browse files
committed
GH-5723: propagate maxExecutionTime as per-request HTTP response timeout
When setMaxExecutionTime(int) is called on an HTTP-based query or update, the value is now applied as a per-request response timeout on the HTTP connection, enforced client-side in addition to being sent as a server-side hint. HttpRequest gains an optional responseTimeout field (Duration). Both SPARQLProtocolSession and RDF4JProtocolSession (which overrides getQueryMethod/getUpdateMethod) set this field when maxQueryTime > 0. The Apache HC5 client applies it via RequestConfig.copy(defaultRequestConfig).setResponseTimeout(), preserving factory-level defaults (connectionRequest Timeout, redirectsEnabled, cookieSpec) while overriding only the response timeout. The default RequestConfig is passed from the factory to the client at construction time. The JDK client applies it via HttpRequest.Builder#timeout(), with the per-request value taking precedence over the global socketTimeoutMs from RDF4JHttpClientConfig.
1 parent d44e42d commit fe55058

3 files changed

Lines changed: 41 additions & 4 deletions

File tree

core/http/client-apache5/src/main/java/org/eclipse/rdf4j/http/client/apache5/ApacheHC5RDF4JHttpClient.java

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,17 @@ public class ApacheHC5RDF4JHttpClient implements RDF4JHttpClient {
4747
*/
4848
private final int maxRetries408;
4949

50-
public ApacheHC5RDF4JHttpClient(CloseableHttpClient httpClient, int maxConnectionsPerRoute) {
50+
/**
51+
* Default request config used as the base when building per-request overrides, so that factory-level settings
52+
* (connectionRequestTimeout, redirectsEnabled, cookieSpec) are preserved.
53+
*/
54+
private final RequestConfig defaultRequestConfig;
55+
56+
public ApacheHC5RDF4JHttpClient(CloseableHttpClient httpClient, int maxConnectionsPerRoute,
57+
RequestConfig defaultRequestConfig) {
5158
this.httpClient = httpClient;
5259
this.maxRetries408 = maxConnectionsPerRoute + 1;
60+
this.defaultRequestConfig = defaultRequestConfig;
5361
}
5462

5563
@Override
@@ -103,10 +111,13 @@ private HttpUriRequestBase buildRequest(HttpRequest request) throws IOException
103111
hcRequest.addHeader(header.getName(), header.getValue());
104112
}
105113

106-
// Apply per-request response timeout if present
114+
// Apply per-request response timeout if present, copying from the default config so that
115+
// factory-level settings (connectionRequestTimeout, redirectsEnabled, cookieSpec) are preserved.
107116
request.getResponseTimeout()
108117
.ifPresent(timeout -> hcRequest
109-
.setConfig(RequestConfig.custom().setResponseTimeout(Timeout.of(timeout)).build()));
118+
.setConfig(RequestConfig.copy(defaultRequestConfig)
119+
.setResponseTimeout(Timeout.of(timeout))
120+
.build()));
110121

111122
// Set body. Repeatable (byte-backed) bodies use ByteArrayEntity so that Apache HC5's
112123
// RedirectExec can follow redirects and the 408 retry loop can re-send the request.

core/http/client-apache5/src/main/java/org/eclipse/rdf4j/http/client/apache5/ApacheHC5RDF4JHttpClientFactory.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ public RDF4JHttpClient create(RDF4JHttpClientConfig config) {
136136
}
137137

138138
CloseableHttpClient httpClient = buildHttpClient(builder, config);
139-
return new ApacheHC5RDF4JHttpClient(httpClient, config.getMaxConnectionsPerRoute());
139+
return new ApacheHC5RDF4JHttpClient(httpClient, config.getMaxConnectionsPerRoute(), requestConfig);
140140
}
141141

142142
/**

core/http/client/src/test/java/org/eclipse/rdf4j/http/client/SPARQLProtocolSessionTest.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,32 @@ public void getQueryMethod_noResponseTimeout_whenMaxQueryTimeIsZero(String facto
327327
assertThat(request.getResponseTimeout()).isEmpty();
328328
}
329329

330+
@ParameterizedTest(name = "[{0}]")
331+
@MethodSource("httpClientFactories")
332+
public void getUpdateMethod_setsResponseTimeout_whenMaxQueryTimeIsPositive(String factoryName) {
333+
this.factoryName = factoryName;
334+
sparqlSession = createProtocolSession();
335+
336+
HttpRequest request = sparqlSession.getUpdateMethod(
337+
QueryLanguage.SPARQL, "INSERT DATA { <urn:s> <urn:p> <urn:o> }", null, null, true, 30);
338+
339+
assertThat(request.getResponseTimeout())
340+
.isPresent()
341+
.hasValue(Duration.ofSeconds(30));
342+
}
343+
344+
@ParameterizedTest(name = "[{0}]")
345+
@MethodSource("httpClientFactories")
346+
public void getUpdateMethod_noResponseTimeout_whenMaxQueryTimeIsZero(String factoryName) {
347+
this.factoryName = factoryName;
348+
sparqlSession = createProtocolSession();
349+
350+
HttpRequest request = sparqlSession.getUpdateMethod(
351+
QueryLanguage.SPARQL, "INSERT DATA { <urn:s> <urn:p> <urn:o> }", null, null, true, 0);
352+
353+
assertThat(request.getResponseTimeout()).isEmpty();
354+
}
355+
330356
@Test
331357
public void getContentTypeSerialisationTest() {
332358
{

0 commit comments

Comments
 (0)