mirror of https://github.com/apache/activemq.git
The JDBCMessageStore now checks hasSpace() when running a message recovery listener to prevent going past the max configured page size
This commit is contained in:
parent
2b1cda1964
commit
b9fad53fc6
|
@ -98,6 +98,7 @@ public class JDBCMessageStore extends AbstractMessageStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void addMessage(final ConnectionContext context, final Message message) throws IOException {
|
public void addMessage(final ConnectionContext context, final Message message) throws IOException {
|
||||||
MessageId messageId = message.getMessageId();
|
MessageId messageId = message.getMessageId();
|
||||||
if (audit != null && audit.isDuplicate(message)) {
|
if (audit != null && audit.isDuplicate(message)) {
|
||||||
|
@ -133,6 +134,7 @@ public class JDBCMessageStore extends AbstractMessageStore {
|
||||||
pendingAdditions.add(sequence);
|
pendingAdditions.add(sequence);
|
||||||
|
|
||||||
c.onCompletion(new Runnable() {
|
c.onCompletion(new Runnable() {
|
||||||
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
// jdbc close or jms commit - while futureOrSequenceLong==null ordered
|
// jdbc close or jms commit - while futureOrSequenceLong==null ordered
|
||||||
// work will remain pending on the Queue
|
// work will remain pending on the Queue
|
||||||
|
@ -207,6 +209,7 @@ public class JDBCMessageStore extends AbstractMessageStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public Message getMessage(MessageId messageId) throws IOException {
|
public Message getMessage(MessageId messageId) throws IOException {
|
||||||
// Get a connection and pull the message out of the DB
|
// Get a connection and pull the message out of the DB
|
||||||
TransactionContext c = persistenceAdapter.getTransactionContext();
|
TransactionContext c = persistenceAdapter.getTransactionContext();
|
||||||
|
@ -245,6 +248,7 @@ public class JDBCMessageStore extends AbstractMessageStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void removeMessage(ConnectionContext context, MessageAck ack) throws IOException {
|
public void removeMessage(ConnectionContext context, MessageAck ack) throws IOException {
|
||||||
|
|
||||||
long seq = ack.getLastMessageId().getFutureOrSequenceLong() != null ?
|
long seq = ack.getLastMessageId().getFutureOrSequenceLong() != null ?
|
||||||
|
@ -263,6 +267,7 @@ public class JDBCMessageStore extends AbstractMessageStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void recover(final MessageRecoveryListener listener) throws Exception {
|
public void recover(final MessageRecoveryListener listener) throws Exception {
|
||||||
|
|
||||||
// Get all the Message ids out of the database.
|
// Get all the Message ids out of the database.
|
||||||
|
@ -270,14 +275,30 @@ public class JDBCMessageStore extends AbstractMessageStore {
|
||||||
try {
|
try {
|
||||||
c = persistenceAdapter.getTransactionContext();
|
c = persistenceAdapter.getTransactionContext();
|
||||||
adapter.doRecover(c, destination, new JDBCMessageRecoveryListener() {
|
adapter.doRecover(c, destination, new JDBCMessageRecoveryListener() {
|
||||||
|
@Override
|
||||||
public boolean recoverMessage(long sequenceId, byte[] data) throws Exception {
|
public boolean recoverMessage(long sequenceId, byte[] data) throws Exception {
|
||||||
|
if (listener.hasSpace()) {
|
||||||
Message msg = (Message) wireFormat.unmarshal(new ByteSequence(data));
|
Message msg = (Message) wireFormat.unmarshal(new ByteSequence(data));
|
||||||
msg.getMessageId().setBrokerSequenceId(sequenceId);
|
msg.getMessageId().setBrokerSequenceId(sequenceId);
|
||||||
return listener.recoverMessage(msg);
|
return listener.recoverMessage(msg);
|
||||||
|
} else {
|
||||||
|
if (LOG.isTraceEnabled()) {
|
||||||
|
LOG.trace("Message recovery limit reached for MessageRecoveryListener");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public boolean recoverMessageReference(String reference) throws Exception {
|
public boolean recoverMessageReference(String reference) throws Exception {
|
||||||
|
if (listener.hasSpace()) {
|
||||||
return listener.recoverMessageReference(new MessageId(reference));
|
return listener.recoverMessageReference(new MessageId(reference));
|
||||||
|
} else {
|
||||||
|
if (LOG.isTraceEnabled()) {
|
||||||
|
LOG.trace("Message recovery limit reached for MessageRecoveryListener");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
|
@ -291,6 +312,7 @@ public class JDBCMessageStore extends AbstractMessageStore {
|
||||||
/**
|
/**
|
||||||
* @see org.apache.activemq.store.MessageStore#removeAllMessages(ConnectionContext)
|
* @see org.apache.activemq.store.MessageStore#removeAllMessages(ConnectionContext)
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public void removeAllMessages(ConnectionContext context) throws IOException {
|
public void removeAllMessages(ConnectionContext context) throws IOException {
|
||||||
// Get a connection and remove the message from the DB
|
// Get a connection and remove the message from the DB
|
||||||
TransactionContext c = persistenceAdapter.getTransactionContext(context);
|
TransactionContext c = persistenceAdapter.getTransactionContext(context);
|
||||||
|
@ -328,6 +350,7 @@ public class JDBCMessageStore extends AbstractMessageStore {
|
||||||
* @see org.apache.activemq.store.MessageStore#recoverNextMessages(int,
|
* @see org.apache.activemq.store.MessageStore#recoverNextMessages(int,
|
||||||
* org.apache.activemq.store.MessageRecoveryListener)
|
* org.apache.activemq.store.MessageRecoveryListener)
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public void recoverNextMessages(int maxReturned, final MessageRecoveryListener listener) throws Exception {
|
public void recoverNextMessages(int maxReturned, final MessageRecoveryListener listener) throws Exception {
|
||||||
TransactionContext c = persistenceAdapter.getTransactionContext();
|
TransactionContext c = persistenceAdapter.getTransactionContext();
|
||||||
try {
|
try {
|
||||||
|
@ -337,6 +360,7 @@ public class JDBCMessageStore extends AbstractMessageStore {
|
||||||
adapter.doRecoverNextMessages(c, destination, perPriorityLastRecovered, minPendingSequeunceId(),
|
adapter.doRecoverNextMessages(c, destination, perPriorityLastRecovered, minPendingSequeunceId(),
|
||||||
maxReturned, isPrioritizedMessages(), new JDBCMessageRecoveryListener() {
|
maxReturned, isPrioritizedMessages(), new JDBCMessageRecoveryListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
public boolean recoverMessage(long sequenceId, byte[] data) throws Exception {
|
public boolean recoverMessage(long sequenceId, byte[] data) throws Exception {
|
||||||
Message msg = (Message)wireFormat.unmarshal(new ByteSequence(data));
|
Message msg = (Message)wireFormat.unmarshal(new ByteSequence(data));
|
||||||
msg.getMessageId().setBrokerSequenceId(sequenceId);
|
msg.getMessageId().setBrokerSequenceId(sequenceId);
|
||||||
|
@ -346,6 +370,7 @@ public class JDBCMessageStore extends AbstractMessageStore {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public boolean recoverMessageReference(String reference) throws Exception {
|
public boolean recoverMessageReference(String reference) throws Exception {
|
||||||
if (listener.hasSpace()) {
|
if (listener.hasSpace()) {
|
||||||
listener.recoverMessageReference(new MessageId(reference));
|
listener.recoverMessageReference(new MessageId(reference));
|
||||||
|
@ -370,6 +395,7 @@ public class JDBCMessageStore extends AbstractMessageStore {
|
||||||
/**
|
/**
|
||||||
* @see org.apache.activemq.store.MessageStore#resetBatching()
|
* @see org.apache.activemq.store.MessageStore#resetBatching()
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public void resetBatching() {
|
public void resetBatching() {
|
||||||
if (LOG.isTraceEnabled()) {
|
if (LOG.isTraceEnabled()) {
|
||||||
LOG.trace(this + " resetBatching. last recovered: " + Arrays.toString(perPriorityLastRecovered));
|
LOG.trace(this + " resetBatching. last recovered: " + Arrays.toString(perPriorityLastRecovered));
|
||||||
|
@ -401,6 +427,7 @@ public class JDBCMessageStore extends AbstractMessageStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
public void setPrioritizedMessages(boolean prioritizedMessages) {
|
public void setPrioritizedMessages(boolean prioritizedMessages) {
|
||||||
super.setPrioritizedMessages(prioritizedMessages);
|
super.setPrioritizedMessages(prioritizedMessages);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,157 @@
|
||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.activemq.store.jdbc;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
import javax.jms.Connection;
|
||||||
|
import javax.jms.MessageProducer;
|
||||||
|
import javax.jms.Session;
|
||||||
|
import javax.jms.TopicSubscriber;
|
||||||
|
|
||||||
|
import org.apache.activemq.ActiveMQConnectionFactory;
|
||||||
|
import org.apache.activemq.broker.BrokerService;
|
||||||
|
import org.apache.activemq.broker.region.policy.PolicyEntry;
|
||||||
|
import org.apache.activemq.broker.region.policy.PolicyMap;
|
||||||
|
import org.apache.activemq.command.ActiveMQTopic;
|
||||||
|
import org.apache.activemq.command.Message;
|
||||||
|
import org.apache.activemq.command.MessageId;
|
||||||
|
import org.apache.activemq.store.MessageRecoveryListener;
|
||||||
|
import org.apache.activemq.store.ProxyTopicMessageStore;
|
||||||
|
import org.apache.activemq.store.TopicMessageStore;
|
||||||
|
import org.apache.activemq.util.Wait;
|
||||||
|
import org.apache.activemq.util.Wait.Condition;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.TemporaryFolder;
|
||||||
|
|
||||||
|
public class JDBCPersistenceAdapterExpiredMessageTest {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public TemporaryFolder dataFileDir = new TemporaryFolder(new File("target"));
|
||||||
|
|
||||||
|
protected BrokerService brokerService;
|
||||||
|
private AtomicBoolean hasSpaceCalled = new AtomicBoolean();
|
||||||
|
private int expireSize = 5;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
hasSpaceCalled.set(false);
|
||||||
|
brokerService = new BrokerService();
|
||||||
|
|
||||||
|
//Wrap the adapter and listener to set a flag to make sure we are calling hasSpace()
|
||||||
|
//during expiration
|
||||||
|
JDBCPersistenceAdapter jdbc = new JDBCPersistenceAdapter() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TopicMessageStore createTopicMessageStore(ActiveMQTopic destination) throws IOException {
|
||||||
|
ProxyTopicMessageStore proxy = new ProxyTopicMessageStore(super.createTopicMessageStore(destination)) {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void recover(final MessageRecoveryListener listener) throws Exception {
|
||||||
|
MessageRecoveryListener delegate = new MessageRecoveryListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean recoverMessageReference(MessageId ref) throws Exception {
|
||||||
|
return listener.recoverMessageReference(ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean recoverMessage(Message message) throws Exception {
|
||||||
|
return listener.recoverMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDuplicate(MessageId ref) {
|
||||||
|
return listener.isDuplicate(ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasSpace() {
|
||||||
|
hasSpaceCalled.set(true);
|
||||||
|
return listener.hasSpace();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
super.recover(delegate);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
return proxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
brokerService.setSchedulerSupport(false);
|
||||||
|
brokerService.setDataDirectoryFile(dataFileDir.getRoot());
|
||||||
|
brokerService.setPersistenceAdapter(jdbc);
|
||||||
|
brokerService.setDeleteAllMessagesOnStartup(true);
|
||||||
|
|
||||||
|
PolicyMap policyMap = new PolicyMap();
|
||||||
|
PolicyEntry defaultEntry = new PolicyEntry();
|
||||||
|
defaultEntry.setExpireMessagesPeriod(5000);
|
||||||
|
defaultEntry.setMaxExpirePageSize(expireSize);
|
||||||
|
policyMap.setDefaultEntry(defaultEntry);
|
||||||
|
brokerService.setDestinationPolicy(policyMap);
|
||||||
|
|
||||||
|
brokerService.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void stop() throws Exception {
|
||||||
|
if (brokerService != null) {
|
||||||
|
brokerService.stop();
|
||||||
|
brokerService.waitUntilStopped();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMaxExpirePageSize() throws Exception {
|
||||||
|
final ActiveMQTopic topic = new ActiveMQTopic("test.topic");
|
||||||
|
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("vm://localhost");
|
||||||
|
factory.setClientID("clientId");;
|
||||||
|
Connection conn = factory.createConnection();
|
||||||
|
conn.start();
|
||||||
|
Session sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
|
||||||
|
TopicSubscriber sub = sess.createDurableSubscriber(topic, "sub1");
|
||||||
|
sub.close();
|
||||||
|
|
||||||
|
MessageProducer producer = sess.createProducer(topic);
|
||||||
|
producer.setTimeToLive(1000);
|
||||||
|
|
||||||
|
for (int i = 0; i < 50; i++) {
|
||||||
|
producer.send(sess.createTextMessage("test message: " + i));
|
||||||
|
}
|
||||||
|
|
||||||
|
//There should be exactly 5 messages expired because the limit was hit and it stopped
|
||||||
|
//The expire messages period is 5 seconds which should give enough time for this assertion
|
||||||
|
//to pass before expiring more messages
|
||||||
|
assertTrue(Wait.waitFor(new Condition() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSatisified() throws Exception {
|
||||||
|
long expired = brokerService.getDestination(topic).getDestinationStatistics().getExpired().getCount();
|
||||||
|
return expired == expireSize && hasSpaceCalled.get();
|
||||||
|
}
|
||||||
|
}, 15000, 1000));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue