Friday, October 12, 2012

received message with Unknown Transaction ID -1: ignoring message

Recently I ran into an interesting problem. In a pretty complex architecture (several Glassfish servers, multiple Liferay nodes) which ran without a problem for over a year, we had to implement a minor improvement. It was a pretty standard JMS message to be sent from one Glassfish instance to an other. We had several JMS messages cruising in the system, so this seemed a quite straightforward task.

Some key words about the setup: Spring JTA transaction management (spring framework ver. 3.0), Glassfish (3.0.1)  built in JMS implementation, Liferay 6.0.6. The database had read-committed isolation, and  used XA transaction, the JMS supported local transactions.

Previously we did not encounter any problem regarding the transactions and the JMS message send, as long as the service implementation was annotated with @org.springframework.transaction.annotation.Transactional(readonly = false)

The required change was that a file should be uploaded to the Lifery Document Library at the end of the method, the a JMS message to be sent to broadcast the event. After implementing the method, the JMS failed with the following exception:

java.lang.RuntimeException: com.sun.messaging.jms.JMSException: [SEND_REPLY(9)] [C4036]: A broker error occurred. :[500] transaction failed: Unexpected Broker Exception: [received message with Unknown Transaction ID -1: ignoring message] user=admin, broker=localhost:2076(50283)

It was clear after a while, if the Liferay file upload is not in the function, everything works as expected. The transaction was active for the method, the stack trace was clear about it. It was no use to debug the Liferay part, because if we want to upgrade to a newer version, it might need to be patched again for our code to work.

So it seemed as if the Liferay terminates (commits) the transaction during file upload. So we need to exit the current transaction while leaving it open, then return to it after the file upload completed, and continue as previously. To do that, we had to start a new thread to do the dirty work. The java language had the built-in solution:

ExecutorService executorService = Executors.newSingleThreadExecutor();
Future future = executorService.submit(new Callable() {
  @Override
  public Object call() throws Exception {
    try {
      // do file upload
      DLFileEntryLocalServiceUtil.
        addFileEntry(userId, groupId, folderId, finalFilename,
        finalFilename, description, (String) null, null, 
        finalBytes, serviceContext);
    } catch (final SystemException e) {
      return e;
    } catch (final PortalException e) {
      return e;
    }
      return Boolean.TRUE;
    }
});

Object object = future.get();
if (object instanceof Boolean) {
  if (((Boolean) object).booleanValue()) {
    // everything OK
  } else {
    // something went wrong
  }
} else if (object instanceof Exception) {
  // something went wrong
}

This implementation certainly has its drawbacks: extra care should be taken if the transaction fails after the file upload happened to rollback it. In our case, this was not an issue: it was OK if the resulting file was a result of a transaction that rolled back afterwards.





No comments:

Post a Comment