Eric created CXF-9162:
-------------------------

             Summary: 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


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)

Reply via email to