Scenario: deploy ActiveMQ RA to EAP6.4 with XA enabled DataSources for JDBC and ActiveMQ Artemis as JMS broker in a failover config (active/passive) and run several applications that use JMS and JDBC datasources. (In our scenario, there are 4 XA JDBC DataSources and 42 deployed WAR files). ActiveMQ resource adapter is used as the Artemis one is not compatible to this EAP version.
JBoss EAP and Artemis are running on the same host in the examples below, but this does not matter. Artemis is running with example HA-by-network-replication settings on localhost:61616 and localhost:61617.
The resource adapter settings on JBoss EAP side and on client side (especially properties of the broker URIs for ActiveMQ's connection factory) are exactly as described in Red Hat's reference documentation for HA config:
- Add ?jms.rmIdFromConnectionId=true to the connection URI
- <is-same-rm-override>false</is-same-rm-override> in standalone.xml
Observed Symptoms: On a regular basis, undeployment of WAR files take minutes (around 10) instead of seconds, blocking other container management operations during this timeframe. This behavior can be traced back to running ActiveMQ RA and Artemis as JMS broker. If running the same WAR files and same config, but using JBoss EAP's internal HornetQ broker, these delays do not occur and undeployments take mere seconds.
Further Investigation: This only affects JavaEE modules that define at least one MDB (message driven bean). During shutdown of the beans, the container management thread is blocked while de-registering the bean's XA Resource, see following stack trace taken while a simple module is being undeployed for reference:
During these 10 minutes of blocking while the module is supposed to be undeployed, EAP's log is literally spammed with thousands of these lines:
Only after these lines stop to be logged the undeployment process continues.
What happens during this time in ActiveMQ: Obviously there are thousands of connections established to Artemis during this time. Tracing back the code path that leads to these calls points to EAP's Periodic XA Resource Recovery thread, see the following full stack trace (taken at the same point in time):
From this stack trace I want to point out especially this line: org.apache.activemq.TransactionContext.isSameRM()
This method is part of the XA Resource Contract. Simply put the periodic recovery process iterates over XA Resources and looks for dormant XA transactions that need to be finished in some way or the other. Here it checks different combinations of XA Resources to asses whether they came from the same Resource Manager (.isSameRM()).
During this recovery process (which in the stack trace above is still in the phase of gathering basic information and not actually working on XA transactions) the deregistration of the message driven beans is blocked.
Analysis for the super long duration of recovery process: The XAResource instances that periodic recovery works on are created by org.apache.activemq.ra.ActiveMQResourceAdapter.getXAResources(ActivationSpec). The anonymous class that extends org.apache.activemq.TransactionContext and that is instantiated here does basically the following steps for each invocation of any of the XA Resource contract methods:
- Replace the current connection to Artemis with a freshly created one
- Call super
- Restore the current connection and close the new one
Specifically for isSameRM this results in the following chain of events:
- Create a pristine connection to remote Artemis (in our case a FailoverTransport)
- Call super.isSameRM(xaresource)
- If xaresource is null or is not an instance of TransactionContext, return false (org/apache/activemq/TransactionContext.java:658)
- Get the current resource manager id and xaresource's resource manager id. This effectively calls this.getResourceManagerId(). As noted above, this leads to the following calls:
- Replace the current connection to Artemis (established in step 1) with a second pristine one
- Call super.getResourceManagerId()
- Call getResourceManagerId() on the connection of step 5
- In org/apache/activemq/ActiveMQConnection.java:1622, the resource manager id effectively is the Connection Id in our setup (according to the flag set in the connection URI).
- This id is unique for each connection (see org/apache/activemq/ActiveMQConnection.java:240)
- Compare the resulting value with the resource manager id of xaresource and return the result
- Close the connection to the JMS broker established in step 5 and set the one established in step 1 as "current" connection
- Close the connection established in step 1 and reset current connection to whatever was there before (in my understanding that is always null as the instances are always being freshly created inside ActiveMQResourceAdapter.getXAResources)
This is repeated for each pair of XA Resources, or rather Activation Specs, that the recovery manager encounters. The log spamming stems from steps 1 and 5 because each time a INFO message is logged tht tells us that a new connection has been established to the server.
This also happens when the recovery process wants to know if a JDBC connection to our SQL server (which we have plenty of due to several modules with a connection pooling data source each) is from the same resource manager as a JMS connection to Artemis - which it obviously never is.
Problematic Aspects of this code:
- It doesn't make sense to establish pristine connections to the remote broker for each invocation of one of XAResource's methods. The connection setup is expensive with a plain non-failover URI as it is, but even more so with a failover transport - this has been supported by tests and also the code shows this.
- If there is the option to find an answer to isSameRM() without having to do any work, this should be done as early as possible.
- If the connection factory is configured with rmIdFromConnectionId enabled, then isSameRM() in my understanding will never return true.
Possible solutions for each of these aspects:
- In order to change the basic pattern of create-connection-then-do-something-then-close-connection that happens inside ActiveMQResourceAdapter.getXAResources(), a big portion of this code would propably have to be re-written.
- In the current code base, the instanceof check (see step 3 above) can be extraced and inlined into the anonymous inner class defined inside ActiveMQResourceAdapter.getXAResources()
- I don't know about the semantic correctness of this, i.e. if this behavior is desired; but if it is, and all the code I read strengthens this assumption, then in this case this is another candidate for short-circuting the expensive connection creation: if this config flag is set on the connection factory, directly return false.
So, basically, the solutions 2 and 3 somewhat mitigate the antipattern described in 1. And only this aspect is addressed by the pull request that is linked to this ticket.