MDB Failure Handling: Difference between revisions

From NovaOrdis Knowledge Base
Jump to navigation Jump to search
No edit summary
 
(34 intermediate revisions by the same user not shown)
Line 13: Line 13:
=Failure Handling Specification=
=Failure Handling Specification=


JSR 318 Enterprise JavaBeans Version 3.1 EJB Core Contract and Requirements, Section 5.4.18 "Dealing with Exceptions" mentions that MDBs should not throw RuntimeExceptions. If a RuntimeExceptions occurs, the container will transition the MDB in the "does not exist" state. The message will not be acknowledged, and if messages arrive to the destination, the container can delegate the message to another MDB instance.
JSR 318 Enterprise JavaBeans Version 3.1 EJB Core Contract and Requirements, Section 5.4.18 "Dealing with Exceptions" mentions that MDBs should not throw RuntimeExceptions. If a RuntimeExceptions that is '''not''' an application exception is thrown by the MDB business logic, the container will transition the MDB instance that triggered the exception in the "does not exist" state. If the MDB uses bean-managed transaction demarcation, the container should not acknowledge the message. From the client perspective, the message consumer continues to exist. If the client continues to send messages to the destination associated with the container, the container can delegate the message to another MDB instance.


=WildFly/HornetQ Behavior on RuntimeException=
=WildFly/HornetQ-Backed MDB Container Behavior on Failure=


==Message Delivery Occurs in a Container-Managed Transactional Context==
The behavior of the WildFly/HornetQ-Backed MDB container in presence of a message processing failure depends on the transactional context (container manager or bean managed) and whether the error materializes as an unchecked application exception or a generic unchecked exception. The MDB's onMessage() method cannot throw checked exceptions.


If the message delivery occurs in a container-managed transactional context, the HornetQ resource adapter ''acknowledges the message'' when it first receives from the HornetQ JMS provider. This behavior can be thought as "removing the message from the queue". The resource adapter attempts to deliver the message in a loop until either it is consumed by an MDB instance or the message is sent to the DLQ.  
The most common situation is an MDB configured to use container managed transactions, with the transactional attribute set to REQUIRED. The message delivery and processing is enclosed by a JTA transaction that is started by the MDB container. Upon starting the transaction, the MDB container enlists the XAResource exposed by the JMS provider with the transaction. The container's transaction interceptor simply takes note of the presence of the JTA transaction and proceeds with the invocation with the "caller" transaction.  
<font color=red>Who loops and Who sends the message to DLQ?</font>


<font color=red>Does this mean that if the JVM fails while the message is processed in the loop, the message is lost?</font>
If the MDB business code throws a non-application unchecked exception, the JTA transaction started by the MDB container is automatically rolled back: the MDB container's transactional interceptor catches the RuntimeException and rolls back the transaction, by invoking <tt>javax.transaction.Transaction.setRollbackOnly()</tt>. The MDB instance that triggered the failure is destroyed, as required by the specification. The @PreDestroy callback, if exists, is invoked, and the instance is discarded from the pool - if there is any subsequent message delivery, new MBD instances will be created to handle those messages. Since the message delivery transaction is rolled back, the message becomes available for delivery again, and it is re-delivered by the built-in HornetQ mechanism, described [[WildFly_HornetQ_Message_Redelivery_on_Failure_and_the_Dead_Letter_Queue#Overview|here]].


The current transaction is automatically rolled back. The MDB container's transactional interceptor catches the RuntimeException and rolls back the transaction, by invoking <tt>javax.transaction.Transaction.setRollbackOnly()</tt>.
If the MDB business code throws an [[EJB_Concepts#Application_Exception|application unchecked exception]], the transactional interceptor rollbacks the message delivery transaction only if the application exception is declared with "rollback=true", and does not interact with the transaction in any way if "rollback=false", just propagates the exception up the stack. The MDB instance is not destroyed and not discarded from pool. However, the MDB container layer that did start the delivery transaction rolls it back, and the message becomes available for delivery again.


The MDB instance that triggered the failure is destroyed. The @PreDestroy callback, if exists, is invoked, and the instance is discarded from the pool. Upon re-delivery, if any, a new instance to handle the message will be created.
If the MDB uses container managed transactions but the transactional attribute is set to NOT_SUPPORTED, <font color=red>TODO</font>.


==Message Delivery Occurs in a Non-Transactional Context==
If the MDB uses bean managed transactions, <font color=red>TODO</font>.


=How to Handle Failure in a Transactional Context=
=How to Handle MDB Processing Failure in a Transactional Context=


=Redelivery=
Since the <tt>MessageListener.onMessage()</tt> method does not declare any exception, the semantically cleanest approach is probably the following:


The message redelivery count on failure is ''not'' an attribute of an individual MDB container, as the "" configuration attribute seems to suggest, but of the HornetQ destination.
<pre>
@Override
public void onMessage(Message message) {


=How to Handle Failure in a Non-Transactional Context=
    try {
 
        //
        // the execution of the business logic takes place within the context of a JTA "message delivery"
        // transaction
        //
 
        //
        // process the message and interact with transactional resources
        //
 
        TextMessage tm = (TextMessage)message;
 
        String result = businessLogic(tm.getText());
 
        writeToDatabase(result);
 
    }
    catch(MyApplicationException e) {
 
        //
        // this is an application exception thrown by our application code; we need to decide whether
        // to rollback the transaction or not, it's an application-specific decision
        //
    }
    catch(Exception e) {
 
        //
        // declare that the message was not processed and it has to be "put back" on the queue
        //
 
        context.setRollbackOnly();
    }
}
</pre>
 
Full example:
 
{{External|https://github.com/NovaOrdis/playground/blob/master/jee/ejb/mdb-failure-handling/src/main/java/io/novaordis/playground/jee/ejb/mdb/FailureHandlingExampleMDB.java}}
 
Note that message re-delivery is handled by the container, as described below.
 
=Redelivery and Dead Letter Queue Configuration=
 
HornetQ automatically redelivers unacknowledged messages, subject to configuration. The message redelivery count on failure is ''not'' an attribute of an individual MDB container, as the "[[MDB#dLQMaxResent|dLQMaxResent]]" and other DLQ-related activation configuration attributes seem to suggest, but of the HornetQ destination the container is associated with. In EAP 6.4.10, "[[MDB#dLQMaxResent|dLQMaxResent]]" is ignored. If an MDB fails to process a message and the message is deemed as not acknowledged, the default behavior of the JMS provider is to attempt redelivery, subject to redelivery configuration of the specific destination. More details are available here: {{Internal|WildFly_HornetQ_Message_Redelivery_on_Failure_and_the_Dead_Letter_Queue|Message Redelivery on Failure and the Dead Letter Queue}}

Latest revision as of 20:51, 25 April 2017

Internal

Relevance

EAP 6.4.10

Overview

This article addresses failure handling in an MDB context. It was written while experimenting with EAP 6.4 and a HornetQ-based messaging subsystem.

Failure Handling Specification

JSR 318 Enterprise JavaBeans Version 3.1 EJB Core Contract and Requirements, Section 5.4.18 "Dealing with Exceptions" mentions that MDBs should not throw RuntimeExceptions. If a RuntimeExceptions that is not an application exception is thrown by the MDB business logic, the container will transition the MDB instance that triggered the exception in the "does not exist" state. If the MDB uses bean-managed transaction demarcation, the container should not acknowledge the message. From the client perspective, the message consumer continues to exist. If the client continues to send messages to the destination associated with the container, the container can delegate the message to another MDB instance.

WildFly/HornetQ-Backed MDB Container Behavior on Failure

The behavior of the WildFly/HornetQ-Backed MDB container in presence of a message processing failure depends on the transactional context (container manager or bean managed) and whether the error materializes as an unchecked application exception or a generic unchecked exception. The MDB's onMessage() method cannot throw checked exceptions.

The most common situation is an MDB configured to use container managed transactions, with the transactional attribute set to REQUIRED. The message delivery and processing is enclosed by a JTA transaction that is started by the MDB container. Upon starting the transaction, the MDB container enlists the XAResource exposed by the JMS provider with the transaction. The container's transaction interceptor simply takes note of the presence of the JTA transaction and proceeds with the invocation with the "caller" transaction.

If the MDB business code throws a non-application unchecked exception, the JTA transaction started by the MDB container is automatically rolled back: the MDB container's transactional interceptor catches the RuntimeException and rolls back the transaction, by invoking javax.transaction.Transaction.setRollbackOnly(). The MDB instance that triggered the failure is destroyed, as required by the specification. The @PreDestroy callback, if exists, is invoked, and the instance is discarded from the pool - if there is any subsequent message delivery, new MBD instances will be created to handle those messages. Since the message delivery transaction is rolled back, the message becomes available for delivery again, and it is re-delivered by the built-in HornetQ mechanism, described here.

If the MDB business code throws an application unchecked exception, the transactional interceptor rollbacks the message delivery transaction only if the application exception is declared with "rollback=true", and does not interact with the transaction in any way if "rollback=false", just propagates the exception up the stack. The MDB instance is not destroyed and not discarded from pool. However, the MDB container layer that did start the delivery transaction rolls it back, and the message becomes available for delivery again.

If the MDB uses container managed transactions but the transactional attribute is set to NOT_SUPPORTED, TODO.

If the MDB uses bean managed transactions, TODO.

How to Handle MDB Processing Failure in a Transactional Context

Since the MessageListener.onMessage() method does not declare any exception, the semantically cleanest approach is probably the following:

@Override
public void onMessage(Message message) {

    try {

        //
        // the execution of the business logic takes place within the context of a JTA "message delivery"
        // transaction
        //

        //
        // process the message and interact with transactional resources
        //

         TextMessage tm = (TextMessage)message;

         String result = businessLogic(tm.getText());

         writeToDatabase(result);

    }
    catch(MyApplicationException e) {

         //
         // this is an application exception thrown by our application code; we need to decide whether
         // to rollback the transaction or not, it's an application-specific decision
         //
     }
     catch(Exception e) {

         //
         // declare that the message was not processed and it has to be "put back" on the queue
         //

        context.setRollbackOnly();
     }
 }

Full example:

https://github.com/NovaOrdis/playground/blob/master/jee/ejb/mdb-failure-handling/src/main/java/io/novaordis/playground/jee/ejb/mdb/FailureHandlingExampleMDB.java

Note that message re-delivery is handled by the container, as described below.

Redelivery and Dead Letter Queue Configuration

HornetQ automatically redelivers unacknowledged messages, subject to configuration. The message redelivery count on failure is not an attribute of an individual MDB container, as the "dLQMaxResent" and other DLQ-related activation configuration attributes seem to suggest, but of the HornetQ destination the container is associated with. In EAP 6.4.10, "dLQMaxResent" is ignored. If an MDB fails to process a message and the message is deemed as not acknowledged, the default behavior of the JMS provider is to attempt redelivery, subject to redelivery configuration of the specific destination. More details are available here:

Message Redelivery on Failure and the Dead Letter Queue