Best way to close async resteasy client call
Asked Answered
P

3

6

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?

Progestational answered 28/1, 2020 at 21:53 Comment(0)
C
1

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();
        }
    }
Commons answered 29/9, 2020 at 19:45 Comment(0)
M
1

If the client definition via JaxRS interface is ok for you, then the answer from rowing-goul above is the way to go. Injected clients are automatically closed by Quarkus Arc during bean destroyer process. If you create clients manually, then you must close them explicitly - best way is try - finally

Mezzo answered 20/3, 2021 at 17:55 Comment(0)
D
0

In my case, it's not Quarkus, but I can close it manually.

try {
    client.call(); 
    .... // it was injected before so I cannot use try-with-resources
} catch (Exception e) {
    ....
} finally {
    client.close();
}
Dyadic answered 20/6, 2022 at 8:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.