Is decorated function returned by Retry threadsafe? - resilience4j

I have a class that sends a message to a remote service as shown below.
I'm using resilience4j-retry to retry the network call. As the retry instance is thread safe according to the documentation, I'm creating it in the class level and reusing it.
public class RemoteMessageService {
Retry retry = Retry.of("RemoteMessageService", RetryConfig.custom()
.maxAttempts(5)
.retryExceptions(ProcessingException.class)
.intervalFunction(IntervalFunction.ofExponentialBackoff())
.build());
public void postMessageWithRetry(final String message){
Function<Integer, Void> postMessageFunction = Retry.decorateFunction(retry, this::postMessage);
try {
postMessageFunction.apply(message)
} catch (final ProcessingException e) {
LOG.warn("Got processing exception: {}", e.getMessage());
} catch (final Exception e) {
LOG.error("Got unknown exception: {}", e.getMessage());
}
}
private Void postMessage(final String message){
// Do a network call to send the message to a rest service
// throw ProcessingException in case of timeout
return null;
}
}
My question is if the decorated function returned by Retry.decorateFunction(retry, this::postMessage); is also thread safe?
In that case I could move this to class level instead of repeating it every time the postMessageWithRetry function is called.

After looking into the resilience4j-retry code, I found that the decorated function is in fact thread safe; as long as the function that we decorate in the first place is thread safe.
So I can rewrite the code as below since the postMessage function is thread safe, and therefor the decorated postMessageFunction function is also thread safe.
public class RemoteMessageService {
private final Retry retry = Retry.of("RemoteMessageService", RetryConfig.custom()
.maxAttempts(5)
.retryExceptions(ProcessingException.class)
.intervalFunction(IntervalFunction.ofExponentialBackoff())
.build());
private final Function<Integer, Void> postMessageFunction = Retry.decorateFunction(retry, this::postMessage);
public void postMessageWithRetry(final String message) {
try {
postMessageFunction.apply(message)
} catch (final ProcessingException e) {
LOG.warn("Got processing exception: {}", e.getMessage());
} catch (final Exception e) {
LOG.error("Got unknown exception: {}", e.getMessage());
}
}
private Void postMessage(final String message) {
// Do a network call to send the message to a rest service
// throw ProcessingException in case of timeout
return null;
}
}

Related

spring websocket client does not detect network connection loss

Spring #ClientEndpoint websocket client does not detect network disconnect due to cable unplug. I also have implemented a ping/pong mechanism. Can someone please help me out with what's going on?
However I see following exception after reconnecting the cable, FYI, all setting are into default. Also I am connecting to a 3rd party remote endpoint where do not have any control.
xxxxxException: closed with code : CLOSED_ABNORMALLY reason: CloseReason: code [1006], reason [java.io.IOException: Connection reset by peer]
at xxxxxx.onClose(WSClient.java:xx)
#ClientEndpoint
public class WSClient {
private Session session;
private int i = 0;
#OnOpen
public void open(Session session) {
System.out.println("Connected to the server");
this.session = session;
}
#OnClose
public void close(Session session, CloseReason closeReason) {
System.out.println("connection closed " + closeReason.getReasonPhrase());
}
#OnError
public void error(Session session, Throwable t) {
System.out.println(session.getId());
System.out.println("Error in connection " + t.getMessage());
}
#OnMessage
public void message(Session session, String message) {
System.out.println("message received: " + message + " " + i++);
}
public void send(String message){
try {
if(session.isOpen()) {
this.session.getBasicRemote().sendPing(ByteBuffer.wrap(message.getBytes()));
System.out.println("socket is open " + i++);
} else {
System.out.println("socket closed");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
#Component
public class ClientApp implements ApplicationListener<ApplicationReadyEvent> {
private void startConnection() throws Exception {
WebSocketContainer container = ContainerProvider.getWebSocketContainer();
WSClient client = new WSClient();
container.connectToServer(client, new URI("ws://wshost:8080/ping"));
while (true) {
client.send("ping");
TimeUnit.SECONDS.sleep(3);
}
}
#Override
public void onApplicationEvent(ApplicationReadyEvent event) {
try {
startConnection();
} catch (Exception e) {
System.out.println(e);
}
}
}
This issue can be resolved by adding below code to WSClient.
#OnMessage
public void pongMessage(Session session, PongMessage msg) {
LOGGER.debug("Pong message received: " + Instant.now());
//schedule a timeout task, and raise an event or so if timed out.
}
The above snippet will be invoked when remote endpoint sends a pong message as a respond to the ping message sent. Basically there will be two methods annotated with #OnMessage, one to received the user message payload and another pong message payload sent by the framework.

#Transactional not working when using try catch block

The transactional roll back is not working when the exception is caught on the catch block, and another method is called for throw the exception. The pseudo code for the above is:
#Transactional(rollBackFor = Exception.class)
public void method1() {
// Calling another method
method2();
}
private void method2() {
try {
dbOperation1();
} catch (Exception e) {
handleFault()
}
}
handleFault() {
// Calling another method and throwing an exception
throwException()
}
throwException() {
//....
throw new Exception();
}

Spring does rollback while saving at finally block of try-catch

This is the class to save:
#Service
public class DataService {
#Transactional(readOnly = true)
public String fetchData() { //no exception signature
try {
//some operations
checkData();
}
catch(Exception e) {
throw new CanerRuntimeException("an error occurred in fetchdata: " + e.getMessage(), e);//it cant come here with exception from child
}
}
private void checkData() throws SystemException { //intellj made me put that exception
try {
//some operations
if (!isCanerNotMade) {
String errorMessage = "It is not caner made by";
throw new CanerBusinessException(errorMessage);
}
}
} catch(CanerBusinessException e) {
logger.error("CheckForFksLimitations CanerBusinessExceptionerror {}", e.getMessage());
throw e;
}
} catch(Exception e) {
logger.error("CheckForFksLimitations Exception error {}", e.getMessage());
throw e;
} finally {
if (fksLog != null) {
saveLog(fksLog);
}
logger.info("CheckForFksLimitations ended for identityNumber: {}", identityNumber);//3
}
}
#Transactional
private void saveLog(FksLog fksLog) {
try {
logger.info("CheckForFksLimitations saving fksControlLog: {}", mobilityUtil.getObjectAsJson(fksControlLog));//1
FksControlLog savedfksControlLog = fksControlLogRepository.saveAndFlush(fksControlLog);
logger.info("CheckForFksLimitations saved fksControlLog: {}", mobilityUtil.getObjectAsJson(savedfksControlLog));//2
} catch(CanerBusinessException e) {
logger.info("CheckForFksLimitations error: {}", e.getMessage(), e);
}
}
and that exceptions are:
public class CanerBusinessException extends RuntimeException {}
public class CanerRuntimeException extends RuntimeException {}
I send data for both cases. One for not to throw exception and it can save without any rollback. I made saveAndFlush because it cant save inside a readonly=False parent method. That is how it can save as child.
But when i send the case to throw exception, it throws exception. It goes to finally block then save method. But after that, it rolls back
I see those logs:
CheckForFksLimitations saving fksControlLog: {"id":null,
CheckForFksLimitations saved fksControlLog: {"id":91,
CheckForFksLimitations ended for identityNumber: ARJUNA016129: Could not end XA resource com.ibm.db2.jcc.t4.a4#2a5410b8 com.ibm.db2.jcc.am.XaException: [jcc][t4][10401][12066][4.24.92] Xa exception: XA_RBROLLBACK ERRORCODE=-4228, SQLSTATE=null
It is oracle db.
I did not put any rollback class for exception. It is because of this?
I also put exception to parent signatures but did not work. This service called by a controller.

Spring batch update db status after rollback due to exception

In Spring batch Writer I'm updating the db row status from 0 to 1. If any exception occurs update to 2.
However due to #transaction rollback I'm unable to update the status to 2.
(I'm throwing exception to trigger the rollback)
#Override
#Transactional
public void write(List<? extends TestEntity> enityList) throws Exception {
for(TestEntity testEntity : enityList) {
try {
testEntity.setStatus(2);
testRepository.save(testEntity);
testRepository.flush();
testMethod(testEntity); (which throws exception)
}catch (Exception exception) {
testEntity.setStatus(2);
testRepository.save(testEntity);
}
}
}
#Transactional
public void testMethod(TestEntity testEntity) throws Exception {
try{
//Some service call
//...
} catch(Exception e) {
log.error("error", e);
throw new Exception("exp");
}
}
Methods that have the #Transactional will rollback the transaction when they throw an exception. So if an exception is an expected and okay-ish flow of your code, you shouldn't throw an exception and return some kind of result object or status code instead.
#Transactional
public void testMethodThatIsAllowedToFail(TestEntity testEntity) {
try{
//Some service call
} catch(Exception e) {
return Status.FAILURE; // enum you have to create
}
return Status.SUCCESS;
}
// spring batch writer
public void write(List<? extends TestEntity> enityList) throws Exception {
[...]
Status result = testMethod(testEntity); (which throws exception);
if (result != Status.SUCCESS) {
// do something with it
}
[...]
}
you could also play around with #Transactional(propagation = Propagation.REQUIRES_NEW) but you would have to think hard whether having an extra transaction is desireable.

PubSub with spring: know the message is publish or not?

My publisher code look like this:
public abstract class PubSubPublisher {
private static final Logger LOGGER = LoggerFactory.getLogger(PubSubPublisher.class);
private final PubSubTemplate pubSubTemplate;
protected PubSubPublisher(PubSubTemplate pubSubTemplate) {
this.pubSubTemplate = pubSubTemplate;
}
protected abstract String topic(String topicName);
public void publish(String topicName, String message) throws StatusRuntimeException {
LOGGER.info("Publishing to topic [{}]. Message: [{}]", topicName, message);
pubSubTemplate.publish(topicName, message);
}
}
My Component
#Component
public class HelloPubSubPublisher extends PubSubPublisher {
#Autowired
public HelloPubSubPublisher(PubSubTemplate pubSubTemplate) throws StatusRuntimeException{
super(pubSubTemplate);
}
#Override
protected String topic(String topicName) {
return topicName;
}
}
Now on my service layer how do i get weather i successful publish the message to topic or not, note all the google api are async which i am using.
try {
publisher.publish(topicName, payload);
}catch (Exception e) {
LOGGER.error("ioException occured: "+e);
throw new TopicNotFoundException();
}
Unfortunately, I am not able to capture the any error, program cursor is not going into the catch block.
Ultimately, I wanted to know weather the code is push the message into topic if not then I have to log it and throw that error to client, which is not happen with my current code with proper exception handling.
Any help or guidance is appreciated, thanks.
Using the function publish() you should be able to capture a future where you can check if the message was published or not.
You have an example of it on Google's PubSub documentation:
// Once published, returns a server-assigned message id (unique within the topic)
ApiFuture<String> future = publisher.publish(pubsubMessage);
// Add an asynchronous callback to handle success / failure
ApiFutures.addCallback(
future,
new ApiFutureCallback<String>() {
#Override
public void onFailure(Throwable throwable) {
if (throwable instanceof ApiException) {
ApiException apiException = ((ApiException) throwable);
// details on the API exception
System.out.println(apiException.getStatusCode().getCode());
System.out.println(apiException.isRetryable());
}
System.out.println("Error publishing message : " + message);
}
#Override
public void onSuccess(String messageId) {
// Once published, returns server-assigned message ids (unique within the topic)
System.out.println(messageId);
}
},
MoreExecutors.directExecutor());

Resources