[
https://issues.apache.org/jira/browse/CXF-9162?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18016119#comment-18016119
]
Freeman Yue Fang commented on CXF-9162:
---------------------------------------
Hi [~cheesemaster],
Thanks for raising this issue and the reproducer. Yes, I can see the problem
with both JDK17 and JDK21.
I believe this is caused by the race condition in
HttpClientHTTPConduit.RefCount class
So imagine this timeline:
Client1 calls release().
count.decrementAndGet() returns 0.
Thread enters the shutdown block, but hasn’t executed it yet.
Client2 calls acquire() while Client1 is still in the shutdown block.
count.incrementAndGet() brings count back to 1.
But it’s too late — Client1 is already about to shut down the underlying
HttpClient.
Need to revisit the HttpClientHTTPConduit.RefCount class.
Freeman
> 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
>
> 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)