viernes, 7 de marzo de 2014

Notes about Oracle AQ default exception queues


  • All AQ Queues have a default associated exception queue where messages that "may not be consumed within given constraints" are moved.
  • By default the exception queue name is called AQ$_[ORIGINAL_QUEUE_NAME]_E. For example, if the original normal queue is called MYQUEUE, its default exception queue is called AQ$_MYQUEUE_E. I notice that for example, in SQL Developer, I can't see the exception queue in the "Queues" tree (but it "exists"). Also by default, the default exception queues are not enabled for dequeuing. Yoy can use this instruction to enable dequeuing: dbms_aqadm.start_queue(queue_name=>'MYQUEUE', enqueue=>FALSE,dequeue=>TRUE). You can't enable an exception queue for enqueuing. Here is additional information about the exception queues.
  • By default, both queues (the normal queue and the exception queue), are supported by the same queue table. You can check it by directly querying the queue table (MYQUEUETABLE) and looking the Q_NAME column or by querying the queue table AQ view (AQ$MYQUEUETABLE) and looking the QUEUE column (this is the prefered way). The value for the "expired" messages is AQ$_MYQUEUE_E and the value for the normal messages is MYQUEUE.
  • You can use an "user-defined exception queue", but it is set through a message header; that is, at message level and not at queue level. At the message enqueue time, you can set the exception queue for that message through a header (JMS_OracleExcpQ); if that message expires, it will be moved to that user-defined exception queue. Here, you can check some good instructions to use it in PL/SQL or Java JMS.
Here are some references in My Oracle Support:

Using JMS message selectors in a message consumer using an Oracle AQ (exception) queue

I was testing JMS selectors in a Message Consumer based on an Oracle AQ (exception) queue; I needed to dequeue only the messages with certain message ID's; at first, I tried with this syntax, but found an unexpected behavior:

"JMSMessageID='ID:XXX' OR JMSMessageID='ID:YYY'"

For example, let's say we have this messages in the queue (this specific messages are in the queue's default exception queue):


If I use the following selector,

String selector = 
  "JMSMessageID='ID:4A3966F3C0604B6DAED6D6E7FC62705A' OR 
   JMSMessageID='ID:01AF9624372C4D8A88CFAFD37D4395E7' OR
   JMSMessageID='ID:42C32FA9BBCB48ABA074EB69C94D1A82' OR
   JMSMessageID='ID:4973147F8AC54BD1A5DA2CE81F9622CD'";

the first receive() works OK, returning one of the matching messages (consistently, the first in the selector):

[TestServlet][doGet]selector=JMSMessageID='ID:4A3966F3C0604B6DAED6D6E7FC62705A' OR JMSMessageID='ID:01AF9624372C4D8A88CFAFD37D4395E7' OR JMSMessageID='ID:42C32FA9BBCB48ABA074EB69C94D1A82' OR JMSMessageID='ID:4973147F8AC54BD1A5DA2CE81F9622CD
[TestServlet][doGet]messageID=ID:4A3966F3C0604B6DAED6D6E7FC62705A

But, from the second, and so on, the receive() method behaves as if there aren't messages matching the selector. In the end, just one message got processed:


I enabled the jdbc and jms debug on the managed server (I'm working on Weblogic 12c), and found this, starting from the second receive(): "java.sql.SQLException: ORA-25263: no message in queue PRUEBAS.AQ$_MYQUEUETABLE_E with message ID 4A3966F3C0604B6DAED6D6E7FC62705A".

[[oracle.jdbc.driver.T4CXAConnection@29cccbf, owner=[weblogic.jdbc.wrapper.JTAConnection_weblogic_jdbc_wrapper_XAConnection_oracle_jdbc_driver_LogicalConnection-myQueuePruebas_DS-17, oracle.jdbc.driver.LogicalConnection@29d1486], rmConn=oracle.jdbc.driver.LogicalConnection@29d1486]] dequeue(unknown) throws: java.sql.SQLException: ORA-25263: no message in queue PRUEBAS.AQ$_MYQUEUETABLE_E with message ID 4A3966F3C0604B6DAED6D6E7FC62705A
        at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:445)
        at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:389)
        at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:382)
        at oracle.jdbc.driver.T4Caqdq.processError(T4Caqdq.java:488)
        at oracle.jdbc.driver.T4CTTIfun.receive(T4CTTIfun.java:450)
        at oracle.jdbc.driver.T4CTTIfun.doRPC(T4CTTIfun.java:192)
        at oracle.jdbc.driver.T4Caqdq.doOAQDQ(T4Caqdq.java:180)
        at oracle.jdbc.driver.T4CConnection.doDequeue(T4CConnection.java:3976)
        at oracle.jdbc.driver.PhysicalConnection.dequeue(PhysicalConnection.java:10106)
        at oracle.jdbc.driver.PhysicalConnection.dequeue(PhysicalConnection.java:10153)
        at oracle.jdbc.OracleConnectionWrapper.dequeue(OracleConnectionWrapper.java:1525)
        at weblogic.jdbc.wrapper.XAConnection_oracle_jdbc_driver_LogicalConnection.dequeue(Unknown Source)
        at weblogic.jdbc.wrapper.JTAConnection_weblogic_jdbc_wrapper_XAConnection_oracle_jdbc_driver_LogicalConnection.dequeue(Unknown Source)
        at oracle.jms.AQjmsConsumer.jdbcConnectionDequeueCheck25242(AQjmsConsumer.java:1713)
        at oracle.jms.AQjmsConsumer.jdbcDequeue(AQjmsConsumer.java:1295)
        at oracle.jms.AQjmsConsumer.receiveFromAQ(AQjmsConsumer.java:1035)
        at oracle.jms.AQjmsConsumer.receiveFromAQ(AQjmsConsumer.java:960)
        at oracle.jms.AQjmsConsumer.receiveFromAQ(AQjmsConsumer.java:938)
        at oracle.jms.AQjmsConsumer.receive(AQjmsConsumer.java:790)
        at test.PruebaConsumerSelectorServlet.doGet(PruebaConsumerSelectorServlet.java:55)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:731)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:844)
        at weblogic.servlet.internal.StubSecurityHelper$ServletServiceAction.run(StubSecurityHelper.java:242)
        at weblogic.servlet.internal.StubSecurityHelper$ServletServiceAction.run(StubSecurityHelper.java:216)
        at weblogic.servlet.internal.StubSecurityHelper.invokeServlet(StubSecurityHelper.java:132)
        at weblogic.servlet.internal.ServletStubImpl.execute(ServletStubImpl.java:338)
        at weblogic.servlet.internal.ServletStubImpl.execute(ServletStubImpl.java:221)
        at weblogic.servlet.internal.WebAppServletContext$ServletInvocationAction.wrapRun(WebAppServletContext.java:3284)
        at weblogic.servlet.internal.WebAppServletContext$ServletInvocationAction.run(WebAppServletContext.java:3254)
        at weblogic.security.acl.internal.AuthenticatedSubject.doAs(AuthenticatedSubject.java:321)
        at weblogic.security.service.SecurityManager.runAs(SecurityManager.java:120)
        at weblogic.servlet.provider.WlsSubjectHandle.run(WlsSubjectHandle.java:57)
        at weblogic.servlet.internal.WebAppServletContext.doSecuredExecute(WebAppServletContext.java:2163)
        at weblogic.servlet.internal.WebAppServletContext.securedExecute(WebAppServletContext.java:2089)
        at weblogic.servlet.internal.WebAppServletContext.execute(WebAppServletContext.java:2074)
        at weblogic.servlet.internal.ServletRequestImpl.run(ServletRequestImpl.java:1513)
        at weblogic.servlet.provider.ContainerSupportProviderImpl$WlsRequestExecutor.run(ContainerSupportProviderImpl.java:254)
        at weblogic.work.ExecuteThread.execute(ExecuteThread.java:256)
        at weblogic.work.ExecuteThread.run(ExecuteThread.java:221)

Am I doing somethig wrong? is this a bug? my test code is pretty straighfoward; something like this:

String selector = 
  "JMSMessageID='ID:4A3966F3C0604B6DAED6D6E7FC62705A' OR 
   JMSMessageID='ID:01AF9624372C4D8A88CFAFD37D4395E7' OR
   JMSMessageID='ID:42C32FA9BBCB48ABA074EB69C94D1A82' OR
   JMSMessageID='ID:4973147F8AC54BD1A5DA2CE81F9622CD'";
messageConsumer = session.createConsumer(queue, selector);
Message msg = null;
System.out.println("[TestServlet][doGet]selector=" + messageConsumer.getMessageSelector());
while((msg = messageConsumer.receive(10000)) != null) {          
if(msg != null) {
System.out.println("[TestServlet][doGet]messageID=" + msg.getJMSMessageID());
}
}

Fortunately, trying with other selector syntax, the test worked OK. The syntax is:

"JMSMessageID IN ('ID:XXX', 'ID:YYY')"

For example, having this messages in the queue


And this selector

String selector = 
  "JMSMessageID IN (
    'ID:ED976B4A41F241F594F570A8F2250D26'
'ID:724794DE2C4648AF9EF7EB1CE3B0BC22'
'ID:33CF68A695E04F6B89F1024F7DFE5925'
'ID:3E7059E3489C472096C58A833434893C')"

The test works as expected:

[TestServlet][doGet]messageID=ID:724794DE2C4648AF9EF7EB1CE3B0BC22
[TestServlet][doGet]messageID=ID:3E7059E3489C472096C58A833434893C
[TestServlet][doGet]messageID=ID:33CF68A695E04F6B89F1024F7DFE5925
[TestServlet][doGet]messageID=ID:ED976B4A41F241F594F570A8F2250D26

The final result in the table is:


I found some (un)related cases in My Oracle Support.
I found also an Hermes bug report discusing the same case:

martes, 4 de marzo de 2014

Weblogic: setRollbackOnly called on transaction

Exception: weblogic.transaction.internal.AppSetRollbackOnlyException: setRollbackOnly called on transaction.

This exception can happen when you invoke transactional methods after calling MessageDrivenContext.setRollbackonly():

For example, given this (default: container, required) transactional MDB:

@MessageDriven
public class PruebaMdbA implements MessageListener {
   
    @EJB
    private PruebaSessionNoIntA pruebaSessionNoIntA;

    @Resource
    MessageDrivenContext mdc;

    public void onMessage(Message message) {
        //...
    mdc.setRollbackOnly();
        //...
     pruebaSessionNoIntA.testTx();
    }

//...
}

And this (default: container, required) transactional session EJB:

@Stateless
@LocalBean
public class PruebaSessionNoIntA {
    public void testTx() {
     //...
    }
}

If you invoke the onMessage() MDB method, it starts a transaction; when you invoke the session EJB method (pruebaSessionNoIntA.testTx()) after marking the transaction as rollbak only, you get the exception:

... <BEA-010080> <An error occurred while attempting to process a message inside a Message-Driven Bean: javax.ejb.EJBTransactionRolledbackException: EJB Exception: 
Nested exception: javax.ejb.TransactionRolledbackLocalException: EJB Exception: 
Nested exception: weblogic.transaction.internal.AppSetRollbackOnlyException: setRollbackOnly called on transaction
Exception is : javax.ejb.EJBTransactionRolledbackException: EJB Exception: 
at weblogic.ejb.container.internal.BaseLocalObject.handleSystemException(BaseLocalObject.java:453)
at weblogic.ejb.container.internal.BaseLocalObject.getBeanInstance(BaseLocalObject.java:162)
at weblogic.ejb.container.internal.BaseLocalObject.preInvoke(BaseLocalObject.java:103)
at weblogic.ejb.container.internal.BaseLocalObject.__WL_preInvoke(BaseLocalObject.java:67)
at weblogic.ejb.container.internal.SessionLocalMethodInvoker.invoke(SessionLocalMethodInvoker.java:20)
at test.PruebaSessionNoIntA_uae4n4_NoIntfViewImpl.testTx(Unknown Source)
at test.PruebaMdbA.onMessage(PruebaMdbA.java:40)
at weblogic.ejb.container.internal.MDListener.execute(MDListener.java:585)
at weblogic.ejb.container.internal.MDListener.transactionalOnMessage(MDListener.java:488)
at weblogic.ejb.container.internal.NewJMSMessagePoller.processOneMessage(NewJMSMessagePoller.java:290)
at weblogic.ejb.container.internal.NewJMSMessagePoller.run(NewJMSMessagePoller.java:126)
at weblogic.work.SelfTuningWorkManagerImpl$WorkAdapterImpl.run(SelfTuningWorkManagerImpl.java:545)
at weblogic.work.ExecuteThread.execute(ExecuteThread.java:256)
at weblogic.work.ExecuteThread.run(ExecuteThread.java:221)
Caused by: javax.ejb.TransactionRolledbackLocalException: EJB Exception: 
at weblogic.ejb.container.internal.EJBRuntimeUtils.throwTransactionRolledbackLocal(EJBRuntimeUtils.java:236)
at weblogic.ejb.container.internal.EJBRuntimeUtils.throwWrappedTransactionRolledbackLocal(EJBRuntimeUtils.java:255)
at weblogic.ejb.container.manager.BaseEJBManager.handleSystemException(BaseEJBManager.java:348)
at weblogic.ejb.container.manager.BaseEJBManager.setupTxListener(BaseEJBManager.java:276)
at weblogic.ejb.container.manager.StatelessManager.preInvoke(StatelessManager.java:171)
at weblogic.ejb.container.internal.BaseLocalObject.getBeanInstance(BaseLocalObject.java:146)
... 12 more
Caused by: weblogic.transaction.internal.AppSetRollbackOnlyException: setRollbackOnly called on transaction
at weblogic.transaction.internal.TransactionImpl.setRollbackOnly(TransactionImpl.java:555)
at weblogic.ejb.container.internal.BaseEJBContext.setRollbackOnly(BaseEJBContext.java:242)
at test.PruebaMdbA.onMessage(PruebaMdbA.java:36)
... 7 more







lunes, 3 de marzo de 2014

Maximum redelivery retries and retry delay in AQ backed MDB in Weblogic

When working with AQ backed MDB in Weblogic, the maximum redelivery retry count and retry delay can be configured at AQ queue level. The two related parameters, are:
  • max_retries
  • retry_delay
They can be configured at queue creation time (DBMS_AQADM.CREATE_QUEUE) or can be updated in a existent queue (DBMS_AQADM.ALTER_QUEUE). The default values are 5 for max_retries and 0 for retry (as soon as possible). The complete set of parameters can be found here.

In Weblogic, you can verify the parameter's operation with something like this:

@MessageDriven(
  activationConfig = {
    @ActivationConfigProperty(propertyName = "ConnectionFactoryJndiName", propertyValue = "..."),
    @ActivationConfigProperty(propertyName = "DestinationJndiName", propertyValue = "..."),
    @ActivationConfigProperty(propertyName = "DestinationType", propertyValue = "javax.jms.Queue")
  })
public class TestMyQueueMDB implements MessageListener {
  
  @Resource
  private MessageDrivenContext mdc;
  
  public void onMessage(Message message) {
    try {
      System.out.println("onMessage][START]time=" + new Date());
      System.out.println("JMSMessageID     : " + message.getJMSMessageID());
      System.out.println("JMSRedelivered   : " + message.getJMSRedelivered());
      System.out.println("JMSXDeliveryCount: " + message.getIntProperty("JMSXDeliveryCount"));

      //force retry
      mdc.setRollbackOnly();
    } catch(Exception e) {
      //...
    } finally {
      System.out.println("[onMessage][END]");
      System.out.println();
    }
  }
  //...
}

With an queue created with the default values,

dbms_aqadm.create_queue(
  queue_name=>'myQueue',
  queue_table=>'myQueueTable'
);

it should print something like this:

[onMessage][START]time: Mon Mar 03 13:18:26 COT 2014
JMSMessageID     : ID:205BD8B6042C4348A778FC5D467D2581
JMSRedelivered   : false
JMSXDeliveryCount: 1
[onMessage][END]

[onMessage][START]time: Mon Mar 03 13:18:26 COT 2014
JMSMessageID     : ID:205BD8B6042C4348A778FC5D467D2581
JMSRedelivered   : true
JMSXDeliveryCount: 2
[onMessage][END]

[onMessage][START]time: Mon Mar 03 13:18:26 COT 2014
JMSMessageID     : ID:205BD8B6042C4348A778FC5D467D2581
JMSRedelivered   : true
JMSXDeliveryCount: 3
[onMessage][END]

[onMessage][START]time: Mon Mar 03 13:18:26 COT 2014
JMSMessageID     : ID:205BD8B6042C4348A778FC5D467D2581
JMSRedelivered   : true
JMSXDeliveryCount: 4
[onMessage][END]

[onMessage][START]time: Mon Mar 03 13:18:26 COT 2014
JMSMessageID     : ID:205BD8B6042C4348A778FC5D467D2581
JMSRedelivered   : true
JMSXDeliveryCount: 5
[onMessage][END]

[onMessage][START]time: Mon Mar 03 13:18:26 COT 2014
JMSMessageID     : ID:205BD8B6042C4348A778FC5D467D2581
JMSRedelivered   : true
JMSXDeliveryCount: 6
[onMessage][END]

After doing an update like the following,

BEGIN
DBMS_AQADM.ALTER_QUEUE (
   queue_name => 'MYQUEUE',
   max_retries => 2,
   retry_delay => 10);
END;
/

it should print something like

[onMessage][START]time: Mon Mar 03 13:13:16 COT 2014
JMSMessageID     : ID:61819BE03F7942DE92B2FA196EF13888
JMSRedelivered   : false
JMSXDeliveryCount: 1
[onMessage][END]

[onMessage][START]time: Mon Mar 03 13:13:26 COT 2014
JMSMessageID     : ID:61819BE03F7942DE92B2FA196EF13888
JMSRedelivered   : true
JMSXDeliveryCount: 2
[onMessage][END]

[onMessage][START]time: Mon Mar 03 13:13:37 COT 2014
JMSMessageID     : ID:61819BE03F7942DE92B2FA196EF13888
JMSRedelivered   : true
JMSXDeliveryCount: 3
[onMessage][END]

References: