Skip to content

Commit 214885b

Browse files
committed
Release instances blocking GC when JAXB has PerThread injections
Signed-off-by: jansupol <[email protected]>
1 parent de3e7fe commit 214885b

File tree

7 files changed

+162
-19
lines changed

7 files changed

+162
-19
lines changed

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2020, 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 org.glassfish.jersey.innate.io.SafelyClosable;
1920
import org.glassfish.jersey.internal.BootstrapBag;
2021
import org.glassfish.jersey.internal.BootstrapConfigurator;
2122
import org.glassfish.jersey.internal.inject.Bindings;
@@ -29,7 +30,7 @@
2930

3031
import javax.ws.rs.core.Configuration;
3132

32-
class ClientMessageBodyFactory extends MessageBodyFactory {
33+
class ClientMessageBodyFactory extends MessageBodyFactory implements SafelyClosable {
3334

3435
/**
3536
* Keep reference to {@link ClientRuntime} so that {@code finalize} on it is not called
@@ -39,7 +40,7 @@ class ClientMessageBodyFactory extends MessageBodyFactory {
3940
* but if the finalizer is invoked before that, the HK2 injection manager gets closed.
4041
* </p>
4142
*/
42-
private final LazyValue<ClientRuntime> clientRuntime;
43+
private LazyValue<ClientRuntime> clientRuntime;
4344

4445
/**
4546
* Create a new message body factory.
@@ -52,6 +53,11 @@ private ClientMessageBodyFactory(Configuration configuration, Value<ClientRuntim
5253
clientRuntime = Values.lazy(clientRuntimeValue);
5354
}
5455

56+
@Override
57+
public void close() {
58+
clientRuntime = null;
59+
}
60+
5561
/**
5662
* Configurator which initializes and register {@link MessageBodyWorkers} instance into {@link InjectionManager} and
5763
* {@link BootstrapBag}.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
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.innate.io;
18+
19+
/**
20+
* A SafelyClosable is a resource that can be closed.
21+
* The close method is invoked to release resources that the object is holding.
22+
* Closing the resource is safe in a sense that no Exception is being thrown.
23+
*/
24+
public interface SafelyClosable {
25+
26+
/**
27+
* Close the resource, no checked exception thrown.
28+
*/
29+
void close();
30+
}

core-common/src/main/java/org/glassfish/jersey/message/MessageBodyWorkers.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2010, 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
@@ -32,6 +32,7 @@
3232
import javax.ws.rs.ext.ReaderInterceptor;
3333
import javax.ws.rs.ext.WriterInterceptor;
3434

35+
import org.glassfish.jersey.innate.io.SafelyClosable;
3536
import org.glassfish.jersey.internal.PropertiesDelegate;
3637

3738
/**
@@ -43,7 +44,7 @@
4344
* @see MessageBodyReader
4445
* @see MessageBodyWriter
4546
*/
46-
public interface MessageBodyWorkers {
47+
public interface MessageBodyWorkers extends SafelyClosable {
4748
/**
4849
* Get the map of media type to list of message body writers that are compatible with
4950
* a media type.

core-common/src/main/java/org/glassfish/jersey/message/internal/InboundMessageContext.java

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2012, 2024 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
@@ -23,39 +23,31 @@
2323
import java.io.InputStream;
2424
import java.lang.annotation.Annotation;
2525
import java.lang.reflect.Type;
26-
import java.net.URI;
2726
import java.text.ParseException;
2827
import java.util.Arrays;
2928
import java.util.Collections;
30-
import java.util.Date;
31-
import java.util.HashMap;
3229
import java.util.HashSet;
3330
import java.util.Iterator;
3431
import java.util.LinkedList;
3532
import java.util.List;
36-
import java.util.Locale;
3733
import java.util.Map;
3834
import java.util.Set;
3935
import java.util.StringTokenizer;
4036
import java.util.function.Function;
4137

4238
import javax.ws.rs.ProcessingException;
4339
import javax.ws.rs.core.Configuration;
44-
import javax.ws.rs.core.Cookie;
45-
import javax.ws.rs.core.EntityTag;
4640
import javax.ws.rs.core.HttpHeaders;
4741
import javax.ws.rs.core.Link;
4842
import javax.ws.rs.core.MediaType;
4943
import javax.ws.rs.core.MultivaluedMap;
50-
import javax.ws.rs.core.NewCookie;
5144
import javax.ws.rs.ext.ReaderInterceptor;
5245

53-
import javax.ws.rs.ext.RuntimeDelegate;
5446
import javax.xml.transform.Source;
5547

48+
import org.glassfish.jersey.innate.io.SafelyClosable;
5649
import org.glassfish.jersey.internal.LocalizationMessages;
5750
import org.glassfish.jersey.internal.PropertiesDelegate;
58-
import org.glassfish.jersey.internal.RuntimeDelegateDecorator;
5951
import org.glassfish.jersey.internal.util.collection.GuardianStringKeyMultivaluedMap;
6052
import org.glassfish.jersey.internal.util.collection.LazyValue;
6153
import org.glassfish.jersey.internal.util.collection.Value;
@@ -67,7 +59,7 @@
6759
*
6860
* @author Marek Potociar
6961
*/
70-
public abstract class InboundMessageContext extends MessageHeaderMethods {
62+
public abstract class InboundMessageContext extends MessageHeaderMethods implements SafelyClosable {
7163

7264
private static final InputStream EMPTY = new InputStream() {
7365

@@ -729,6 +721,9 @@ public boolean bufferEntity() throws ProcessingException {
729721
*/
730722
public void close() {
731723
entityContent.close(true);
724+
if (workers != null) {
725+
workers.close();
726+
}
732727
setWorkers(null);
733728
}
734729

core-common/src/main/java/org/glassfish/jersey/message/internal/MessageBodyFactory.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2010, 2019 Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2010, 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
@@ -1189,4 +1189,11 @@ public static boolean isReadable(final MessageBodyReader<?> provider,
11891189
}
11901190
return false;
11911191
}
1192+
1193+
1194+
@Override
1195+
public void close() {
1196+
// NOOP
1197+
}
1198+
11921199
}

core-common/src/main/java/org/glassfish/jersey/message/internal/OutboundMessageContext.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2012, 2024 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
@@ -39,6 +39,7 @@
3939
import javax.ws.rs.core.MultivaluedMap;
4040

4141
import org.glassfish.jersey.CommonProperties;
42+
import org.glassfish.jersey.innate.io.SafelyClosable;
4243
import org.glassfish.jersey.internal.RuntimeDelegateDecorator;
4344
import org.glassfish.jersey.internal.util.ReflectionHelper;
4445
import org.glassfish.jersey.internal.util.collection.GuardianStringKeyMultivaluedMap;
@@ -52,7 +53,7 @@
5253
*
5354
* @author Marek Potociar
5455
*/
55-
public class OutboundMessageContext extends MessageHeaderMethods {
56+
public class OutboundMessageContext extends MessageHeaderMethods implements SafelyClosable {
5657
private static final Annotation[] EMPTY_ANNOTATIONS = new Annotation[0];
5758
private static final List<MediaType> WILDCARD_ACCEPTABLE_TYPE_SINGLETON_LIST =
5859
Collections.<MediaType>singletonList(MediaTypes.WILDCARD_ACCEPTABLE_TYPE);
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
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.e2e.client;
18+
19+
import org.glassfish.jersey.client.ClientConfig;
20+
import org.hamcrest.MatcherAssert;
21+
import org.hamcrest.Matchers;
22+
import org.junit.jupiter.api.Test;
23+
24+
import javax.ws.rs.client.Client;
25+
import javax.ws.rs.client.ClientBuilder;
26+
import javax.ws.rs.client.ClientRequestContext;
27+
import javax.ws.rs.client.ClientRequestFilter;
28+
import javax.ws.rs.client.WebTarget;
29+
import javax.ws.rs.core.MediaType;
30+
import javax.ws.rs.core.Response;
31+
import javax.xml.bind.annotation.XmlRootElement;
32+
import java.io.IOException;
33+
import java.lang.ref.ReferenceQueue;
34+
import java.lang.ref.WeakReference;
35+
import java.lang.reflect.Method;
36+
import java.util.ArrayList;
37+
import java.util.List;
38+
39+
public class JerseyClientRuntimeTest {
40+
41+
private static int COUNT = 10;
42+
private List<WeakReference<Object>> list = new ArrayList<>();
43+
private ReferenceQueue queue = new ReferenceQueue();
44+
45+
@Test
46+
public void testClientRuntimeInstancesAreGCed() throws InterruptedException {
47+
Client c = ClientBuilder.newClient();
48+
c.register(new ClientRequestFilter() {
49+
@Override
50+
public void filter(ClientRequestContext requestContext) throws IOException {
51+
requestContext.abortWith(Response
52+
.ok("<myDTO xmlns=\"http://org.example.dtos\"/>")
53+
.type(MediaType.APPLICATION_XML_TYPE)
54+
.build());
55+
}
56+
});
57+
58+
WebTarget target = c.target("http://localhost/nowhere");
59+
for (int i = 0; i != COUNT; i++) {
60+
target = target.property("SOME", "PROPERTY");
61+
ClientConfig config = (ClientConfig) target.getConfiguration();
62+
Object clientRuntime = getClientRuntime(config);
63+
addToList(clientRuntime);
64+
try (Response response = target.request().get()) {
65+
MatcherAssert.assertThat(response.getStatus(), Matchers.is(200));
66+
MyDTO dto = response.readEntity(MyDTO.class);
67+
MatcherAssert.assertThat(dto, Matchers.notNullValue());
68+
}
69+
}
70+
71+
System.gc();
72+
do {
73+
Thread.sleep(100L);
74+
} while (queueIsEmpty(queue));
75+
76+
c.close();
77+
78+
}
79+
80+
private static Object getClientRuntime(ClientConfig config) {
81+
try {
82+
Method m = ClientConfig.class.getDeclaredMethod("getRuntime");
83+
m.setAccessible(true);
84+
return m.invoke(config);
85+
} catch (Exception e) {
86+
e.printStackTrace();
87+
return null;
88+
}
89+
}
90+
91+
private static boolean queueIsEmpty(ReferenceQueue queue) {
92+
return queue.poll() == null;
93+
}
94+
95+
private void addToList(Object object) {
96+
list.add(new WeakReference<>(object, queue));
97+
}
98+
99+
@XmlRootElement(name = "myDTO", namespace = "http://org.example.dtos")
100+
public static class MyDTO {
101+
102+
}
103+
}

0 commit comments

Comments
 (0)