This is an automated email from the ASF dual-hosted git repository.
ffang pushed a commit to branch 4.0.x-fixes
in repository https://gitbox.apache.org/repos/asf/cxf.git
The following commit(s) were added to refs/heads/4.0.x-fixes by this push:
new d5b945eb78 [CXF-9162]avoid reusing HttpClientHTTPConduit if the
underlying HttpC… (#2571)
d5b945eb78 is described below
commit d5b945eb78fc9554ee805029e35e1102dbb3be44
Author: Freeman(Yue) Fang <[email protected]>
AuthorDate: Tue Sep 9 12:28:05 2025 -0400
[CXF-9162]avoid reusing HttpClientHTTPConduit if the underlying HttpC…
(#2571)
(cherry picked from commit 19bc70b7eebaa3b0d66c9d68282d8f463171234c)
---
.../cxf/transport/http/HttpClientHTTPConduit.java | 32 +++++--
.../org/apache/cxf/systest/jaxrs/CXF9162Test.java | 101 +++++++++++++++++++++
2 files changed, 127 insertions(+), 6 deletions(-)
diff --git
a/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HttpClientHTTPConduit.java
b/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HttpClientHTTPConduit.java
index baf4b49a3b..59a0a977c1 100644
---
a/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HttpClientHTTPConduit.java
+++
b/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HttpClientHTTPConduit.java
@@ -114,7 +114,7 @@ public class HttpClientHTTPConduit extends
URLConnectionHTTPConduit {
private final Queue<RefCount<HttpClient>> deferredClientRefs = new
ConcurrentLinkedQueue<>();
private static final class RefCount<T extends HttpClient> {
- private final AtomicLong count = new AtomicLong();
+ private final AtomicLong count;
private final TLSClientParameters clientParameters;
private final HTTPClientPolicy policy;
private final T client;
@@ -125,10 +125,18 @@ public class HttpClientHTTPConduit extends
URLConnectionHTTPConduit {
this.policy = policy;
this.clientParameters = clientParameters;
this.finalizer = finalizer;
+ this.count = new AtomicLong(1);
}
RefCount<T> acquire() {
- count.incrementAndGet();
+ while (true) {
+ final long c = count.get();
+ if (c == 0L) {
+ throw new IllegalStateException("The client is already
shutdown");
+ } else if (count.compareAndSet(c, c + 1)) {
+ break;
+ }
+ }
return this;
}
@@ -169,6 +177,10 @@ public class HttpClientHTTPConduit extends
URLConnectionHTTPConduit {
}
}
}
+
+ boolean isClosed() {
+ return count.get() == 0L;
+ }
HttpClient client() {
return client;
@@ -195,14 +207,22 @@ public class HttpClientHTTPConduit extends
URLConnectionHTTPConduit {
// Do not share if it is not allowed for the conduit or cache
capacity is exceeded
if (!shareHttpClient || clients.size() >= MAX_SIZE) {
- return new RefCount<HttpClient>(supplier.get(), policy,
clientParameters, () -> { }).acquire();
+ return new RefCount<HttpClient>(supplier.get(), policy,
clientParameters, () -> { });
}
lock.lock();
try {
for (final RefCount<HttpClient> p: clients) {
- if (cpc.equals(p.policy(), policy) &&
p.clientParameters().equals(clientParameters)) {
- return p.acquire();
+ try {
+ if (p.isClosed()) { // skip closed but not yet removed
clients
+ continue;
+ }
+ if (cpc.equals(p.policy(), policy) &&
p.clientParameters().equals(clientParameters)) {
+ return p.acquire();
+ }
+ } catch (final IllegalStateException ex) {
+ // candidate raced into shutdown; skip and try next
+ continue;
}
}
@@ -211,7 +231,7 @@ public class HttpClientHTTPConduit extends
URLConnectionHTTPConduit {
() -> this.remove(policy, clientParameters));
clients.add(clientRef);
- return clientRef.acquire();
+ return clientRef;
} finally {
lock.unlock();
}
diff --git
a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/CXF9162Test.java
b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/CXF9162Test.java
new file mode 100644
index 0000000000..c19f697980
--- /dev/null
+++ b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/CXF9162Test.java
@@ -0,0 +1,101 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cxf.systest.jaxrs;
+
+import java.util.concurrent.CompletableFuture;
+
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import org.apache.cxf.endpoint.Server;
+import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
+import org.apache.cxf.jaxrs.client.JAXRSClientFactoryBean;
+import org.apache.cxf.testutil.common.TestUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+
+public class CXF9162Test {
+
+ static final String URL = "http://localhost:"
+ + TestUtil.getPortNumber(CXF9162Test.class) + "/";
+
+ static Server server;
+
+ @Before
+ public void startServer() {
+ JAXRSServerFactoryBean serverFactoryBean = new
JAXRSServerFactoryBean();
+ serverFactoryBean.setResourceClasses(MyServiceImpl.class);
+ serverFactoryBean.setAddress(URL);
+ server = serverFactoryBean.create();
+ }
+
+ @After
+ public void stopServer() {
+ server.stop();
+ }
+
+ @Test
+ public void testMultipleProxyCalls() {
+ JAXRSClientFactoryBean clientFactoryBean = new
JAXRSClientFactoryBean();
+ clientFactoryBean.setAddress(URL);
+ clientFactoryBean.setResourceClass(MyService.class);
+
+ // Create the first Client and call the RestService
+ MyService myClient1 = clientFactoryBean.create(MyService.class);
+ myClient1.hello();
+
+ // Create a second Client, but do not call yet
+ MyService myClient2 = clientFactoryBean.create(MyService.class);
+ // Register an async GC with finalizer exec and make client1 eligible
for gc
+ CompletableFuture.runAsync(() -> {
+ System.gc();
+ System.runFinalization();
+ System.gc();
+ });
+ myClient1 = null;
+
+ try {
+ myClient2.hello();
+ } catch (Exception ex) {
+ //the JDK HttpClient may being shutting down
+ assertEquals(ex.getCause().getMessage(), "Client already shutting
down");
+ }
+
+ }
+
+ @Path("")
+ public interface MyService {
+
+ @GET
+ @Path("/hello")
+ void hello();
+ }
+
+ public static class MyServiceImpl implements MyService {
+
+ @Override
+ public void hello() {
+ System.out.println("hello");
+ }
+ }
+}