Skip to content

Commit 197485a

Browse files
committed
Merge branch 'master' into develop
2 parents d33e785 + 9b2fe8c commit 197485a

6 files changed

Lines changed: 125 additions & 64 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## [5.5.4] - 2026-06-29
2+
### Fixed
3+
- HTTP client connection-pool exhaustion: the pooled clients had no socket/read timeout (Apache default `SO_TIMEOUT` = 0 = infinite), so a stalled backend read held its leased connection forever and the route eventually pinned at max, wedging the listener. Added socket timeout, connect timeout, connection time-to-live and validate-after-inactivity to the pooled clients, configurable via the `CLIENT_SOCKET_TIMEOUT`, `CLIENT_CONNECT_TIMEOUT`, `CLIENT_CONNECTION_TIME_TO_LIVE` and `CLIENT_VALIDATE_AFTER_INACTIVITY` env vars (`CATALINA_OPTS` system properties), with image defaults in the `Dockerfile`
4+
- `SignUp`: PublicKey/Agent/Authorization client `Response`s are now closed on all code paths (connection leak on the signup path)
5+
16
## [5.5.3] - 2026-06-09
27
### Changed
38
- Dependency hygiene: exclude duplicate `jakarta.json` from `jena-arq`, align `slf4j-reload4j` to 2.0.17, drop unused `tomcat-coyote`

Dockerfile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,14 @@ ENV MAX_REQUEST_RETRIES=3
111111

112112
ENV CONNECTION_REQUEST_TIMEOUT=30000
113113

114+
ENV CLIENT_SOCKET_TIMEOUT=120000
115+
116+
ENV CLIENT_CONNECT_TIMEOUT=10000
117+
118+
ENV CLIENT_CONNECTION_TIME_TO_LIVE=300000
119+
120+
ENV CLIENT_VALIDATE_AFTER_INACTIVITY=10000
121+
114122
ENV IMPORT_KEEPALIVE=
115123

116124
ENV MAX_IMPORT_THREADS=10

platform/entrypoint.sh

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1080,6 +1080,22 @@ if [ -n "$CONNECTION_REQUEST_TIMEOUT" ]; then
10801080
export CATALINA_OPTS="$CATALINA_OPTS -Dcom.atomgraph.linkeddatahub.connectionRequestTimeout=$CONNECTION_REQUEST_TIMEOUT"
10811081
fi
10821082

1083+
if [ -n "$CLIENT_SOCKET_TIMEOUT" ]; then
1084+
export CATALINA_OPTS="$CATALINA_OPTS -Dcom.atomgraph.linkeddatahub.socketTimeout=$CLIENT_SOCKET_TIMEOUT"
1085+
fi
1086+
1087+
if [ -n "$CLIENT_CONNECT_TIMEOUT" ]; then
1088+
export CATALINA_OPTS="$CATALINA_OPTS -Dcom.atomgraph.linkeddatahub.connectTimeout=$CLIENT_CONNECT_TIMEOUT"
1089+
fi
1090+
1091+
if [ -n "$CLIENT_CONNECTION_TIME_TO_LIVE" ]; then
1092+
export CATALINA_OPTS="$CATALINA_OPTS -Dcom.atomgraph.linkeddatahub.connectionTimeToLive=$CLIENT_CONNECTION_TIME_TO_LIVE"
1093+
fi
1094+
1095+
if [ -n "$CLIENT_VALIDATE_AFTER_INACTIVITY" ]; then
1096+
export CATALINA_OPTS="$CATALINA_OPTS -Dcom.atomgraph.linkeddatahub.validateAfterInactivity=$CLIENT_VALIDATE_AFTER_INACTIVITY"
1097+
fi
1098+
10831099
if [ -n "$MAX_CONTENT_LENGTH" ]; then
10841100
MAX_CONTENT_LENGTH_PARAM="--stringparam ldhc:maxContentLength '$MAX_CONTENT_LENGTH' "
10851101
fi

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
<url>https://github.com/AtomGraph/LinkedDataHub</url>
4747
<connection>scm:git:git://github.com/AtomGraph/LinkedDataHub.git</connection>
4848
<developerConnection>scm:git:git@github.com:AtomGraph/LinkedDataHub.git</developerConnection>
49-
<tag>linkeddatahub-2.1.1</tag>
49+
<tag>linkeddatahub-5.5.4</tag>
5050
</scm>
5151

5252
<repositories>

src/main/java/com/atomgraph/linkeddatahub/Application.java

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,10 @@ public Application(@Context ServletConfig servletConfig) throws URISyntaxExcepti
357357
servletConfig.getServletContext().getInitParameter(LDHC.maxRequestRetries.getURI()) != null ? Integer.valueOf(servletConfig.getServletContext().getInitParameter(LDHC.maxRequestRetries.getURI())) : null,
358358
System.getProperty("com.atomgraph.linkeddatahub.connectionRequestTimeout") != null ? Integer.valueOf(System.getProperty("com.atomgraph.linkeddatahub.connectionRequestTimeout")) :
359359
servletConfig.getServletContext().getInitParameter(LDHC.connectionRequestTimeout.getURI()) != null ? Integer.valueOf(servletConfig.getServletContext().getInitParameter(LDHC.connectionRequestTimeout.getURI())) : null,
360+
System.getProperty("com.atomgraph.linkeddatahub.socketTimeout") != null ? Integer.valueOf(System.getProperty("com.atomgraph.linkeddatahub.socketTimeout")) : null,
361+
System.getProperty("com.atomgraph.linkeddatahub.connectTimeout") != null ? Integer.valueOf(System.getProperty("com.atomgraph.linkeddatahub.connectTimeout")) : null,
362+
System.getProperty("com.atomgraph.linkeddatahub.connectionTimeToLive") != null ? Long.valueOf(System.getProperty("com.atomgraph.linkeddatahub.connectionTimeToLive")) : null,
363+
System.getProperty("com.atomgraph.linkeddatahub.validateAfterInactivity") != null ? Integer.valueOf(System.getProperty("com.atomgraph.linkeddatahub.validateAfterInactivity")) : null,
360364
servletConfig.getServletContext().getInitParameter(LDHC.maxImportThreads.getURI()) != null ? Integer.valueOf(servletConfig.getServletContext().getInitParameter(LDHC.maxImportThreads.getURI())) : null,
361365
servletConfig.getServletContext().getInitParameter(LDHC.notificationAddress.getURI()) != null ? servletConfig.getServletContext().getInitParameter(LDHC.notificationAddress.getURI()) : null,
362366
servletConfig.getServletContext().getInitParameter(LDHC.supportedLanguages.getURI()) != null ? servletConfig.getServletContext().getInitParameter(LDHC.supportedLanguages.getURI()) : null,
@@ -444,7 +448,8 @@ public Application(final ServletConfig servletConfig, final MediaTypes mediaType
444448
final String baseURIString, final String proxyScheme, final String proxyHostname, final Integer proxyPort,
445449
final String uploadRootString, final boolean invalidateCache,
446450
final Integer cookieMaxAge, final boolean enableLinkedDataProxy, final boolean allowInternalUrls, final Integer maxContentLength,
447-
final Integer maxConnPerRoute, final Integer maxTotalConn, final Integer maxRequestRetries, final Integer connectionRequestTimeout, final Integer maxImportThreads,
451+
final Integer maxConnPerRoute, final Integer maxTotalConn, final Integer maxRequestRetries, final Integer connectionRequestTimeout,
452+
final Integer socketTimeout, final Integer connectTimeout, final Long connectionTimeToLive, final Integer validateAfterInactivity, final Integer maxImportThreads,
448453
final String notificationAddressString, final String supportedLanguageCodes, final boolean enableWebIDSignUp, final String oidcRefreshTokensPropertiesPath,
449454
final String frontendProxyString, final String backendProxyAdminString, final String backendProxyEndUserString,
450455
final String mailUser, final String mailPassword, final String smtpHost, final String smtpPort,
@@ -699,10 +704,10 @@ public Application(final ServletConfig servletConfig, final MediaTypes mediaType
699704
trustStore.load(trustStoreInputStream, clientTrustStorePassword.toCharArray());
700705
}
701706

702-
client = getClient(keyStore, clientKeyStorePassword, trustStore, maxConnPerRoute, maxTotalConn, null, false, connectionRequestTimeout);
703-
externalClient = getClient(keyStore, clientKeyStorePassword, trustStore, maxConnPerRoute, maxTotalConn, null, false, connectionRequestTimeout);
704-
importClient = getClient(keyStore, clientKeyStorePassword, trustStore, maxConnPerRoute, maxTotalConn, maxRequestRetries, true, connectionRequestTimeout);
705-
noCertClient = getNoCertClient(trustStore, maxConnPerRoute, maxTotalConn, maxRequestRetries, connectionRequestTimeout);
707+
client = getClient(keyStore, clientKeyStorePassword, trustStore, maxConnPerRoute, maxTotalConn, null, false, connectionRequestTimeout, socketTimeout, connectTimeout, connectionTimeToLive, validateAfterInactivity);
708+
externalClient = getClient(keyStore, clientKeyStorePassword, trustStore, maxConnPerRoute, maxTotalConn, null, false, connectionRequestTimeout, socketTimeout, connectTimeout, connectionTimeToLive, validateAfterInactivity);
709+
importClient = getClient(keyStore, clientKeyStorePassword, trustStore, maxConnPerRoute, maxTotalConn, maxRequestRetries, true, connectionRequestTimeout, socketTimeout, connectTimeout, connectionTimeToLive, validateAfterInactivity);
710+
noCertClient = getNoCertClient(trustStore, maxConnPerRoute, maxTotalConn, maxRequestRetries, connectionRequestTimeout, socketTimeout, connectTimeout, connectionTimeToLive, validateAfterInactivity);
706711

707712
if (maxContentLength != null)
708713
{
@@ -1487,21 +1492,26 @@ public void submitImport(RDFImport rdfImport, com.atomgraph.linkeddatahub.apps.m
14871492

14881493
/**
14891494
* Builds JAX-RS client instance from given configuration.
1490-
*
1495+
*
14911496
* @param keyStore keystore
14921497
* @param keyStorePassword keystore password
14931498
* @param trustStore truststore
14941499
* @param maxConnPerRoute max connections per route
14951500
* @param maxTotalConn max total connections
14961501
* @param maxRequestRetries maximum number of times that the HTTP client will retry a request
14971502
* @param buffered true if request entity should be buffered
1503+
* @param connectionRequestTimeout timeout in milliseconds to wait for a connection lease from the pool (null to leave unset)
1504+
* @param socketTimeout socket (read) timeout in milliseconds (null to leave unset)
1505+
* @param connectTimeout connection (connect) timeout in milliseconds (null to leave unset)
1506+
* @param connectionTimeToLive time-to-live in milliseconds after which pooled connections are discarded (null to leave unset)
1507+
* @param validateAfterInactivity period of inactivity in milliseconds after which a pooled connection is revalidated before reuse (null to leave unset)
14981508
* @return client instance
14991509
* @throws NoSuchAlgorithmException SSL algorithm error
15001510
* @throws KeyStoreException keystore loading error
15011511
* @throws UnrecoverableKeyException key loading error
15021512
* @throws KeyManagementException key loading error
15031513
*/
1504-
public static Client getClient(KeyStore keyStore, String keyStorePassword, KeyStore trustStore, Integer maxConnPerRoute, Integer maxTotalConn, Integer maxRequestRetries, boolean buffered, Integer connectionRequestTimeout) throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException, KeyManagementException
1514+
public static Client getClient(KeyStore keyStore, String keyStorePassword, KeyStore trustStore, Integer maxConnPerRoute, Integer maxTotalConn, Integer maxRequestRetries, boolean buffered, Integer connectionRequestTimeout, Integer socketTimeout, Integer connectTimeout, Long connectionTimeToLive, Integer validateAfterInactivity) throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException, KeyManagementException
15051515
{
15061516
if (keyStore == null) throw new IllegalArgumentException("KeyStore cannot be null");
15071517
if (keyStorePassword == null) throw new IllegalArgumentException("KeyStore password string cannot be null");
@@ -1523,7 +1533,7 @@ public static Client getClient(KeyStore keyStore, String keyStorePassword, KeySt
15231533
register("http", new PlainConnectionSocketFactory()).
15241534
build();
15251535

1526-
PoolingHttpClientConnectionManager conman = new PoolingHttpClientConnectionManager(socketFactoryRegistry)
1536+
PoolingHttpClientConnectionManager conman = new PoolingHttpClientConnectionManager(socketFactoryRegistry, null, null, null, connectionTimeToLive != null ? connectionTimeToLive : -1L, TimeUnit.MILLISECONDS)
15271537
{
15281538

15291539
// https://github.com/eclipse-ee4j/jersey/issues/4449
@@ -1554,7 +1564,8 @@ public void releaseConnection(final HttpClientConnection managedConn, final Obje
15541564
};
15551565
if (maxConnPerRoute != null) conman.setDefaultMaxPerRoute(maxConnPerRoute);
15561566
if (maxTotalConn != null) conman.setMaxTotal(maxTotalConn);
1557-
1567+
if (validateAfterInactivity != null) conman.setValidateAfterInactivity(validateAfterInactivity);
1568+
15581569
ClientConfig config = new ClientConfig();
15591570
config.connectorProvider(new ApacheConnectorProvider());
15601571
config.register(MultiPartFeature.class);
@@ -1566,10 +1577,11 @@ public void releaseConnection(final HttpClientConnection managedConn, final Obje
15661577
config.property(ClientProperties.FOLLOW_REDIRECTS, true);
15671578
config.property(ClientProperties.REQUEST_ENTITY_PROCESSING, RequestEntityProcessing.BUFFERED); // https://stackoverflow.com/questions/42139436/jersey-client-throws-cannot-retry-request-with-a-non-repeatable-request-entity
15681579
config.property(ApacheClientProperties.CONNECTION_MANAGER, conman);
1569-
if (connectionRequestTimeout != null)
1570-
config.property(ApacheClientProperties.REQUEST_CONFIG, RequestConfig.custom().
1571-
setConnectionRequestTimeout(connectionRequestTimeout).
1572-
build());
1580+
RequestConfig.Builder requestConfig = RequestConfig.custom();
1581+
if (connectionRequestTimeout != null) requestConfig.setConnectionRequestTimeout(connectionRequestTimeout);
1582+
if (socketTimeout != null) requestConfig.setSocketTimeout(socketTimeout);
1583+
if (connectTimeout != null) requestConfig.setConnectTimeout(connectTimeout);
1584+
config.property(ApacheClientProperties.REQUEST_CONFIG, requestConfig.build());
15731585

15741586
if (maxRequestRetries != null)
15751587
config.property(ApacheClientProperties.RETRY_HANDLER, (HttpRequestRetryHandler) (IOException ex, int executionCount, HttpContext context) ->
@@ -1605,9 +1617,14 @@ public void releaseConnection(final HttpClientConnection managedConn, final Obje
16051617
* @param maxConnPerRoute max connections per route
16061618
* @param maxTotalConn max total connections
16071619
* @param maxRequestRetries maximum number of times that the HTTP client will retry a request
1620+
* @param connectionRequestTimeout timeout in milliseconds to wait for a connection lease from the pool (null to leave unset)
1621+
* @param socketTimeout socket (read) timeout in milliseconds (null to leave unset)
1622+
* @param connectTimeout connection (connect) timeout in milliseconds (null to leave unset)
1623+
* @param connectionTimeToLive time-to-live in milliseconds after which pooled connections are discarded (null to leave unset)
1624+
* @param validateAfterInactivity period of inactivity in milliseconds after which a pooled connection is revalidated before reuse (null to leave unset)
16081625
* @return client instance
16091626
*/
1610-
public static Client getNoCertClient(KeyStore trustStore, Integer maxConnPerRoute, Integer maxTotalConn, Integer maxRequestRetries, Integer connectionRequestTimeout)
1627+
public static Client getNoCertClient(KeyStore trustStore, Integer maxConnPerRoute, Integer maxTotalConn, Integer maxRequestRetries, Integer connectionRequestTimeout, Integer socketTimeout, Integer connectTimeout, Long connectionTimeToLive, Integer validateAfterInactivity)
16111628
{
16121629
try
16131630
{
@@ -1623,11 +1640,11 @@ public static Client getNoCertClient(KeyStore trustStore, Integer maxConnPerRout
16231640
register("http", new PlainConnectionSocketFactory()).
16241641
build();
16251642

1626-
PoolingHttpClientConnectionManager conman = new PoolingHttpClientConnectionManager(socketFactoryRegistry)
1643+
PoolingHttpClientConnectionManager conman = new PoolingHttpClientConnectionManager(socketFactoryRegistry, null, null, null, connectionTimeToLive != null ? connectionTimeToLive : -1L, TimeUnit.MILLISECONDS)
16271644
{
16281645

16291646
// https://github.com/eclipse-ee4j/jersey/issues/4449
1630-
1647+
16311648
@Override
16321649
public void close()
16331650
{
@@ -1654,6 +1671,7 @@ public void releaseConnection(final HttpClientConnection managedConn, final Obje
16541671
};
16551672
if (maxConnPerRoute != null) conman.setDefaultMaxPerRoute(maxConnPerRoute);
16561673
if (maxTotalConn != null) conman.setMaxTotal(maxTotalConn);
1674+
if (validateAfterInactivity != null) conman.setValidateAfterInactivity(validateAfterInactivity);
16571675

16581676
ClientConfig config = new ClientConfig();
16591677
config.connectorProvider(new ApacheConnectorProvider());
@@ -1666,10 +1684,11 @@ public void releaseConnection(final HttpClientConnection managedConn, final Obje
16661684
config.property(ClientProperties.FOLLOW_REDIRECTS, true);
16671685
config.property(ClientProperties.REQUEST_ENTITY_PROCESSING, RequestEntityProcessing.BUFFERED); // https://stackoverflow.com/questions/42139436/jersey-client-throws-cannot-retry-request-with-a-non-repeatable-request-entity
16681686
config.property(ApacheClientProperties.CONNECTION_MANAGER, conman);
1669-
if (connectionRequestTimeout != null)
1670-
config.property(ApacheClientProperties.REQUEST_CONFIG, RequestConfig.custom().
1671-
setConnectionRequestTimeout(connectionRequestTimeout).
1672-
build());
1687+
RequestConfig.Builder requestConfig = RequestConfig.custom();
1688+
if (connectionRequestTimeout != null) requestConfig.setConnectionRequestTimeout(connectionRequestTimeout);
1689+
if (socketTimeout != null) requestConfig.setSocketTimeout(socketTimeout);
1690+
if (connectTimeout != null) requestConfig.setConnectTimeout(connectTimeout);
1691+
config.property(ApacheClientProperties.REQUEST_CONFIG, requestConfig.build());
16731692

16741693
if (maxRequestRetries != null)
16751694
config.property(ApacheClientProperties.RETRY_HANDLER, (HttpRequestRetryHandler) (IOException ex, int executionCount, HttpContext context) ->

0 commit comments

Comments
 (0)