[
https://issues.apache.org/jira/browse/CXF-9162?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Freeman Yue Fang updated CXF-9162:
----------------------------------
Fix Version/s: 4.1.4
3.6.9
4.0.10
> 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)