[
https://issues.apache.org/jira/browse/CXF-9162?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18016130#comment-18016130
]
Andriy Redko edited comment on CXF-9162 at 8/26/25 12:15 AM:
-------------------------------------------------------------
Thanks [~ffang] , I think we don't need `shuttingDown` - it introduces another
race condition (and second variable), we should be able to use AtomicLong
semantics in order to fix that.
Something along these lines (but we would need to change the construction a
bit since it starts with 0 now):
{noformat}
RefCount<T> acquire() {
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;
} {noformat}
was (Author: reta):
Thanks [~ffang] , I think we don't need `shuttingDown` - it introduces another
race condition (and second variable), we should be able to use AtomicLong
semantics in order to fix that.
> Reusing a Client with a HttpClientHTTPConduit is not threadsafe in regards to
> finalization
> ------------------------------------------------------------------------------------------
>
> Key: CXF-9162
> URL: https://issues.apache.org/jira/browse/CXF-9162
> Project: CXF
> Issue Type: Bug
> Reporter: Eric
> Assignee: Freeman Yue Fang
> Priority: Major
> Fix For: 4.1.4, 3.6.9, 4.0.10
>
>
> If a RestClient is created with the default HttpClientHTTPConduit it reuses
> the client by default.
> This, however, is not threadsafe in all cases, which is especially
> problematic when the client is implicitly closed by the finalizer. Consider
> the following example:
> {code:java}
> public class ResourceProblemWithCxfClientWithHttpClientHTTPConduitTest {
> static final String URL = "http://localhost:8080/";
> static Server server;
> @BeforeAll
> static void startServer() {
> var serverFactoryBean = new JAXRSServerFactoryBean();
> serverFactoryBean.setResourceClasses(MyServiceImpl.class);
> serverFactoryBean.setAddress(URL);
> server = serverFactoryBean.create();
> }
> @AfterAll
> static void stopServer() {
> server.stop();
> }
> @Test
> void testMultipleProxyCalls() {
> var clientFactoryBean = new JAXRSClientFactoryBean();
> clientFactoryBean.setAddress(URL);
> clientFactoryBean.setResourceClass(MyService.class);
> // Create the first Client and call the RestService
> var myClient1 = clientFactoryBean.create(MyService.class);
> myClient1.hello();
> // Create a second Client, but do not call yet
> var 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();
> System.out.println("GC'D");
> });
> myClient1 = null;
> // Now call the second client:
> // -- with Java17, the method very often just blocks forever
> // -- with Java21, it might throw the following exception:
> // jakarta.ws.rs.ProcessingException: java.io.IOException:
> IOException invoking http://localhost:8080/hello:
> // shutdownNow
> myClient2.hello();
> // the reason behind seems to be wether the resource-aquiciring of
> the shared client collides
> // with the cleanup of the first in the finalizer
> }
> @Path("")
> public interface MyService {
> @GET
> @Path("/hello")
> void hello();
> }
> public static class MyServiceImpl implements MyService {
> @Override
> public void hello() {
> System.out.println("hello");
> }
> }
> } {code}
>
> When everything works fine, this code prints the following:
> {noformat}
> hello
> GC'D
> hello{noformat}
>
> In many cases, however, it does not, because the GC collects the first client
> while second tries to acquire the ressource. Here, the garbage collector is
> aggressively invoked, but this has happend to us in production as well if the
> GC thinks its cleanup time while the second webclient-invocation starts.
> In the error case, the following can happen:
> * On Java 17, the first two statements are printed, then the test hangs
> forever
> * On Java 21+ which invokes close on the httpclient, all statments are
> printed followed by an IOException:
> {noformat}
> 17:35:13.284 [main] WARN org.apache.cxf.phase.PhaseInterceptorChain --
> Interceptor for
> {http://rest.itest.appkit.platform.arc.finkonsens.de/}MyService has thrown
> exception, unwinding now
> org.apache.cxf.interceptor.Fault: Could not send Message.{noformat}
>
> This error always happens when the gc finalizer closes the last running
> httpclient while a new call tries to acquired the shared client. If you
> cannot reproduce it with the test case above, then just run it in debug mode
> and set the following breakpoint with a hit count of 2 and blocking only the
> active thread, not all threads:
> {noformat}
> HttpClientHTTPConduit.RefCount.acquire{noformat}
> This should block the acquire long enough so that the error will always
> happen.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)