Skip to content

Commit 8ea25ba

Browse files
committed
Memory leak when using Jersey with HK2 eclipse-ee4j#5796
Signed-off-by: Jorge Bescos Gascon <jorge.bescos.gascon@oracle.com>
1 parent 382f69e commit 8ea25ba

File tree

7 files changed

+212
-100
lines changed

7 files changed

+212
-100
lines changed

core-client/src/main/java/org/glassfish/jersey/client/ClientConfig.java

+4-10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2012, 2023 Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2012, 2025 Oracle and/or its affiliates. All rights reserved.
33
* Copyright (c) 2018 Payara Foundation and/or its affiliates.
44
*
55
* This program and the accompanying materials are made available under the
@@ -55,6 +55,7 @@
5555
import org.glassfish.jersey.internal.util.collection.LazyValue;
5656
import org.glassfish.jersey.internal.util.collection.Value;
5757
import org.glassfish.jersey.internal.util.collection.Values;
58+
import org.glassfish.jersey.message.internal.MessageBodyFactory;
5859
import org.glassfish.jersey.model.internal.CommonConfig;
5960
import org.glassfish.jersey.model.internal.ComponentBag;
6061
import org.glassfish.jersey.model.internal.ManagedObjectsFinalizer;
@@ -416,17 +417,12 @@ private ClientRuntime initRuntime() {
416417

417418
final ClientBootstrapBag bootstrapBag = new ClientBootstrapBag();
418419
bootstrapBag.setManagedObjectsFinalizer(new ManagedObjectsFinalizer(injectionManager));
419-
420-
final ClientMessageBodyFactory.MessageBodyWorkersConfigurator messageBodyWorkersConfigurator =
421-
new ClientMessageBodyFactory.MessageBodyWorkersConfigurator();
422-
423-
List<BootstrapConfigurator> bootstrapConfigurators = Arrays.asList(
424-
new RequestScope.RequestScopeConfigurator(),
420+
List<BootstrapConfigurator> bootstrapConfigurators = Arrays.asList(new RequestScope.RequestScopeConfigurator(),
425421
new ParamConverterConfigurator(),
426422
new ParameterUpdaterConfigurator(),
427423
new RuntimeConfigConfigurator(runtimeCfgState),
428424
new ContextResolverFactory.ContextResolversConfigurator(),
429-
messageBodyWorkersConfigurator,
425+
new MessageBodyFactory.MessageBodyWorkersConfigurator(),
430426
new ExceptionMapperFactory.ExceptionMappersConfigurator(),
431427
new JaxrsProviders.ProvidersConfigurator(),
432428
new AutoDiscoverableConfigurator(RuntimeType.CLIENT),
@@ -467,8 +463,6 @@ private ClientRuntime initRuntime() {
467463
final ClientRuntime crt = new ClientRuntime(configuration, connector, injectionManager, bootstrapBag);
468464

469465
client.registerShutdownHook(crt);
470-
messageBodyWorkersConfigurator.setClientRuntime(crt);
471-
472466
return crt;
473467
}
474468

core-client/src/main/java/org/glassfish/jersey/client/ClientMessageBodyFactory.java

-87
This file was deleted.

core-client/src/main/java/org/glassfish/jersey/client/ClientRuntime.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2012, 2022 Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2012, 2025 Oracle and/or its affiliates. All rights reserved.
33
*
44
* This program and the accompanying materials are made available under the
55
* terms of the Eclipse Public License v. 2.0, which is available at
@@ -16,6 +16,7 @@
1616

1717
package org.glassfish.jersey.client;
1818

19+
import java.io.InputStream;
1920
import java.util.Collections;
2021
import java.util.concurrent.Callable;
2122
import java.util.concurrent.ExecutorService;
@@ -173,6 +174,8 @@ Runnable createRunnableForAsyncProcessing(ClientRequest request, final ResponseC
173174

174175
@Override
175176
public void response(final ClientResponse response) {
177+
InputStream in = response.getEntityStream();
178+
request.getClientConfig().getClient().putClientRuntimeLifeCycle(in, ClientRuntime.this);
176179
requestScope.runInScope(() -> processResponse(request, response, callback));
177180
}
178181

@@ -298,6 +301,8 @@ public ClientResponse invoke(final ClientRequest request) {
298301

299302
try {
300303
response = connector.apply(addUserAgent(Stages.process(request, requestProcessingRoot), connector.getName()));
304+
InputStream in = response.getEntityStream();
305+
request.getClientConfig().getClient().putClientRuntimeLifeCycle(in, this);
301306
} catch (final AbortException aborted) {
302307
response = aborted.getAbortResponse();
303308
}

core-client/src/main/java/org/glassfish/jersey/client/JerseyClient.java

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2011, 2023 Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2011, 2025 Oracle and/or its affiliates. All rights reserved.
33
*
44
* This program and the accompanying materials are made available under the
55
* terms of the Eclipse Public License v. 2.0, which is available at
@@ -16,11 +16,13 @@
1616

1717
package org.glassfish.jersey.client;
1818

19+
import java.io.InputStream;
1920
import java.lang.ref.Reference;
2021
import java.lang.ref.ReferenceQueue;
2122
import java.lang.ref.WeakReference;
2223
import java.net.URI;
2324
import java.util.Map;
25+
import java.util.WeakHashMap;
2426
import java.util.concurrent.ExecutorService;
2527
import java.util.concurrent.Executors;
2628
import java.util.concurrent.LinkedBlockingDeque;
@@ -69,6 +71,8 @@ public SSLContext getDefaultSslContext() {
6971
private final LinkedBlockingDeque<WeakReference<JerseyClient.ShutdownHook>> shutdownHooks =
7072
new LinkedBlockingDeque<WeakReference<JerseyClient.ShutdownHook>>();
7173
private final ReferenceQueue<JerseyClient.ShutdownHook> shReferenceQueue = new ReferenceQueue<JerseyClient.ShutdownHook>();
74+
// Keeps ClientRuntime alive till InputStream is GCed
75+
private final Map<InputStream, ClientRuntime> clientRuntimeLifeCycle = new WeakHashMap<>();
7276

7377
/**
7478
* Client instance shutdown hook.
@@ -387,4 +391,8 @@ public JerseyClient preInitialize() {
387391
config.preInitialize();
388392
return this;
389393
}
394+
395+
void putClientRuntimeLifeCycle(InputStream in, ClientRuntime cr) {
396+
clientRuntimeLifeCycle.put(in, cr);
397+
}
390398
}

tests/integration/jersey-5796/pom.xml

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
4+
Copyright (c) 2025 Oracle and/or its affiliates. All rights reserved.
5+
6+
This program and the accompanying materials are made available under the
7+
terms of the Eclipse Public License v. 2.0, which is available at
8+
http://www.eclipse.org/legal/epl-2.0.
9+
10+
This Source Code may also be made available under the following Secondary
11+
Licenses when the conditions for such availability set forth in the
12+
Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
13+
version 2 with the GNU Classpath Exception, which is available at
14+
https://www.gnu.org/software/classpath/license.html.
15+
16+
SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
17+
18+
-->
19+
20+
<project xmlns="http://maven.apache.org/POM/4.0.0"
21+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
22+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
23+
<parent>
24+
<artifactId>project</artifactId>
25+
<groupId>org.glassfish.jersey.tests.integration</groupId>
26+
<version>2.47-SNAPSHOT</version>
27+
</parent>
28+
<modelVersion>4.0.0</modelVersion>
29+
30+
<artifactId>jersey-5796</artifactId>
31+
<name>jersey-tests-integration-jersey-5796</name>
32+
33+
<dependencies>
34+
<dependency>
35+
<groupId>org.glassfish.jersey.test-framework.providers</groupId>
36+
<artifactId>jersey-test-framework-provider-bundle</artifactId>
37+
<type>pom</type>
38+
<scope>test</scope>
39+
</dependency>
40+
</dependencies>
41+
42+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
/*
2+
* Copyright (c) 2025 Oracle and/or its affiliates. All rights reserved.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License v. 2.0, which is available at
6+
* http://www.eclipse.org/legal/epl-2.0.
7+
*
8+
* This Source Code may also be made available under the following Secondary
9+
* Licenses when the conditions for such availability set forth in the
10+
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
11+
* version 2 with the GNU Classpath Exception, which is available at
12+
* https://www.gnu.org/software/classpath/license.html.
13+
*
14+
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15+
*/
16+
17+
package org.glassfish.jersey.tests.integration.jersey5796;
18+
19+
import static org.junit.jupiter.api.Assertions.assertEquals;
20+
21+
import java.lang.ref.WeakReference;
22+
import java.lang.reflect.Field;
23+
import java.util.concurrent.LinkedBlockingDeque;
24+
25+
import javax.ws.rs.GET;
26+
import javax.ws.rs.Path;
27+
import javax.ws.rs.client.Client;
28+
import javax.ws.rs.client.ClientBuilder;
29+
import javax.ws.rs.core.Application;
30+
import javax.ws.rs.core.GenericType;
31+
import javax.ws.rs.core.Response;
32+
33+
import org.glassfish.jersey.client.ChunkedInput;
34+
import org.glassfish.jersey.server.ChunkedOutput;
35+
import org.glassfish.jersey.server.ResourceConfig;
36+
import org.glassfish.jersey.test.JerseyTest;
37+
import org.junit.jupiter.api.Test;
38+
39+
40+
public class Jersey5796Test extends JerseyTest {
41+
42+
@Override
43+
protected Application configure() {
44+
return new ResourceConfig(Resource.class);
45+
}
46+
47+
@Test
48+
public void testMemoryLeak() throws Exception {
49+
Client client = ClientBuilder.newClient();
50+
for (int i = 0; i < 50; i++) {
51+
Response response = client.target(getBaseUri()).property("test", "test").path("/get1").request().get();
52+
assertEquals("GET", response.readEntity(String.class));
53+
}
54+
Runtime.getRuntime().gc();
55+
// Give some time to GC
56+
Thread.sleep(500);
57+
assertEquals(0, livingClientRuntimeInstances(client));
58+
client.close();
59+
}
60+
61+
/* Reproduces issue 4507
62+
MultiException stack 1 of 1
63+
java.lang.IllegalStateException: ServiceLocatorImpl(__HK2_Generated_0,0,427183206) has been shut down
64+
at org.jvnet.hk2.internal.ServiceLocatorImpl.checkState(ServiceLocatorImpl.java:2399)
65+
at org.jvnet.hk2.internal.ServiceLocatorImpl.getServiceHandleImpl(ServiceLocatorImpl.java:627)
66+
at org.jvnet.hk2.internal.ServiceLocatorImpl.getServiceHandle(ServiceLocatorImpl.java:620)
67+
at org.jvnet.hk2.internal.ServiceLocatorImpl.getServiceHandle(ServiceLocatorImpl.java:638)
68+
at org.jvnet.hk2.internal.FactoryCreator.getFactoryHandle(FactoryCreator.java:79)
69+
at org.jvnet.hk2.internal.FactoryCreator.dispose(FactoryCreator.java:149)
70+
at org.jvnet.hk2.internal.SystemDescriptor.dispose(SystemDescriptor.java:521)
71+
at org.glassfish.jersey.inject.hk2.RequestContext.lambda$findOrCreate$0(RequestContext.java:60)
72+
at org.glassfish.jersey.internal.inject.ForeignDescriptorImpl.dispose(ForeignDescriptorImpl.java:63)
73+
at org.glassfish.jersey.inject.hk2.Hk2RequestScope$Instance.remove(Hk2RequestScope.java:126)
74+
at java.base/java.lang.Iterable.forEach(Iterable.java:75)
75+
at org.glassfish.jersey.inject.hk2.Hk2RequestScope$Instance.release(Hk2RequestScope.java:143)
76+
at org.glassfish.jersey.server.ChunkedOutput.flushQueue(ChunkedOutput.java:405)
77+
at org.glassfish.jersey.server.ChunkedOutput.write(ChunkedOutput.java:264)
78+
at org.glassfish.jersey.tests.integration.jersey5796.Jersey5796Test$Resource.lambda$get2$0(Jersey5796Test.java:116)
79+
at java.base/java.lang.Thread.run(Thread.java:1583)
80+
81+
*/
82+
@Test
83+
public void testChunkedInput() throws Exception {
84+
Client client = ClientBuilder.newClient();
85+
assertEquals(0, livingClientRuntimeInstances(client));
86+
for (int i = 0; i < 50; i++) {
87+
ChunkedInput<String> chunkedInput = client.target(getBaseUri()).property("test", "test")
88+
.path("/get2").request().get(new GenericType<ChunkedInput<String>>() {});
89+
chunkedInput.setParser(ChunkedInput.createParser("\n"));
90+
int j = 0;
91+
String chunk;
92+
while ((chunk = chunkedInput.read()) != null) {
93+
assertEquals("Chunk " + j, chunk);
94+
j++;
95+
}
96+
}
97+
Runtime.getRuntime().gc();
98+
Thread.sleep(500);
99+
assertEquals(0, livingClientRuntimeInstances(client));
100+
client.close();
101+
}
102+
103+
private int livingClientRuntimeInstances(Client client) throws Exception {
104+
Class<?> clientRuntime = Class.forName("org.glassfish.jersey.client.ClientRuntime");
105+
Class<?> clazz = client.getClass();
106+
Field field = clazz.getDeclaredField("shutdownHooks");
107+
field.setAccessible(true);
108+
LinkedBlockingDeque<WeakReference<?>> shutdownHooks = (LinkedBlockingDeque<WeakReference<?>>) field.get(client);
109+
int counter = 0;
110+
for (WeakReference<?> ref : shutdownHooks) {
111+
if (ref.get() != null && ref.get().getClass() == clientRuntime) {
112+
counter++;
113+
}
114+
}
115+
return counter;
116+
}
117+
118+
@Path("/")
119+
public static class Resource {
120+
121+
@GET
122+
@Path("/get1")
123+
public String get1() {
124+
return "GET";
125+
}
126+
127+
@GET
128+
@Path("/get2")
129+
public ChunkedOutput<String> get2() {
130+
ChunkedOutput<String> output = new ChunkedOutput<>(String.class);
131+
new Thread(() -> {
132+
try {
133+
for (int i = 0; i < 3; i++) {
134+
output.write("Chunk " + i + "\n");
135+
}
136+
} catch (Exception e1) {
137+
e1.printStackTrace();
138+
} finally {
139+
try {
140+
output.close();
141+
} catch (Exception e2) {
142+
e2.printStackTrace();
143+
}
144+
}
145+
}).start();
146+
return output;
147+
}
148+
}
149+
}

0 commit comments

Comments
 (0)