mirror of https://github.com/apache/activemq.git
[AMQ-7485] add check for rollbackonly flag in session send such that failed ended transactions prevent further work till next transaction boundary
This commit is contained in:
parent
41bef94293
commit
0ebb0f88ef
|
@ -1926,6 +1926,9 @@ public class ActiveMQSession implements Session, QueueSession, TopicSession, Sta
|
||||||
synchronized (sendMutex) {
|
synchronized (sendMutex) {
|
||||||
// tell the Broker we are about to start a new transaction
|
// tell the Broker we are about to start a new transaction
|
||||||
doStartTransaction();
|
doStartTransaction();
|
||||||
|
if (transactionContext.isRollbackOnly()) {
|
||||||
|
throw new IllegalStateException("transaction marked rollback only");
|
||||||
|
}
|
||||||
TransactionId txid = transactionContext.getTransactionId();
|
TransactionId txid = transactionContext.getTransactionId();
|
||||||
long sequenceNumber = producer.getMessageSequence();
|
long sequenceNumber = producer.getMessageSequence();
|
||||||
|
|
||||||
|
|
|
@ -111,6 +111,10 @@ public class TransactionContext implements XAResource {
|
||||||
rollbackOnly = val;
|
rollbackOnly = val;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isRollbackOnly() {
|
||||||
|
return rollbackOnly;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isInLocalTransaction() {
|
public boolean isInLocalTransaction() {
|
||||||
return transactionId != null && transactionId.isLocalTransaction();
|
return transactionId != null && transactionId.isLocalTransaction();
|
||||||
}
|
}
|
||||||
|
|
|
@ -102,6 +102,11 @@ public class LocalAndXATransaction implements XAResource, LocalTransaction {
|
||||||
} catch (JMSException e) {
|
} catch (JMSException e) {
|
||||||
throw (XAException)new XAException(XAException.XAER_PROTO).initCause(e);
|
throw (XAException)new XAException(XAException.XAER_PROTO).initCause(e);
|
||||||
}
|
}
|
||||||
|
if ((arg1 & TMFAIL) != 0) {
|
||||||
|
// do no further work in this context
|
||||||
|
LOG.debug("Marking transaction: {} rollbackOnly", this);
|
||||||
|
transactionContext.setRollbackOnly(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -113,9 +113,13 @@ public class ManagedTransactionContext extends TransactionContext {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isRollbackOnly() {
|
||||||
|
return sharedContext.isRollbackOnly() || super.isRollbackOnly();
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isInXATransaction() {
|
public boolean isInXATransaction() {
|
||||||
if (useSharedTxContext) {
|
if (useSharedTxContext) {
|
||||||
// context considers endesd XA transactions as active, so just check for presence
|
// context considers ended XA transactions as active, so just check for presence
|
||||||
// of tx when it is shared
|
// of tx when it is shared
|
||||||
return sharedContext.isInTransaction();
|
return sharedContext.isInTransaction();
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -20,8 +20,12 @@ import java.io.ByteArrayOutputStream;
|
||||||
import java.io.DataOutputStream;
|
import java.io.DataOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
import javax.jms.ConnectionFactory;
|
import javax.jms.ConnectionFactory;
|
||||||
|
import javax.jms.JMSException;
|
||||||
|
import javax.jms.Message;
|
||||||
|
import javax.jms.MessageProducer;
|
||||||
import javax.jms.Session;
|
import javax.jms.Session;
|
||||||
import javax.resource.spi.ManagedConnection;
|
import javax.resource.spi.ManagedConnection;
|
||||||
import javax.transaction.xa.XAResource;
|
import javax.transaction.xa.XAResource;
|
||||||
|
@ -33,9 +37,11 @@ import org.apache.activemq.ActiveMQPrefetchPolicy;
|
||||||
import org.apache.activemq.JmsQueueTransactionTest;
|
import org.apache.activemq.JmsQueueTransactionTest;
|
||||||
import org.apache.activemq.broker.BrokerFactory;
|
import org.apache.activemq.broker.BrokerFactory;
|
||||||
import org.apache.activemq.broker.BrokerService;
|
import org.apache.activemq.broker.BrokerService;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
public class JmsXAQueueTransactionTest extends JmsQueueTransactionTest {
|
public class JmsXAQueueTransactionTest extends JmsQueueTransactionTest {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(JmsXAQueueTransactionTest.class);
|
||||||
private ConnectionManagerAdapter connectionManager = new ConnectionManagerAdapter();
|
private ConnectionManagerAdapter connectionManager = new ConnectionManagerAdapter();
|
||||||
private ActiveMQManagedConnectionFactory managedConnectionFactory;
|
private ActiveMQManagedConnectionFactory managedConnectionFactory;
|
||||||
private XAResource xaResource;
|
private XAResource xaResource;
|
||||||
|
@ -113,6 +119,12 @@ public class JmsXAQueueTransactionTest extends JmsQueueTransactionTest {
|
||||||
xid = null;
|
xid = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void abortTx() throws Exception {
|
||||||
|
xaResource.end(xid, XAResource.TMFAIL);
|
||||||
|
xaResource.rollback(xid);
|
||||||
|
xid = null;
|
||||||
|
}
|
||||||
|
|
||||||
//This test won't work with xa tx it is overridden to do nothing here
|
//This test won't work with xa tx it is overridden to do nothing here
|
||||||
@Override
|
@Override
|
||||||
public void testMessageListener() throws Exception {
|
public void testMessageListener() throws Exception {
|
||||||
|
@ -130,6 +142,84 @@ public class JmsXAQueueTransactionTest extends JmsQueueTransactionTest {
|
||||||
public void testSendSessionClose() throws Exception {
|
public void testSendSessionClose() throws Exception {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testSendOnAbortedXATx() throws Exception {
|
||||||
|
connection.close();
|
||||||
|
|
||||||
|
ConnectionFactory connectionFactory = newConnectionFactory();
|
||||||
|
connection = connectionFactory.createConnection();
|
||||||
|
connection.start();
|
||||||
|
|
||||||
|
ManagedConnectionProxy proxy = (ManagedConnectionProxy) connection;
|
||||||
|
ManagedConnection mc = proxy.getManagedConnection();
|
||||||
|
xaResource = mc.getXAResource();
|
||||||
|
|
||||||
|
beginTx();
|
||||||
|
|
||||||
|
Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
|
||||||
|
|
||||||
|
MessageProducer producer = session.createProducer(destination);
|
||||||
|
|
||||||
|
abortTx();
|
||||||
|
|
||||||
|
try {
|
||||||
|
producer.send(session.createTextMessage("my tx aborted!"));
|
||||||
|
fail("expect error on send with rolled back tx");
|
||||||
|
} catch (JMSException expected) {
|
||||||
|
assertTrue("matches expected message", expected.getLocalizedMessage().contains("rollback only"));
|
||||||
|
expected.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
connection.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testReceiveTwoThenAbort() throws Exception {
|
||||||
|
Message[] outbound = new Message[] {session.createTextMessage("First Message"), session.createTextMessage("Second Message")};
|
||||||
|
|
||||||
|
// lets consume any outstanding messages from prev test runs
|
||||||
|
beginTx();
|
||||||
|
while (consumer.receive(1000) != null) {
|
||||||
|
}
|
||||||
|
commitTx();
|
||||||
|
|
||||||
|
//
|
||||||
|
beginTx();
|
||||||
|
producer.send(outbound[0]);
|
||||||
|
producer.send(outbound[1]);
|
||||||
|
commitTx();
|
||||||
|
|
||||||
|
LOG.info("Sent 0: " + outbound[0]);
|
||||||
|
LOG.info("Sent 1: " + outbound[1]);
|
||||||
|
|
||||||
|
ArrayList<Message> messages = new ArrayList<Message>();
|
||||||
|
beginTx();
|
||||||
|
Message message = consumer.receive(1000);
|
||||||
|
assertEquals(outbound[0], message);
|
||||||
|
|
||||||
|
message = consumer.receive(1000);
|
||||||
|
assertNotNull(message);
|
||||||
|
assertEquals(outbound[1], message);
|
||||||
|
abortTx();
|
||||||
|
|
||||||
|
// Consume again.. the prev message should
|
||||||
|
// get redelivered.
|
||||||
|
beginTx();
|
||||||
|
message = consumer.receive(5000);
|
||||||
|
assertNotNull("Should have re-received the first message again!", message);
|
||||||
|
messages.add(message);
|
||||||
|
assertEquals(outbound[0], message);
|
||||||
|
message = consumer.receive(5000);
|
||||||
|
assertNotNull("Should have re-received the second message again!", message);
|
||||||
|
messages.add(message);
|
||||||
|
assertEquals(outbound[1], message);
|
||||||
|
|
||||||
|
assertNull(consumer.receiveNoWait());
|
||||||
|
commitTx();
|
||||||
|
|
||||||
|
Message inbound[] = new Message[messages.size()];
|
||||||
|
messages.toArray(inbound);
|
||||||
|
assertTextMessagesEqual("Rollback did not work", outbound, inbound);
|
||||||
|
}
|
||||||
|
|
||||||
public Xid createXid() throws IOException {
|
public Xid createXid() throws IOException {
|
||||||
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
|
|
@ -459,6 +459,40 @@ public class ActiveMQXAConnectionFactoryTest extends CombinationTestSupport {
|
||||||
} catch (javax.jms.IllegalStateException expected) {}
|
} catch (javax.jms.IllegalStateException expected) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testProducerFailAfterRollbackOnly() throws Exception {
|
||||||
|
|
||||||
|
ActiveMQConnectionFactory cf1 = getXAConnectionFactory("vm://localhost?broker.persistent=false");
|
||||||
|
XAConnection connection1 = (XAConnection)cf1.createConnection();
|
||||||
|
connection1.start();
|
||||||
|
|
||||||
|
XASession session = connection1.createXASession();
|
||||||
|
XAResource resource = session.getXAResource();
|
||||||
|
Destination dest = new ActiveMQQueue(getName());
|
||||||
|
|
||||||
|
// publish a message
|
||||||
|
Xid tid = createXid();
|
||||||
|
resource.start(tid, XAResource.TMNOFLAGS);
|
||||||
|
MessageProducer producer = session.createProducer(dest);
|
||||||
|
ActiveMQTextMessage message = new ActiveMQTextMessage();
|
||||||
|
message.setText(getName());
|
||||||
|
|
||||||
|
// can happen out of band with XA via RAR
|
||||||
|
resource.end(tid, XAResource.TMFAIL);
|
||||||
|
((ActiveMQSession)session).getTransactionContext().setRollbackOnly(true);
|
||||||
|
try {
|
||||||
|
producer.send(message);
|
||||||
|
fail("expect error on setRollbackOnly");
|
||||||
|
} catch (JMSException expected) {}
|
||||||
|
|
||||||
|
// rollback only state does not linger
|
||||||
|
tid = createXid();
|
||||||
|
resource.start(tid, XAResource.TMNOFLAGS);
|
||||||
|
producer.send(message);
|
||||||
|
resource.end(tid, XAResource.TMSUCCESS);
|
||||||
|
resource.commit(tid, true);
|
||||||
|
connection1.close();
|
||||||
|
}
|
||||||
|
|
||||||
public void testRollbackXaErrorCode() throws Exception {
|
public void testRollbackXaErrorCode() throws Exception {
|
||||||
String brokerName = "rollbackErrorCode";
|
String brokerName = "rollbackErrorCode";
|
||||||
BrokerService broker = BrokerFactory.createBroker(new URI("broker:(tcp://localhost:0)/" + brokerName));
|
BrokerService broker = BrokerFactory.createBroker(new URI("broker:(tcp://localhost:0)/" + brokerName));
|
||||||
|
|
Loading…
Reference in New Issue