Reputation: 1235
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?
Upvotes: 6
Views: 6264
Reputation: 12787
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();
}
Upvotes: 0
Reputation: 3228
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
Upvotes: 1
Reputation: 1393
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();
}
}
Upvotes: 1