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 .io .ByteArrayInputStream ;
22+ import java .io .InputStream ;
23+ import java .lang .ref .WeakReference ;
24+ import java .lang .reflect .Field ;
25+ import java .util .Map ;
26+ import java .util .concurrent .LinkedBlockingDeque ;
27+ import java .util .concurrent .atomic .AtomicInteger ;
28+
29+ import javax .ws .rs .GET ;
30+ import javax .ws .rs .Path ;
31+ import javax .ws .rs .client .Client ;
32+ import javax .ws .rs .client .ClientBuilder ;
33+ import javax .ws .rs .core .Application ;
34+ import javax .ws .rs .core .GenericType ;
35+ import javax .ws .rs .core .Response ;
36+
37+ import org .glassfish .jersey .client .ChunkedInput ;
38+ import org .glassfish .jersey .client .ClientConfig ;
39+ import org .glassfish .jersey .client .ClientLifecycleListener ;
40+ import org .glassfish .jersey .client .JerseyClient ;
41+ import org .glassfish .jersey .server .ChunkedOutput ;
42+ import org .glassfish .jersey .server .ResourceConfig ;
43+ import org .glassfish .jersey .test .JerseyTest ;
44+ import org .junit .jupiter .api .Test ;
45+
46+
47+ public class Jersey5796Test extends JerseyTest {
48+
49+ private static final int COUNT = 50 ;
50+
51+ @ Override
52+ protected Application configure () {
53+ return new ResourceConfig (Resource .class );
54+ }
55+
56+ @ Test
57+ public void testMemoryLeak () throws Exception {
58+ ClientRuntimeCloseVerifier .closedClientRuntime = new AtomicInteger (0 );
59+ Client client = ClientBuilder .newClient (new ClientConfig (ClientRuntimeCloseVerifier .class ));
60+ assertEquals (0 , ClientRuntimeCloseVerifier .closedClientRuntime .get ());
61+ for (int i = 0 ; i < COUNT ; i ++) {
62+ Response response = client .target (getBaseUri ()).property ("test" , "test" ).path ("/get1" ).request ().get ();
63+ assertEquals ("GET" , response .readEntity (String .class ));
64+ response .close ();
65+ }
66+ System .gc ();
67+ do {
68+ Thread .sleep (100L );
69+ } while (ClientRuntimeCloseVerifier .closedClientRuntime .get () != 50 );
70+ assertEquals (COUNT , ClientRuntimeCloseVerifier .closedClientRuntime .get ());
71+ client .close ();
72+
73+ }
74+
75+ /* Reproduces issue 4507
76+ MultiException stack 1 of 1
77+ java.lang.IllegalStateException: ServiceLocatorImpl(__HK2_Generated_0,0,427183206) has been shut down
78+ at org.jvnet.hk2.internal.ServiceLocatorImpl.checkState(ServiceLocatorImpl.java:2399)
79+ at org.jvnet.hk2.internal.ServiceLocatorImpl.getServiceHandleImpl(ServiceLocatorImpl.java:627)
80+ at org.jvnet.hk2.internal.ServiceLocatorImpl.getServiceHandle(ServiceLocatorImpl.java:620)
81+ at org.jvnet.hk2.internal.ServiceLocatorImpl.getServiceHandle(ServiceLocatorImpl.java:638)
82+ at org.jvnet.hk2.internal.FactoryCreator.getFactoryHandle(FactoryCreator.java:79)
83+ at org.jvnet.hk2.internal.FactoryCreator.dispose(FactoryCreator.java:149)
84+ at org.jvnet.hk2.internal.SystemDescriptor.dispose(SystemDescriptor.java:521)
85+ at org.glassfish.jersey.inject.hk2.RequestContext.lambda$findOrCreate$0(RequestContext.java:60)
86+ at org.glassfish.jersey.internal.inject.ForeignDescriptorImpl.dispose(ForeignDescriptorImpl.java:63)
87+ at org.glassfish.jersey.inject.hk2.Hk2RequestScope$Instance.remove(Hk2RequestScope.java:126)
88+ at java.base/java.lang.Iterable.forEach(Iterable.java:75)
89+ at org.glassfish.jersey.inject.hk2.Hk2RequestScope$Instance.release(Hk2RequestScope.java:143)
90+ at org.glassfish.jersey.server.ChunkedOutput.flushQueue(ChunkedOutput.java:405)
91+ at org.glassfish.jersey.server.ChunkedOutput.write(ChunkedOutput.java:264)
92+ at org.glassfish.jersey.tests.integration.jersey5796.Jersey5796Test$Resource.lambda$get2$0(Jersey5796Test.java:116)
93+ at java.base/java.lang.Thread.run(Thread.java:1583)
94+
95+ */
96+ @ Test
97+ public void testChunkedInput () throws Exception {
98+ ClientRuntimeCloseVerifier .closedClientRuntime = new AtomicInteger (0 );
99+ Client client = ClientBuilder .newClient (new ClientConfig (ClientRuntimeCloseVerifier .class ));
100+ assertEquals (0 , ClientRuntimeCloseVerifier .closedClientRuntime .get ());
101+ for (int i = 0 ; i < COUNT ; i ++) {
102+ ChunkedInput <String > chunkedInput = client .target (getBaseUri ()).property ("test" , "test" )
103+ .path ("/get2" ).request ().get (new GenericType <ChunkedInput <String >>() {});
104+ chunkedInput .setParser (ChunkedInput .createParser ("\n " ));
105+ int j = 0 ;
106+ String chunk ;
107+ while ((chunk = chunkedInput .read ()) != null ) {
108+ assertEquals ("Chunk " + j , chunk );
109+ j ++;
110+ }
111+ chunkedInput .close ();
112+ }
113+ System .gc ();
114+ do {
115+ Thread .sleep (100L );
116+ } while (ClientRuntimeCloseVerifier .closedClientRuntime .get () != 50 );
117+ assertEquals (COUNT , ClientRuntimeCloseVerifier .closedClientRuntime .get ());
118+ client .close ();
119+ }
120+
121+ @ Path ("/" )
122+ public static class Resource {
123+
124+ @ GET
125+ @ Path ("/get1" )
126+ public String get1 () {
127+ return "GET" ;
128+ }
129+
130+ @ GET
131+ @ Path ("/get2" )
132+ public ChunkedOutput <String > get2 () {
133+ ChunkedOutput <String > output = new ChunkedOutput <>(String .class );
134+ new Thread (() -> {
135+ try {
136+ for (int i = 0 ; i < 3 ; i ++) {
137+ output .write ("Chunk " + i + "\n " );
138+ }
139+ } catch (Exception e1 ) {
140+ e1 .printStackTrace ();
141+ } finally {
142+ try {
143+ output .close ();
144+ } catch (Exception e2 ) {
145+ e2 .printStackTrace ();
146+ }
147+ }
148+ }).start ();
149+ return output ;
150+ }
151+ }
152+
153+ public static class ClientRuntimeCloseVerifier implements ClientLifecycleListener {
154+
155+ private static AtomicInteger closedClientRuntime ;
156+
157+ @ Override
158+ public void onInit () {
159+ }
160+
161+ @ Override
162+ public void onClose () {
163+ closedClientRuntime .incrementAndGet ();
164+ }
165+ }
166+ }
0 commit comments