3434import org .apache .http .client .utils .HttpClientUtils ;
3535import org .apache .http .impl .client .CloseableHttpClient ;
3636import org .apache .http .impl .client .HttpClientBuilder ;
37+ import org .apache .http .impl .conn .PoolingHttpClientConnectionManager ;
3738import org .apache .http .protocol .HttpContext ;
3839import org .eclipse .rdf4j .http .client .util .HttpClientBuilders ;
3940import org .slf4j .Logger ;
@@ -53,6 +54,163 @@ public class SharedHttpClientSessionManager implements HttpClientSessionManager,
5354
5455 private static final AtomicLong threadCount = new AtomicLong ();
5556
57+ // System property constants for regular timeouts
58+
59+ /**
60+ * Configurable system property {@code org.eclipse.rdf4j.client.http.connectionTimeout} for specifying the HTTP
61+ * connection timeout in milliseconds for general use. Default is 1 hour.
62+ *
63+ * <p>
64+ * The connection timeout determines the maximum time the client will wait to establish a TCP connection to the
65+ * server. A default of 1 hour is set to allow for potential network delays without causing unnecessary timeouts.
66+ * </p>
67+ */
68+ public static final String CONNECTION_TIMEOUT_PROPERTY = "org.eclipse.rdf4j.client.http.connectionTimeout" ;
69+
70+ /**
71+ * Configurable system property {@code org.eclipse.rdf4j.client.http.connectionRequestTimeout} for specifying the
72+ * HTTP connection request timeout in milliseconds for general use. Default is 10 days.
73+ *
74+ * <p>
75+ * The connection request timeout defines how long the client will wait for a connection from the connection pool. A
76+ * longer timeout is acceptable here since operations like large file uploads may need to wait for an available
77+ * connection.
78+ * </p>
79+ */
80+ public static final String CONNECTION_REQUEST_TIMEOUT_PROPERTY = "org.eclipse.rdf4j.client.http.connectionRequestTimeout" ;
81+
82+ /**
83+ * Configurable system property {@code org.eclipse.rdf4j.client.http.socketTimeout} for specifying the HTTP socket
84+ * timeout in milliseconds for general use. Default is 10 days.
85+ *
86+ * <p>
87+ * The socket timeout controls the maximum period of inactivity between data packets during data transfer. A longer
88+ * timeout is appropriate for large data transfers, ensuring that operations are not interrupted prematurely.
89+ * </p>
90+ */
91+ public static final String SOCKET_TIMEOUT_PROPERTY = "org.eclipse.rdf4j.client.http.socketTimeout" ;
92+
93+ // System property constants for SPARQL SERVICE timeouts
94+
95+ /**
96+ * Configurable system property {@code org.eclipse.rdf4j.client.sparql.http.connectionTimeout} for specifying the
97+ * HTTP connection timeout in milliseconds when used in SPARQL SERVICE calls. Default is 10 minutes.
98+ *
99+ * <p>
100+ * A shorter connection timeout is set for SPARQL SERVICE calls to quickly detect unresponsive endpoints in
101+ * federated queries, improving overall query performance by avoiding long waits for unreachable servers.
102+ * </p>
103+ */
104+ public static final String SPARQL_CONNECTION_TIMEOUT_PROPERTY = "org.eclipse.rdf4j.client.sparql.http.connectionTimeout" ;
105+
106+ /**
107+ * Configurable system property {@code org.eclipse.rdf4j.client.sparql.http.connectionRequestTimeout} for specifying
108+ * the HTTP connection request timeout in milliseconds when used in SPARQL SERVICE calls. Default is 6 hours.
109+ *
110+ * <p>
111+ * This timeout controls how long the client waits for a connection from the pool when making SPARQL SERVICE calls.
112+ * A shorter timeout than general use ensures that queries fail fast if resources are constrained, maintaining
113+ * responsiveness.
114+ * </p>
115+ */
116+ public static final String SPARQL_CONNECTION_REQUEST_TIMEOUT_PROPERTY = "org.eclipse.rdf4j.client.sparql.http.connectionRequestTimeout" ;
117+
118+ /**
119+ * Configurable system property {@code org.eclipse.rdf4j.client.sparql.http.socketTimeout} for specifying the HTTP
120+ * socket timeout in milliseconds when used in SPARQL SERVICE calls. Default is 6 hours.
121+ *
122+ * <p>
123+ * The socket timeout for SPARQL SERVICE calls is set to a shorter duration to detect unresponsive servers during
124+ * data transfer, ensuring that the client does not wait indefinitely for data that may never arrive.
125+ * </p>
126+ */
127+ public static final String SPARQL_SOCKET_TIMEOUT_PROPERTY = "org.eclipse.rdf4j.client.sparql.http.socketTimeout" ;
128+
129+ // Default timeout values for general use
130+
131+ /**
132+ * Default HTTP connection timeout in milliseconds for general use. Set to 1 hour.
133+ */
134+ public static final int DEFAULT_CONNECTION_TIMEOUT = 60 * 60 * 1000 ; // 1 hour
135+
136+ /**
137+ * Default HTTP connection request timeout in milliseconds for general use. Set to 10 days.
138+ */
139+ public static final int DEFAULT_CONNECTION_REQUEST_TIMEOUT = 10 * 24 * 60 * 60 * 1000 ; // 10 days
140+
141+ /**
142+ * Default HTTP socket timeout in milliseconds for general use. Set to 10 days.
143+ */
144+ public static final int DEFAULT_SOCKET_TIMEOUT = 10 * 24 * 60 * 60 * 1000 ; // 10 days
145+
146+ // Default timeout values for SPARQL SERVICE calls
147+
148+ /**
149+ * Default HTTP connection timeout in milliseconds for SPARQL SERVICE calls. Set to 10 minutes.
150+ */
151+ public static final int DEFAULT_SPARQL_CONNECTION_TIMEOUT = 10 * 60 * 1000 ; // 10 minutes
152+
153+ /**
154+ * Default HTTP connection request timeout in milliseconds for SPARQL SERVICE calls. Set to 6 hours.
155+ */
156+ public static final int DEFAULT_SPARQL_CONNECTION_REQUEST_TIMEOUT = 6 * 60 * 60 * 1000 ; // 6 hours
157+
158+ /**
159+ * Default HTTP socket timeout in milliseconds for SPARQL SERVICE calls. Set to 6 hours.
160+ */
161+ public static final int DEFAULT_SPARQL_SOCKET_TIMEOUT = 6 * 60 * 60 * 1000 ; // 6 hours
162+
163+ // Timeout values as read from system properties or defaults
164+
165+ /**
166+ * HTTP connection timeout in milliseconds for general use.
167+ */
168+ public static final int CONNECTION_TIMEOUT = Integer .parseInt (
169+ System .getProperty (CONNECTION_TIMEOUT_PROPERTY , String .valueOf (DEFAULT_CONNECTION_TIMEOUT ))
170+ );
171+
172+ /**
173+ * HTTP connection request timeout in milliseconds for general use.
174+ */
175+ public static final int CONNECTION_REQUEST_TIMEOUT = Integer .parseInt (
176+ System .getProperty (CONNECTION_REQUEST_TIMEOUT_PROPERTY , String .valueOf (DEFAULT_CONNECTION_REQUEST_TIMEOUT ))
177+ );
178+
179+ /**
180+ * HTTP socket timeout in milliseconds for general use.
181+ */
182+ public static final int SOCKET_TIMEOUT = Integer .parseInt (
183+ System .getProperty (SOCKET_TIMEOUT_PROPERTY , String .valueOf (DEFAULT_SOCKET_TIMEOUT ))
184+ );
185+
186+ /**
187+ * HTTP connection timeout in milliseconds for SPARQL SERVICE calls.
188+ */
189+ public static final int SPARQL_CONNECTION_TIMEOUT = Integer .parseInt (
190+ System .getProperty (SPARQL_CONNECTION_TIMEOUT_PROPERTY , String .valueOf (DEFAULT_SPARQL_CONNECTION_TIMEOUT ))
191+ );
192+
193+ /**
194+ * HTTP connection request timeout in milliseconds for SPARQL SERVICE calls.
195+ */
196+ public static final int SPARQL_CONNECTION_REQUEST_TIMEOUT = Integer .parseInt (
197+ System .getProperty (SPARQL_CONNECTION_REQUEST_TIMEOUT_PROPERTY ,
198+ String .valueOf (DEFAULT_SPARQL_CONNECTION_REQUEST_TIMEOUT ))
199+ );
200+
201+ /**
202+ * HTTP socket timeout in milliseconds for SPARQL SERVICE calls.
203+ */
204+ public static final int SPARQL_SOCKET_TIMEOUT = Integer .parseInt (
205+ System .getProperty (SPARQL_SOCKET_TIMEOUT_PROPERTY , String .valueOf (DEFAULT_SPARQL_SOCKET_TIMEOUT ))
206+ );
207+
208+ // Variables for the currently used timeouts
209+
210+ private int currentConnectionTimeout = CONNECTION_TIMEOUT ;
211+ private int currentConnectionRequestTimeout = CONNECTION_REQUEST_TIMEOUT ;
212+ private int currentSocketTimeout = SOCKET_TIMEOUT ;
213+
56214 private final Logger logger = LoggerFactory .getLogger (SharedHttpClientSessionManager .class );
57215
58216 /**
@@ -267,6 +425,7 @@ public void close() {
267425 @ Override
268426 public void shutDown () {
269427 try {
428+ // Close open sessions
270429 openSessions .keySet ().forEach (session -> {
271430 try {
272431 session .close ();
@@ -280,11 +439,11 @@ public void shutDown() {
280439 HttpClientUtils .closeQuietly (toCloseDependentClient );
281440 }
282441 } finally {
442+ // Shutdown the executor
283443 try {
284444 executor .shutdown ();
285445 executor .awaitTermination (10 , TimeUnit .SECONDS );
286446 } catch (InterruptedException e ) {
287- // Preserve the interrupt status so others can check it as necessary
288447 Thread .currentThread ().interrupt ();
289448 } finally {
290449 if (!executor .isTerminated ()) {
@@ -314,17 +473,67 @@ protected final ExecutorService getExecutorService() {
314473 }
315474
316475 private CloseableHttpClient createHttpClient () {
476+
317477 HttpClientBuilder nextHttpClientBuilder = httpClientBuilder ;
318478 if (nextHttpClientBuilder != null ) {
319479 return nextHttpClientBuilder .build ();
320480 }
321481
482+ RequestConfig requestConfig = getDefaultRequestConfig ();
483+
322484 return HttpClientBuilder .create ()
323485 .evictExpiredConnections ()
486+ .evictIdleConnections (30 , TimeUnit .MINUTES )
324487 .setRetryHandler (retryHandlerStale )
325488 .setServiceUnavailableRetryStrategy (serviceUnavailableRetryHandler )
326489 .useSystemProperties ()
327- .setDefaultRequestConfig (RequestConfig .custom ().setCookieSpec (CookieSpecs .STANDARD ).build ())
490+ .setDefaultRequestConfig (requestConfig )
491+ .build ();
492+ }
493+
494+ /**
495+ * Returns the default {@link RequestConfig} using the currently set timeout values.
496+ *
497+ * @return a configured {@link RequestConfig} with the current timeouts.
498+ */
499+ public RequestConfig getDefaultRequestConfig () {
500+ return RequestConfig .custom ()
501+ .setConnectTimeout (currentConnectionTimeout )
502+ .setConnectionRequestTimeout (currentConnectionRequestTimeout )
503+ .setSocketTimeout (currentSocketTimeout )
504+ .setCookieSpec (CookieSpecs .STANDARD )
328505 .build ();
329506 }
507+
508+ /**
509+ * Switches the current timeout settings to use the SPARQL-specific timeouts. This method should be called when
510+ * making SPARQL SERVICE calls to apply shorter timeout values.
511+ *
512+ * <p>
513+ * The SPARQL-specific timeouts are shorter to ensure that unresponsive or slow SPARQL endpoints do not cause long
514+ * delays in federated query processing. Quick detection of such issues improves the responsiveness and reliability
515+ * of SPARQL queries.
516+ * </p>
517+ */
518+ public void setDefaultSparqlServiceTimeouts () {
519+ this .currentConnectionTimeout = SPARQL_CONNECTION_TIMEOUT ;
520+ this .currentConnectionRequestTimeout = SPARQL_CONNECTION_REQUEST_TIMEOUT ;
521+ this .currentSocketTimeout = SPARQL_SOCKET_TIMEOUT ;
522+ }
523+
524+ /**
525+ * Resets the current timeout settings to the general timeouts. This method should be called to revert any changes
526+ * made by {@link #setDefaultSparqlServiceTimeouts()} and apply the general timeout values.
527+ *
528+ * <p>
529+ * The general timeouts are longer to accommodate operations that may take more time, such as large data transfers
530+ * or extensive processing, without causing premature timeouts.
531+ * </p>
532+ */
533+ public void setDefaultTimeouts () {
534+ this .currentConnectionTimeout = CONNECTION_TIMEOUT ;
535+ this .currentConnectionRequestTimeout = CONNECTION_REQUEST_TIMEOUT ;
536+ this .currentSocketTimeout = SOCKET_TIMEOUT ;
537+ }
538+
330539}
0 commit comments