Skip to content

Commit 9b2fe8c

Browse files
committed
Release version 5.5.4
2 parents 12e1bc9 + f1576c8 commit 9b2fe8c

6 files changed

Lines changed: 126 additions & 65 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: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
<groupId>com.atomgraph</groupId>
55
<artifactId>linkeddatahub</artifactId>
6-
<version>5.5.3</version>
6+
<version>5.5.4</version>
77
<packaging>${packaging.type}</packaging>
88

99
<name>AtomGraph LinkedDataHub</name>
@@ -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-5.5.3</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
@@ -358,6 +358,10 @@ public Application(@Context ServletConfig servletConfig) throws URISyntaxExcepti
358358
servletConfig.getServletContext().getInitParameter(LDHC.maxRequestRetries.getURI()) != null ? Integer.valueOf(servletConfig.getServletContext().getInitParameter(LDHC.maxRequestRetries.getURI())) : null,
359359
System.getProperty("com.atomgraph.linkeddatahub.connectionRequestTimeout") != null ? Integer.valueOf(System.getProperty("com.atomgraph.linkeddatahub.connectionRequestTimeout")) :
360360
servletConfig.getServletContext().getInitParameter(LDHC.connectionRequestTimeout.getURI()) != null ? Integer.valueOf(servletConfig.getServletContext().getInitParameter(LDHC.connectionRequestTimeout.getURI())) : null,
361+
System.getProperty("com.atomgraph.linkeddatahub.socketTimeout") != null ? Integer.valueOf(System.getProperty("com.atomgraph.linkeddatahub.socketTimeout")) : null,
362+
System.getProperty("com.atomgraph.linkeddatahub.connectTimeout") != null ? Integer.valueOf(System.getProperty("com.atomgraph.linkeddatahub.connectTimeout")) : null,
363+
System.getProperty("com.atomgraph.linkeddatahub.connectionTimeToLive") != null ? Long.valueOf(System.getProperty("com.atomgraph.linkeddatahub.connectionTimeToLive")) : null,
364+
System.getProperty("com.atomgraph.linkeddatahub.validateAfterInactivity") != null ? Integer.valueOf(System.getProperty("com.atomgraph.linkeddatahub.validateAfterInactivity")) : null,
361365
servletConfig.getServletContext().getInitParameter(LDHC.maxImportThreads.getURI()) != null ? Integer.valueOf(servletConfig.getServletContext().getInitParameter(LDHC.maxImportThreads.getURI())) : null,
362366
servletConfig.getServletContext().getInitParameter(LDHC.notificationAddress.getURI()) != null ? servletConfig.getServletContext().getInitParameter(LDHC.notificationAddress.getURI()) : null,
363367
servletConfig.getServletContext().getInitParameter(LDHC.supportedLanguages.getURI()) != null ? servletConfig.getServletContext().getInitParameter(LDHC.supportedLanguages.getURI()) : null,
@@ -445,7 +449,8 @@ public Application(final ServletConfig servletConfig, final MediaTypes mediaType
445449
final String baseURIString, final String proxyScheme, final String proxyHostname, final Integer proxyPort,
446450
final String uploadRootString, final boolean invalidateCache,
447451
final Integer cookieMaxAge, final boolean enableLinkedDataProxy, final boolean allowInternalUrls, final Integer maxContentLength,
448-
final Integer maxConnPerRoute, final Integer maxTotalConn, final Integer maxRequestRetries, final Integer connectionRequestTimeout, final Integer maxImportThreads,
452+
final Integer maxConnPerRoute, final Integer maxTotalConn, final Integer maxRequestRetries, final Integer connectionRequestTimeout,
453+
final Integer socketTimeout, final Integer connectTimeout, final Long connectionTimeToLive, final Integer validateAfterInactivity, final Integer maxImportThreads,
449454
final String notificationAddressString, final String supportedLanguageCodes, final boolean enableWebIDSignUp, final String oidcRefreshTokensPropertiesPath,
450455
final String frontendProxyString, final String backendProxyAdminString, final String backendProxyEndUserString,
451456
final String mailUser, final String mailPassword, final String smtpHost, final String smtpPort,
@@ -704,10 +709,10 @@ public Application(final ServletConfig servletConfig, final MediaTypes mediaType
704709
trustStore.load(trustStoreInputStream, clientTrustStorePassword.toCharArray());
705710
}
706711

707-
client = getClient(keyStore, clientKeyStorePassword, trustStore, maxConnPerRoute, maxTotalConn, null, false, connectionRequestTimeout);
708-
externalClient = getClient(keyStore, clientKeyStorePassword, trustStore, maxConnPerRoute, maxTotalConn, null, false, connectionRequestTimeout);
709-
importClient = getClient(keyStore, clientKeyStorePassword, trustStore, maxConnPerRoute, maxTotalConn, maxRequestRetries, true, connectionRequestTimeout);
710-
noCertClient = getNoCertClient(trustStore, maxConnPerRoute, maxTotalConn, maxRequestRetries, connectionRequestTimeout);
712+
client = getClient(keyStore, clientKeyStorePassword, trustStore, maxConnPerRoute, maxTotalConn, null, false, connectionRequestTimeout, socketTimeout, connectTimeout, connectionTimeToLive, validateAfterInactivity);
713+
externalClient = getClient(keyStore, clientKeyStorePassword, trustStore, maxConnPerRoute, maxTotalConn, null, false, connectionRequestTimeout, socketTimeout, connectTimeout, connectionTimeToLive, validateAfterInactivity);
714+
importClient = getClient(keyStore, clientKeyStorePassword, trustStore, maxConnPerRoute, maxTotalConn, maxRequestRetries, true, connectionRequestTimeout, socketTimeout, connectTimeout, connectionTimeToLive, validateAfterInactivity);
715+
noCertClient = getNoCertClient(trustStore, maxConnPerRoute, maxTotalConn, maxRequestRetries, connectionRequestTimeout, socketTimeout, connectTimeout, connectionTimeToLive, validateAfterInactivity);
711716

712717
if (maxContentLength != null)
713718
{
@@ -1507,21 +1512,26 @@ public void submitImport(RDFImport rdfImport, com.atomgraph.linkeddatahub.apps.m
15071512

15081513
/**
15091514
* Builds JAX-RS client instance from given configuration.
1510-
*
1515+
*
15111516
* @param keyStore keystore
15121517
* @param keyStorePassword keystore password
15131518
* @param trustStore truststore
15141519
* @param maxConnPerRoute max connections per route
15151520
* @param maxTotalConn max total connections
15161521
* @param maxRequestRetries maximum number of times that the HTTP client will retry a request
15171522
* @param buffered true if request entity should be buffered
1523+
* @param connectionRequestTimeout timeout in milliseconds to wait for a connection lease from the pool (null to leave unset)
1524+
* @param socketTimeout socket (read) timeout in milliseconds (null to leave unset)
1525+
* @param connectTimeout connection (connect) timeout in milliseconds (null to leave unset)
1526+
* @param connectionTimeToLive time-to-live in milliseconds after which pooled connections are discarded (null to leave unset)
1527+
* @param validateAfterInactivity period of inactivity in milliseconds after which a pooled connection is revalidated before reuse (null to leave unset)
15181528
* @return client instance
15191529
* @throws NoSuchAlgorithmException SSL algorithm error
15201530
* @throws KeyStoreException keystore loading error
15211531
* @throws UnrecoverableKeyException key loading error
15221532
* @throws KeyManagementException key loading error
15231533
*/
1524-
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
1534+
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
15251535
{
15261536
if (keyStore == null) throw new IllegalArgumentException("KeyStore cannot be null");
15271537
if (keyStorePassword == null) throw new IllegalArgumentException("KeyStore password string cannot be null");
@@ -1543,7 +1553,7 @@ public static Client getClient(KeyStore keyStore, String keyStorePassword, KeySt
15431553
register("http", new PlainConnectionSocketFactory()).
15441554
build();
15451555

1546-
PoolingHttpClientConnectionManager conman = new PoolingHttpClientConnectionManager(socketFactoryRegistry)
1556+
PoolingHttpClientConnectionManager conman = new PoolingHttpClientConnectionManager(socketFactoryRegistry, null, null, null, connectionTimeToLive != null ? connectionTimeToLive : -1L, TimeUnit.MILLISECONDS)
15471557
{
15481558

15491559
// https://github.com/eclipse-ee4j/jersey/issues/4449
@@ -1574,7 +1584,8 @@ public void releaseConnection(final HttpClientConnection managedConn, final Obje
15741584
};
15751585
if (maxConnPerRoute != null) conman.setDefaultMaxPerRoute(maxConnPerRoute);
15761586
if (maxTotalConn != null) conman.setMaxTotal(maxTotalConn);
1577-
1587+
if (validateAfterInactivity != null) conman.setValidateAfterInactivity(validateAfterInactivity);
1588+
15781589
ClientConfig config = new ClientConfig();
15791590
config.connectorProvider(new ApacheConnectorProvider());
15801591
config.register(MultiPartFeature.class);
@@ -1586,10 +1597,11 @@ public void releaseConnection(final HttpClientConnection managedConn, final Obje
15861597
config.property(ClientProperties.FOLLOW_REDIRECTS, true);
15871598
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
15881599
config.property(ApacheClientProperties.CONNECTION_MANAGER, conman);
1589-
if (connectionRequestTimeout != null)
1590-
config.property(ApacheClientProperties.REQUEST_CONFIG, RequestConfig.custom().
1591-
setConnectionRequestTimeout(connectionRequestTimeout).
1592-
build());
1600+
RequestConfig.Builder requestConfig = RequestConfig.custom();
1601+
if (connectionRequestTimeout != null) requestConfig.setConnectionRequestTimeout(connectionRequestTimeout);
1602+
if (socketTimeout != null) requestConfig.setSocketTimeout(socketTimeout);
1603+
if (connectTimeout != null) requestConfig.setConnectTimeout(connectTimeout);
1604+
config.property(ApacheClientProperties.REQUEST_CONFIG, requestConfig.build());
15931605

15941606
if (maxRequestRetries != null)
15951607
config.property(ApacheClientProperties.RETRY_HANDLER, (HttpRequestRetryHandler) (IOException ex, int executionCount, HttpContext context) ->
@@ -1625,9 +1637,14 @@ public void releaseConnection(final HttpClientConnection managedConn, final Obje
16251637
* @param maxConnPerRoute max connections per route
16261638
* @param maxTotalConn max total connections
16271639
* @param maxRequestRetries maximum number of times that the HTTP client will retry a request
1640+
* @param connectionRequestTimeout timeout in milliseconds to wait for a connection lease from the pool (null to leave unset)
1641+
* @param socketTimeout socket (read) timeout in milliseconds (null to leave unset)
1642+
* @param connectTimeout connection (connect) timeout in milliseconds (null to leave unset)
1643+
* @param connectionTimeToLive time-to-live in milliseconds after which pooled connections are discarded (null to leave unset)
1644+
* @param validateAfterInactivity period of inactivity in milliseconds after which a pooled connection is revalidated before reuse (null to leave unset)
16281645
* @return client instance
16291646
*/
1630-
public static Client getNoCertClient(KeyStore trustStore, Integer maxConnPerRoute, Integer maxTotalConn, Integer maxRequestRetries, Integer connectionRequestTimeout)
1647+
public static Client getNoCertClient(KeyStore trustStore, Integer maxConnPerRoute, Integer maxTotalConn, Integer maxRequestRetries, Integer connectionRequestTimeout, Integer socketTimeout, Integer connectTimeout, Long connectionTimeToLive, Integer validateAfterInactivity)
16311648
{
16321649
try
16331650
{
@@ -1643,11 +1660,11 @@ public static Client getNoCertClient(KeyStore trustStore, Integer maxConnPerRout
16431660
register("http", new PlainConnectionSocketFactory()).
16441661
build();
16451662

1646-
PoolingHttpClientConnectionManager conman = new PoolingHttpClientConnectionManager(socketFactoryRegistry)
1663+
PoolingHttpClientConnectionManager conman = new PoolingHttpClientConnectionManager(socketFactoryRegistry, null, null, null, connectionTimeToLive != null ? connectionTimeToLive : -1L, TimeUnit.MILLISECONDS)
16471664
{
16481665

16491666
// https://github.com/eclipse-ee4j/jersey/issues/4449
1650-
1667+
16511668
@Override
16521669
public void close()
16531670
{
@@ -1674,6 +1691,7 @@ public void releaseConnection(final HttpClientConnection managedConn, final Obje
16741691
};
16751692
if (maxConnPerRoute != null) conman.setDefaultMaxPerRoute(maxConnPerRoute);
16761693
if (maxTotalConn != null) conman.setMaxTotal(maxTotalConn);
1694+
if (validateAfterInactivity != null) conman.setValidateAfterInactivity(validateAfterInactivity);
16771695

16781696
ClientConfig config = new ClientConfig();
16791697
config.connectorProvider(new ApacheConnectorProvider());
@@ -1686,10 +1704,11 @@ public void releaseConnection(final HttpClientConnection managedConn, final Obje
16861704
config.property(ClientProperties.FOLLOW_REDIRECTS, true);
16871705
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
16881706
config.property(ApacheClientProperties.CONNECTION_MANAGER, conman);
1689-
if (connectionRequestTimeout != null)
1690-
config.property(ApacheClientProperties.REQUEST_CONFIG, RequestConfig.custom().
1691-
setConnectionRequestTimeout(connectionRequestTimeout).
1692-
build());
1707+
RequestConfig.Builder requestConfig = RequestConfig.custom();
1708+
if (connectionRequestTimeout != null) requestConfig.setConnectionRequestTimeout(connectionRequestTimeout);
1709+
if (socketTimeout != null) requestConfig.setSocketTimeout(socketTimeout);
1710+
if (connectTimeout != null) requestConfig.setConnectTimeout(connectTimeout);
1711+
config.property(ApacheClientProperties.REQUEST_CONFIG, requestConfig.build());
16931712

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

0 commit comments

Comments
 (0)