Search code examples
resteasyquarkus

Best way to close async resteasy client call


I have the following code:

var threadsWaiter = new CountDownLatch(customers.size());
for(var c: List<Customer> customers) {
   sendSms(c.phoneNr, threadsWaiter)
}
threadsWaiter.await();

public void sendSms(String phoneNr, CountDownLatch threadsWaiter) {
  ResteasyClientBuilder.newClient()
            .target(smsUrl)                
            .queryParam("to", phoneNr)
            .queryParam("message", message)
            .request()
            .async()
            .get(new InvocationCallback<String>() {
                @Override
                public void completed(String res) {
                    threadsWaiter.countDown();
                    if (res != null && !res.contains("code=ok") {
                        logger.error("Received sms response for '{}'\n{}", phoneNr, res);
                    } else {                            
                        logger.debug("Sms sent to '{}'", phoneNr);
                    }
                }

                @Override
                public void failed(Throwable throwable) {
                    threadsWaiter.countDown();
                    logger.error("Error sending sms for {}: \n{}", phoneNr, throwable.getMessage());
                }
            });
}

And I get the following warning from the console:

RESTEASY004687: Closing a class org.jboss.resteasy.client.jaxrs.engines.ApacheHttpClient43Engine instance for you. Please close clients yourself.

What is the proper way to close this client call? Because this might be a source for a potential memory leak in the application. And even I get this warning from RestEasy that it closes the client automatically for me, but I've a strong feeling that it doesn't close all the clients since I see a huge memory increase in the metrics and this doesn't "go down" after a while.

I've placed the rest client call between try-finally, but the problem with this is that you can close the client before the call is finished. Is it ok to close the client in the completed(..) and the failed(..) methods in the InvocationCallback or is there a better way?


Solution

  • The best way to do this with Quarkus would be to use the REST client with async support. Example:

        /**
         * This is the client stub.
         */
        @Path("/sms/response") // base URL is set in application.yml
        @RegisterRestClient
        public interface SmsServiceClient {
            
            @GET
            @Produces(MediaType.TEXT_PLAIN)
            CompletionStage<String> sendSms(
                @QueryParam("to") String phoneNr,
                @QueryParam("message") String message);
        }
    

    In the following example, I'm using SmallRye Mutiny to convert the CompletionStage into a Uni which has a leaner API. But you can achieve the same using the CompletionStage. Normally, I would not block the execution with that CountDownLatch.await() method. I stuck it in there to keep the code similar to your example.

        /**
         * This class will actually use the client.
         */
        @Slf4J
        @ApplicationScoped
        public class MySomething {
            
            @Inject
            @RestClient
            SmsServiceClient smsClient;
    
            public void sendSmsInLoop() {
                var waiter = new CountDownLatch(customers.size());
                customers.forEach(customer -> {
                    Uni.createFrom().completionStage(
                        smsClient.sendSms(customer.getPhoneNr(), "Lorem Ipsum...")
                    ).onItem().invoke(responseString -> {
                        if (responseString == null || !responseString.contains("code=ok")) {
                            log.error("Unexpected sms response for '{}'\n{}", customer.getPhoneNr(), responseString); 
                        } else {
                            log.debug("Sms sent to '{}'", customer.getPhoneNr());
                        }
                    }).onFailure().invoke(throwable -> {
                        log.error("Error sending sms to '{}'\n{}", customer.getPhoneNr(), throwable.getMessage());
                    }).eventually(() -> waiter.countDown());
                });
                waiter.await();
            }
        }