I am trying to understand, how an exception thrown by an action during a state transition is possible. I‘ve this simple state machine configured:
transitions
.withExternal()
.source(State.A1)
.target(State.A2)
.event(Event.E1)
.action(executeAnActionThrowingAnException())
In my service class, I injected my state machine and send this event E1:
#Service
public class MyService() {
#Autowired
private StateMachine<State, Event> stateMachine;
public void executeMyLogic() {
stateMachine.start()
stateMachine.sendEvent(Event.E1);
// how to get thrown exception here
}
}
In my service I just want to know, if and why my state machine wasn‘t able to reached State.A2. Because a thrown exception is fetched by Spring state machine, I am not able to get any response after sending the event. But the state machine hasn‘t any error, which means that
stateMachine.hasStateMachineError()
will return false. So, how can I get the information in my service, that something went wrong and more importantly what?
I appriciate your help.
Best regards
For transitions exceptions, there's an overload for the actions method available in the TransitionConfigurer
action(Action<S,E> action, Action<S,E> error)
This means you can specify and additional action to be triggered, if there's an exception raised during the transition. The exception is available from the StateContext passed to the action.
When your error action is triggered, you can retrieve the exception with:
context.getException();
Inside the error action you can do a couple of things to deal with the exception:
do logging of the exception and the context
transition to some error state
clear the context and transition to the same state and try to execute some retry logic
add some additional info to the context and return the context to the caller
For example:
context.getVariables().put("hasError", true);
context.getVariables().put("error", ex);
And in your service(caller) you handle the exception as you like, for example:
public void executeMyLogic() {
stateMachine.start()
stateMachine.sendEvent(Event.E1);
if (stateMachine.getExtendedState().getVariables().containsKey("hasError") {
throw (RuntimeException)stateMachine.getExtendedState().getVariables().get("error")
}
}
Related
I have an Scheduler in spring boot that fulfils a specific business task every X minutes. It works fine until it suddenly stops and does not engage anymore. There is no exception in the logs or any other logs. I need to restart the program for the scheduler to work again.
Sometimes the task of the scheduler goes wrong, and I throw an exception. To be able to handle those exceptions specifically, I wrote a custom ErrorHandler in Spring for the scheduler that resolves a seperate task for logging purposes. It is linked correctly to the scheduler and processes the task.
This issue can come up when an unhandled exception gets thrown inside of an ErrorHandler. I am not sure about the specifics, however a Runtime Exception thrown by an ErrorHandler (or a method inside of it) that gets propagated outside of it basically kills the scheduled thread for that task. Furthermore NOTHING gets written to the logs (no Exception message, nada).
The "easiest" way to resolve this is by wrapping the entirety of the method in a try/catch block catching Exception - although depending on why you have that Error Handler that might be a bad idea. This does not solve the underlying issue at hand, but it keeps the thread alive and allows you to log the issue.
Example:
public class MyErrorHandler implements ErrorHandler {
#Override
public void handleError(Throwable t) {
try {
//handle intended exception (ex. write to database or logs)
} catch (Exception e) {
//handle exception that was thrown while trying to handle the intended exception.
}
}
I have a class:
#Service
#Transactional
class MyService{
#Autowired MyTableRepository repository;
#Autowired FacebookMessageSender sender;
public void updateTableAndSendMessage(MyTable m){
m.setProcessed(1);
repository.save(m);
sender.sendMessageToFacebook(m);
}
}
Somewhere in the code:
List<MyTable> list=repository.findByProcessed(0);
for(MyTable m:list){
myService.process(m);
}
So, I have the following:
In the quartz job I retrieve all records with processed flag set to 0. Then I pass it to service, then processed flag becomes 1 and system sends message to Facebook messenger (wherever). But, if there's a sql exception occurs, before or after 'process' method execution, transaction will be rolled back and flag will be still 0. So Facebook message will be sent on next job launch, and again and again. I tried to break it down to 2 methods, one saves flag other sends message. But then what if message was not sent and I will have to rollback the transaction? So it's like deadlock. I need prevent FB message from being send on other exception and rollback DB changes on FB sending failure. How to do that in spring-data, afaik transaction will be committed after method ends. Thanks
The following code structure should work:
Service class
#Service
class TaskService {
#Autowired TaskRepository repository;
#Autowired FacebookMessageSender sender;
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void sendTaskNotification(Task task){
try {
task.setProcessed(1);
repository.save(task);
sender.sendMessageToFacebook(task);
}
catch (Throwable t) {
throw new RuntimeException(t);
}
}
}
Caller class
#Component
class TaskNotificationJob {
#Autowired TaskService service;
#Scheduled
#Transactional
public void sendTaskNotifications() {
for(Task task : repository.findByProcessed(0)) {
try {
service.sendTaskNotification(task);
}
catch (RuntimeException e) {
// Log the error, but don't rethrow the exception.
}
}
}
}
Explanation
a. Ensure that a Facebook message is not attempted if task cannot be saved
If a single task cannot be saved, an exception will be raised and the code will skip the part that attempts to send a Facebook message for the task. Therefore, this requirement is automatically met due to the way the code is structured.
b. Ensure that an error while sending a Facebook message rolls back the task
The lines catch(Throwable t) { throw new RuntimeException(t); } ensure that if any exception (including one that derives from Exception instead of RuntimeException) is thrown while either saving a task or sending a Facebook message for it, it gets re-thrown as a RuntimeException. This in turn will ensure that the #Transactional annotation surrounding the method will roll the database transaction back.
c. Ensure that an error while processing a task does not affect other tasks
Each task is processed in its own transaction (#Transactional(propagation = Propagation.REQUIRES_NEW)) so anything that gets committed before an error occurs, stays committed.
When an error occurs, the calling code catches it (catch (RuntimeException e)) so that the error does not bubble up to the transaction handler for the caller and caller is allowed to attempt and process remaining tasks, as required. Not having this catch clause would mean that the first RuntimeException raised in the service will also bubble up to the caller and will terminate the whole operation immediately, potentially leaving unprocessed tasks. It is important not to re-throw the caught exception at this point because re-throwing it would bubble it up and hence defeat the purpose of having the catch clause in the first place.
Application declares interface for sending notifications with single method with ListenableFuture<> return type.
For mail service I don't see exceptions in case of misconfiguration (for example if SMTP server is down or host is not resolved) in my batch job.
Debugging shows that on:
MailNotificationService mailService = applicationContext.getBean(MailNotificationService.class);
I receive proxy that on method invocation:
mailService.send(mime);
resolved with stack:
at org.springframework.aop.interceptor.AsyncExecutionInterceptor.invoke(AsyncExecutionInterceptor.java:101)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:208)
Documentation for invoke says:
* Intercept the given method invocation, submit the actual calling of the method to
* the correct task executor and return immediately to the caller.
After some steps I see new thread SimpleAsyncTaskExecutor-1 and service executed here.
It looks like service executed in separate thread and exception doesn't propagated to original thread (if it is meaningful at all).
Is it correct that for bean methods with ListenableFuture<> return type Spring execute them in separate thread?
NB My original issue in that I am blind in case of errors in notification service (traces are not logged). Mail service throws unchecked org.springframework.mail.MailException and the only way to find that out is to wrap .send() method by Exception with logging:
#Autowired
private JavaMailSender mailSender;
public void notify() {
try {
mailSender.send(mime);
} catch (Exception ex) {
log.warn("Can't deliver mail", ex);
}
}
As white predicted I have #Async annotation on service method and #EnableAsync on configuration class.
Exception handling described in section:
http://docs.spring.io/spring-framework/docs/current/spring-framework-reference/htmlsingle/#scheduling-annotation-support-exception
In case of Future return type (ListenableFuture is subtype of it) exception propagated to future object and can be retrieved from exception ExecutionException.getCause() which occur on Future.get() method call.
In my case I ignore return type (don't call .get()). So exception was not captured and logged. As I wrote originally excpetion logging should be done in task itself in this case.
https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ExecutionException.html
Consider the following code snippet. (I am using Spring 3.1 and Hibernate 3.6)
#Override
#Transactional
public <T extends Termination> void progressToPendingStage(Class<T> entity,
Long terminationId, String userName) throws Exception {
Termination termination = findTerminationById(entity, terminationId);
//TODO improvise such that email does not get sent if data is not saved
if (termination.getStatus().equals(TerminationStatus.BEING_PREPARED.toString())) {
termination.setStatus(TerminationStatus.PENDING.toString());
termination.setSubmittedDate(new Date());
termination.setSubmittedBy(userName);
saveOrUpdateTermination(termination);
//Send an email to SAS
emailHelper.configureEmailAndSend(termination);
}
}
Unit tests for the above method indicate that email will be sent regardless that the saveOrUpdateTermination(termination) throws an exception or not. On further testing and some research I have uncovered that this behavior is the expected behavior. This is not what the business rules desire. An email should be sent only if the termination record was saved successfully. Any suggestions on how to make this behave in the desired manner? One way I can think of is to make the caller handle the exception thrown by the progressToPendingStage method and if no exception was thrown send an email. Am I on the right track or can we alter the way #Transaction behaves.
I have solved this issue by designing around the problem. Sending an Email was never meant to be part of the transaction. I created an object that performed post saving tasks. The object will catch the exception thrown upon saving the termination and if no exceptions were thrown I would then trigger an email to be sent out. One could also put this in an Spring Aspect which could be executed upon successfully returning after a successful save.
Lessons learn't: Don't include steps that don't belong in a method marked with #transaction. If its included in a transaction Spring will silently handle the exception and not throw the exception till the transaction is finished. In short if a method is annotated with #Transaction every line in that method will be execute even though a line in the middle of the method throws an exception.
How must I handle exceptions inside a mdb? I have the funny feeling that the exception happens after the try catch block so I'm not able to catch and log it. Glassfish v3 decides to repeat the whole message. It runns into a infinite loop and writes lot's of logfiles on the harddrive.
I'm using Glassfishv3.01 + Eclipselink 2.0.1
public class SaveAdMessageDrivenBean implements MessageListener {
#PersistenceContext(unitName="QIS")
private EntityManager em;
#Resource
private MessageDrivenContext mdc;
public void onMessage(Message message) {
try {
if (message instanceof ObjectMessage) {
ObjectMessage obj = (ObjectMessage)message;
AnalyzerResult alyzres = (AnalyzerResult)obj.getObject();
save(alyzres);
}
} catch (Throwable e) {
mdc.setRollbackOnly();
log.log(Level.SEVERE, e);
}
}
#TransactionAttribute(TransactionAttributeType.REQUIRED)
private void save(AnalyzerResult alyzres) throws PrdItemNotFoundException {
Some s = em.find(Some.class, somepk);
s.setSomeField("newvalue");
// SQL Exception happens after leaving this method because of missing field for ex.
}
}
You got a bad case of message poisoning...
The main issues I see are that:
you are calling directly the save() method in your onMessage(): this means thet the container has no way to inject the proper transaction handling proxy around the save method
in any case the save() method should have #TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) in order to commit in a separate transaction, otherwise it will join the onMessage transaction (which default to REQUIRED) and bypass your exception handling code, beign committed after the successful execution of onMessage
What I woud do is:
Move the save method to a new Stateless session bean:
#Stateless
public class AnalyzerResultSaver
{
#PersistenceContext(unitName="QIS")
private EntityManager em;
#TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
private void save(AnalyzerResult alyzres) throws PrdItemNotFoundException {
Some s = em.find(Some.class, somepk);
s.setSomeField("newvalue");
// SQL Exception happens after leaving this method
}
}
Inject this bean in your MDB:
public class SaveAdMessageDrivenBean implements MessageListener {
#Inject
private AnalyzerResultSaver saver;
#Resource
private MessageDrivenContext mdc;
public void onMessage(Message message) {
try {
if (message instanceof ObjectMessage) {
ObjectMessage obj = (ObjectMessage)message;
AnalyzerResult alyzres = (AnalyzerResult)obj.getObject();
saver.save(alyzres);
}
} catch (Throwable e) {
mdc.setRollbackOnly();
log.log(Level.SEVERE, e);
}
}
}
Another tip: in this code the message poisoning still exists. Now it derives from the line invoking mdc.setRollbackOnly();.
I'd suggest here to log the exception and transfer the message to a poison queue, thus preventing the container to resubmit the message ad infinitum.
UPDATE:
A 'poison queue' or 'error queue' is simply a mean to guarantee that your (hopefully recoverable) discarded messages will not be completely lost. It is used heavily in integration scenarios, where the correctness of the message data is not guaranteed.
Setting up a poison queue implies defining a destination queue or topic and redeliver the 'bad' messages to this destination.
Periodically, an operator should inspect this queue (via a dedicated application) and either modify the messages and resubmit to the 'good' queue, or discard the message and ask for a resumbit.
I believe that the code that you have posted is mostly OK.
Your use of
#TransactionAttribute(TransactionAttributeType.REQUIRED)
is completely ignored because this (and most other) annotations can only be applied to business methods (including onMessage). That doesn't matter though because your onMessage method gets an implicit one for free.
This leads to the fact that message handling is transactional in a Java EE container. If the transaction fails for any reason the container is required to try and deliver the message again.
Now, your code is catching the exception from the save method, which is good. But then you're explicitly marking the transaction for rollback. This has the effect of telling the container that message delivery failed and that it should try again.
Therefore, if you remove:
mdc.setRollbackOnly();
the container will stop trying to redeliver the message.
If I'm not mistaken, you're letting the container handle the transactions. This way, the entity manager will queue the operations that will be flushed after the method finishes, that's why you're having exceptions after the method is finished.
Using em.flush() directly as a final step in the method will execute all the related queries of the transaction, throwing the exceptions there instead of being thrown later when the flush() is made by the container while commiting the transaction.