/** * 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.kahadb; 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.ActiveMQQueue; import org.apache.activemq.store.kahadb.disk.journal.DataFile; import org.apache.activemq.store.kahadb.disk.journal.Location; import org.apache.activemq.store.kahadb.disk.page.Page; import org.apache.activemq.store.kahadb.disk.page.PageFile; import org.apache.activemq.store.kahadb.disk.page.Transaction; import org.junit.After; import org.junit.Ignore; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.jms.Connection; import javax.jms.Destination; import javax.jms.Message; import javax.jms.MessageProducer; import javax.jms.Session; import java.io.IOException; import java.util.Collection; import static org.apache.activemq.store.kahadb.JournalCorruptionEofIndexRecoveryTest.drain; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class JournalMetaDataCheckpointTest { private static final Logger LOG = LoggerFactory.getLogger(JournalMetaDataCheckpointTest.class); private final String KAHADB_DIRECTORY = "target/activemq-data/"; private final String payload = new String(new byte[1024]); private BrokerService broker = null; private final Destination destination = new ActiveMQQueue("Test"); private KahaDBPersistenceAdapter adapter; protected void startBroker() throws Exception { doStartBroker(true); } protected void restartBroker() throws Exception { if (broker != null) { broker.stop(); broker.waitUntilStopped(); } doStartBroker(false); } private void doStartBroker(boolean delete) throws Exception { doCreateBroker(delete); LOG.info("Starting broker.."); broker.start(); } private void doCreateBroker(boolean delete) throws Exception { broker = new BrokerService(); broker.setDeleteAllMessagesOnStartup(delete); broker.setPersistent(true); broker.setUseJmx(true); broker.setDataDirectory(KAHADB_DIRECTORY); PolicyMap policyMap = new PolicyMap(); PolicyEntry policyEntry = new PolicyEntry(); policyEntry.setUseCache(false); policyMap.setDefaultEntry(policyEntry); broker.setDestinationPolicy(policyMap); configurePersistence(broker); } protected void configurePersistence(BrokerService brokerService) throws Exception { adapter = (KahaDBPersistenceAdapter) brokerService.getPersistenceAdapter(); // ensure there are a bunch of data files but multiple entries in each adapter.setJournalMaxFileLength(1024 * 20); // manual cleanup adapter.setCheckpointInterval(0); adapter.setCleanupInterval(0); adapter.setCheckForCorruptJournalFiles(true); } @After public void tearDown() throws Exception { if (broker != null) { broker.stop(); broker.waitUntilStopped(); } } @Test public void testRecoveryOnDeleteFailureMetaDataOk() throws Exception { startBroker(); int sent = produceMessagesToConsumeMultipleDataFiles(50); int numFilesAfterSend = getNumberOfJournalFiles(); LOG.info("Sent {}, Num journal files: {} ", sent, numFilesAfterSend); assertTrue("more than x files: " + numFilesAfterSend, numFilesAfterSend > 4); int received = tryConsume(destination, sent/2); assertEquals("all message received", sent/2, received); int numFilesAfterRestart = getNumberOfJournalFiles(); LOG.info("Num journal files before gc: " + numFilesAfterRestart); // force gc ((KahaDBPersistenceAdapter) broker.getPersistenceAdapter()).getStore().checkpoint(true); int numFilesAfterGC = getNumberOfJournalFiles(); assertEquals("all message received", sent/2, received); LOG.info("Num journal files after restart nd gc: " + numFilesAfterGC); assertTrue("Gc has happened", numFilesAfterGC < numFilesAfterRestart); // verify metadata is correct on disk final MessageDatabase.Metadata[] fromDiskMetaData = new MessageDatabase.Metadata[1]; final KahaDBStore messageStore = ((KahaDBPersistenceAdapter) broker.getPersistenceAdapter()).getStore(); // need to avoid cache and in-progress writes of existing pageFile PageFile fromDiskPageFile = new PageFile(messageStore.getIndexDirectory(), "db"); fromDiskPageFile.setEnablePageCaching(false); fromDiskPageFile.setEnableRecoveryFile(false); fromDiskPageFile.load(); fromDiskPageFile.tx().execute(new Transaction.Closure<IOException>() { @Override public void execute(Transaction tx) throws IOException { Page<MessageDatabase.Metadata> page = tx.load(0, messageStore.metadataMarshaller); fromDiskMetaData[0] = page.get(); } }); assertEquals("location is uptodate", messageStore.getMetadata().ackMessageFileMapLocation, fromDiskMetaData[0].ackMessageFileMapLocation); } @Ignore("needs work") public void testAckMessageFileMapSyncOnModOnly() throws Exception { startBroker(); // force gc ((KahaDBPersistenceAdapter) broker.getPersistenceAdapter()).getStore().checkpoint(true); KahaDBStore messageStore = ((KahaDBPersistenceAdapter) broker.getPersistenceAdapter()).getStore(); Location ackMessageFileModLoc = messageStore.getMetadata().ackMessageFileMapLocation; // force gc ((KahaDBPersistenceAdapter) broker.getPersistenceAdapter()).getStore().checkpoint(true); assertEquals("location is not changed on no modification", ackMessageFileModLoc, messageStore.getMetadata().ackMessageFileMapLocation); } private int getNumberOfJournalFiles() throws IOException { Collection<DataFile> files = ((KahaDBPersistenceAdapter) broker.getPersistenceAdapter()).getStore().getJournal().getFileMap().values(); int reality = 0; for (DataFile file : files) { if (file != null) { reality++; } } return reality; } private int produceMessages(Destination destination, int numToSend) throws Exception { int sent = 0; Connection connection = new ActiveMQConnectionFactory(broker.getVmConnectorURI()).createConnection(); connection.start(); try { Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); MessageProducer producer = session.createProducer(destination); for (int i = 0; i < numToSend; i++) { producer.send(createMessage(session, i)); sent++; } } finally { connection.close(); } return sent; } private int tryConsume(Destination destination, int numToGet) throws Exception { ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory(broker.getVmConnectorURI()); return drain(cf, destination, numToGet); } private int produceMessagesToConsumeMultipleDataFiles(int numToSend) throws Exception { return produceMessages(destination, numToSend); } private Message createMessage(Session session, int i) throws Exception { return session.createTextMessage(payload + "::" + i); } }