9 Replies Latest reply on May 18, 2005 11:18 AM by adrian.brock

    Acknowledgment handling in the core

    ovidiu.feodorov


      Tim wrote:



      Further to the discussion regarding asynchronous ACKs, here are some thoughts on how we can implement QueueBrowsing on the core classes and how it all works together.

      The assumption here is that we want to be able to browse any messages in a queue where delivery has not been attempted. We don't want to browse those messages where the message has been delivered but the JMS client has NACKed it.

      Currently the core classes don't allow us to distinguish those messages that have not had delivery attempted and those that have.

      A proposal would be modify the return value of Receiver.handle() from a boolean to one of three states:

      ACK - the message has been handled sychronously and acknowledged.
      NACK-DeliveryAttempted - the message is not (yet) acknowledged but delivery is being attempted.
      NACK-DeliveryNotAttempted - the message is not acknowledged and delivery has not been attempted.

      The acknowledgement store would also be modified to allow us to distinguish between NACK-DeliveryAttempted and NACK-DeliveryNotAttempted. I.e. it needs to store the tuple <receiver_id, message_id, nack_type (- one of delivery attempted or delivery not attempted)>

      For the case of a queue:

      If the queue has no consumers and messages arrive, nacks are stored with state NACK-DeliveryNotAttempted and the messages are stored.

      The QueueBrowser will only browse NACKed messages with state NACK-DeliveryNotAttempted, so it sees these messages.

      A consumer is now added to the queue, causing deliver() to be triggered. Messages with either of the two NACK states are sent for delivery. The consumer starts to accept messages for delivery, returning NACK-DeliveryAttempted to the router.

      A QueueBrowser will not see the messages with state NACK-DeliveryAttempted.


        • 1. Re: Acknowledgment handling in the core
          ovidiu.feodorov

          The problem described by Tim is indirectly related to the way a Router handles acknowlegments. Actually, there are two distinct problems that need to be solved

          1. A channel cannot distinguish between messages that have been delivered and NACKed and messages that haven't been delivered at all. We need this distinction to implement channel browsers.

          2. Because a Router is currently a Receiver, its handle() method can only return a binary result (ACK/NACK) which is certainly not sufficient to express combinations of ACKs/NACKs from multiple receivers, in the case of a PointToMultipointRouter, for example.


          Tim proposed to solve issue 1 by having three acknowledgment types . This would require changes throughout the core. I also think three acknowledgment types are not necessary, since NACK-DeliveryNotAttempted is a state that shouldn't propagate back on the receiver chain. We don't need to be able to browse all channels along a delivery path, we only need to be able to browse the last one, that actually keeps the message in question.


          Here is another proposal that addresses 1. and 2. and it only requires localized changes. We still only have two types of acknowledgments: ACKs (true) and NACKs (false).

          As originally designed, a Router is a synchronous component. It returns a NACK to the component that initiated delivery, if the delivery is not successful, but it does not hold messages and it does not attempt redelivery. In this respect, is not a Channel. To address 1. and 2. its interface should changes as follows

          public interface Router extends Distributor
          {
           public Serializable getRouterID();
          
           public Serializable[] handle(Routable);
           public Serializable[] handle(Routable, Serializable[]);
          
           public boolean isPassByReference();
          }
          


          A Router is not a Receiver anymore, since the Receiver interface is not semantically rich enough.

          The handle() method behaves as follows: the sender attempts delivery using handle(Routable) or handle(Routable, null). The Router synchronously delivers the Routable to the receivers it chooses (all receivers for a PointToMultipointRouter or only one receiver for a PointToPointRouter) and returns the array containing ReceiverIDs of receivers that NACKed. This way, the sender can store the NACKs and attempt redelivery later using handle(Routable, Serializable[]).

          If the Router has no receivers, handle() only returns a single element array containing the Router's ID. This way, the sender knows the message was not delivered in the first place (this is the equivalent to Tim's NACK-DeliveryNotAttempted). The pipe that calls into the Router stores the message and it's also able to distinguish between NACKed messages and non-delivered messages.

          Browsing the queue would mean quering the AcknowledgmentStore for all messages that have been NACKed by the Router and not by individual Receivers.

          • 2. Re: Acknowledgment handling in the core

            Tim wrote:



            Further to the discussion regarding asynchronous ACKs, here are some thoughts on how we can implement QueueBrowsing on the core classes and how it all works together.

            The assumption here is that we want to be able to browse any messages in a queue where delivery has not been attempted. We don't want to browse those messages where the message has been delivered but the JMS client has NACKed it.


            That assumption is NOT correct. If the message has been NACKed it is in the queue
            again and can be browsed just like any other message in the queue.

            • 3. Re: Acknowledgment handling in the core
              timfox

               


              That assumption is NOT correct. If the message has been NACKed it is in the queue
              again and can be browsed just like any other message in the queue.


              That's good... So we just browse any NACK. That should make thngs considerably simpler.

              We've come full circle back to the original design approach I posted but along the way I've learnt quite a lot about core, so that's good :)





              • 4. Re: Acknowledgment handling in the core
                ovidiu.feodorov

                 


                That assumption is NOT correct. If the message has been NACKed it is in the queue again and can be browsed just like any other message in the queue.


                JMS Specification is very vague about this:


                Section 5.9

                A client uses a QueueBrowser to look at messages on a queue without removing them. A QueueBrowser can be created from a Session or a QueueSession.

                The browse methods return a java.util.Enumeration that is used to scan the queue?s messages. It may be an enumeration of the entire content of a queue, or it may contain only the messages matching a message selector.

                Messages may be arriving and expiring while the scan is done. JMS does not require the content of an enumeration to be a static snapshot of queue content. Whether these changes are visible or not depends on the JMS provider.


                Nowhere in this section is explicitely specified whether the "entire content of the queue" consists in messages that have not been delivered AND messages that have been delivered and NACKed, or only messages that haven't been delivered.

                JBossMQ's QueueBrowsers do not return NACKed messages, only undelivered messages.

                At this point, it is not very clear to me what a browser should return, actually. We could have this as a configuration option, I believe.



                • 5. Re: Acknowledgment handling in the core

                  All that is saying is that the jms implementation is NOT required to give
                  a synchronized snapshot and that it is not transactional.

                  In other words, unless you have no concurrent producers/consumers
                  (and no message expiration/scheduled delivery) the
                  messages returned by the browser are entirely unpredictable.

                  e.g.

                  Enumeration e = browser.getEnumeration();
                  Message m1 = receiver.receive();
                  print(e);
                  


                  Will it show m1 in the enumeration? Who knows? It is implementation defined.

                  • 6. Re: Acknowledgment handling in the core
                    ovidiu.feodorov

                    Right.

                    And that's why JBossMQ's browsers do not show messages currently in queue but being in process of being acknowledged, while Reference Implementation's browsers do.

                    Tim, what about a Channel.browse() method? Your QueueBrowser will rely on Channel's behavior. Later we can even make this behavior configurable in the Queue's deployment descriptor.

                    • 7. Re: Acknowledgment handling in the core
                      timfox

                      We _could_ do this either way and make it configurable by the user.

                      However, discriminating the two types of NACKs adds significant complexity to the implementation (it prompted this whole discussion) including changes to the core interfaces.

                      My gut feeling is to keep it simple unless we really think the new feature would add value for the user.

                      I.e. perhaps we should ask the quesion: Would a user really care about discriminating those messages that have never been delivered and those that have been NACKed by the channel's receivers?

                      I'm sure there probably is a use case where this distinction is important - but I can't think of one right now.

                      My feeling is that in most cases JMS users just want to have a rough idea of what's on the queue and hence what's going to be (re)delivered when the consumer/durables subcriber attaches.

                      (I add durable subscriber here - since even though it's not part of JMS spec, browsing durable subscribers is something we could add, that does add value IMO... but that's a different discussion).

                      Moreover, as Adrian has pointed out, the JMS spec makes no guarantees as to the consistency/"staticness" so anyone building a JMS application that relies on some extra knowledge of what's in the queue other than what's guaranteed by the spec. would be non-portable between JMS implementations.

                      If it really is important for JMS Users to distinguish messages that have been never delivered/messages that have been delivered once etc., then another approach could be to use a jmsx message property, e.g. delivery_count which gets incremented every-time a delivery is attempted.

                      The JMS user could then just filter on this value via a message selector when they do the browse.

                      This should allow them to discriminate between these messages, and would require no changes to the core class interfaces.

                      I think this approach is used by several jms implementations (i'm not sure about this but I just googled) including websphere and activemq.


                      • 8. Re: Acknowledgment handling in the core
                        genman


                        There's a number of JBoss specific properties to the 3.2/4.0, one of which includes the number of delivery attempts. (This is used primarily by the MDB code, so that when the number of attempts reaches 10 the message goes to the DLQ.)

                        • 9. Re: Acknowledgment handling in the core

                           

                          "timfox" wrote:

                          I.e. perhaps we should ask the quesion: Would a user really care about discriminating those messages that have never been delivered and those that have been NACKed by the channel's receivers?

                          I'm sure there probably is a use case where this distinction is important - but I can't think of one right now.


                          Besides genman's comment there is the spec defined:
                          javax.jms.Message.getJMSRedelivered()