/*
* 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.artemis.tests.unit.core.journal.impl;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.activemq.artemis.core.io.SequentialFile;
import org.apache.activemq.artemis.core.io.SequentialFileFactory;
import org.apache.activemq.artemis.core.journal.EncodingSupport;
import org.apache.activemq.artemis.core.journal.LoaderCallback;
import org.apache.activemq.artemis.core.journal.PreparedTransactionInfo;
import org.apache.activemq.artemis.core.journal.RecordInfo;
import org.apache.activemq.artemis.core.journal.TransactionFailureCallback;
import org.apache.activemq.artemis.core.journal.impl.JournalImpl;
import org.apache.activemq.artemis.tests.unit.UnitTestLogger;
import org.apache.activemq.artemis.tests.unit.core.journal.impl.fakes.FakeSequentialFileFactory;
import org.apache.activemq.artemis.tests.unit.core.journal.impl.fakes.SimpleEncoding;
import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class AlignedJournalImplTest extends ActiveMQTestBase {
// Constants -----------------------------------------------------
private static final LoaderCallback dummyLoader = new LoaderCallback() {
@Override
public void addPreparedTransaction(final PreparedTransactionInfo preparedTransaction) {
}
@Override
public void addRecord(final RecordInfo info) {
}
@Override
public void deleteRecord(final long id) {
}
@Override
public void updateRecord(final RecordInfo info) {
}
@Override
public void failedTransaction(final long transactionID,
final List<RecordInfo> records,
final List<RecordInfo> recordsToDelete) {
}
};
// Attributes ----------------------------------------------------
private SequentialFileFactory factory;
JournalImpl journalImpl = null;
private ArrayList<RecordInfo> records = null;
private ArrayList<Long> incompleteTransactions = null;
private ArrayList<PreparedTransactionInfo> transactions = null;
// Static --------------------------------------------------------
// Constructors --------------------------------------------------
// Public --------------------------------------------------------
// This test just validates basic alignment on the FakeSequentialFile itself
@Test
public void testBasicAlignment() throws Exception {
FakeSequentialFileFactory factory = new FakeSequentialFileFactory(200, true);
SequentialFile file = factory.createSequentialFile("test1");
file.open();
try {
ByteBuffer buffer = ByteBuffer.allocateDirect(200);
for (int i = 0; i < 200; i++) {
buffer.put(i, (byte) 1);
}
file.writeDirect(buffer, true);
buffer = ByteBuffer.allocate(400);
for (int i = 0; i < 400; i++) {
buffer.put(i, (byte) 2);
}
file.writeDirect(buffer, true);
buffer = ByteBuffer.allocate(600);
file.position(0);
file.read(buffer);
for (int i = 0; i < 200; i++) {
Assert.assertEquals((byte) 1, buffer.get(i));
}
for (int i = 201; i < 600; i++) {
Assert.assertEquals("Position " + i, (byte) 2, buffer.get(i));
}
} catch (Exception ignored) {
}
}
@Test
public void testInconsistentAlignment() throws Exception {
factory = new FakeSequentialFileFactory(512, true);
try {
journalImpl = new JournalImpl(2000, 2, 2, 0, 0, factory, "tt", "tt", 1000);
Assert.fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException ignored) {
// expected
}
}
@Test
public void testSimpleAdd() throws Exception {
final int JOURNAL_SIZE = 1060;
setupAndLoadJournal(JOURNAL_SIZE, 10);
journalImpl.appendAddRecord(13, (byte) 14, new SimpleEncoding(1, (byte) 15), false);
journalImpl.forceMoveNextFile();
journalImpl.checkReclaimStatus();
setupAndLoadJournal(JOURNAL_SIZE, 10);
Assert.assertEquals(1, records.size());
Assert.assertEquals(13, records.get(0).id);
Assert.assertEquals(14, records.get(0).userRecordType);
Assert.assertEquals(1, records.get(0).data.length);
Assert.assertEquals(15, records.get(0).data[0]);
}
@Test
public void testAppendAndUpdateRecords() throws Exception {
final int JOURNAL_SIZE = 1060;
setupAndLoadJournal(JOURNAL_SIZE, 10);
Assert.assertEquals(0, records.size());
Assert.assertEquals(0, transactions.size());
for (int i = 0; i < 25; i++) {
byte[] bytes = new byte[5];
for (int j = 0; j < bytes.length; j++) {
bytes[j] = (byte) i;
}
journalImpl.appendAddRecord(i * 100L, (byte) i, bytes, false);
}
for (int i = 25; i < 50; i++) {
EncodingSupport support = new SimpleEncoding(5, (byte) i);
journalImpl.appendAddRecord(i * 100L, (byte) i, support, false);
}
setupAndLoadJournal(JOURNAL_SIZE, 1024);
Assert.assertEquals(50, records.size());
int i = 0;
for (RecordInfo recordItem : records) {
Assert.assertEquals(i * 100L, recordItem.id);
Assert.assertEquals(i, recordItem.getUserRecordType());
Assert.assertEquals(5, recordItem.data.length);
for (int j = 0; j < 5; j++) {
Assert.assertEquals((byte) i, recordItem.data[j]);
}
i++;
}
for (i = 40; i < 50; i++) {
byte[] bytes = new byte[10];
for (int j = 0; j < 10; j++) {
bytes[j] = (byte) 'x';
}
journalImpl.appendUpdateRecord(i * 100L, (byte) i, bytes, false);
}
setupAndLoadJournal(JOURNAL_SIZE, 1024);
i = 0;
for (RecordInfo recordItem : records) {
if (i < 50) {
Assert.assertEquals(i * 100L, recordItem.id);
Assert.assertEquals(i, recordItem.getUserRecordType());
Assert.assertEquals(5, recordItem.data.length);
for (int j = 0; j < 5; j++) {
Assert.assertEquals((byte) i, recordItem.data[j]);
}
} else {
Assert.assertEquals((i - 10) * 100L, recordItem.id);
Assert.assertEquals(i - 10, recordItem.getUserRecordType());
Assert.assertTrue(recordItem.isUpdate);
Assert.assertEquals(10, recordItem.data.length);
for (int j = 0; j < 10; j++) {
Assert.assertEquals((byte) 'x', recordItem.data[j]);
}
}
i++;
}
journalImpl.stop();
}
@Test
public void testPartialDelete() throws Exception {
final int JOURNAL_SIZE = 10000;
setupAndLoadJournal(JOURNAL_SIZE, 100);
journalImpl.setAutoReclaim(false);
journalImpl.checkReclaimStatus();
journalImpl.debugWait();
Assert.assertEquals(2, factory.listFiles("tt").size());
UnitTestLogger.LOGGER.debug("Initial:--> " + journalImpl.debug());
UnitTestLogger.LOGGER.debug("_______________________________");
for (int i = 0; i < 50; i++) {
journalImpl.appendAddRecord(i, (byte) 1, new SimpleEncoding(1, (byte) 'x'), false);
}
journalImpl.forceMoveNextFile();
// as the request to a new file is asynchronous, we need to make sure the
// async requests are done
journalImpl.debugWait();
Assert.assertEquals(3, factory.listFiles("tt").size());
for (int i = 10; i < 50; i++) {
journalImpl.appendDeleteRecord(i, false);
}
journalImpl.debugWait();
setupAndLoadJournal(JOURNAL_SIZE, 100);
Assert.assertEquals(10, records.size());
Assert.assertEquals(3, factory.listFiles("tt").size());
}
@Test
public void testAddAndDeleteReclaimWithoutTransactions() throws Exception {
final int JOURNAL_SIZE = 10000;
setupAndLoadJournal(JOURNAL_SIZE, 1);
journalImpl.setAutoReclaim(false);
journalImpl.checkReclaimStatus();
journalImpl.debugWait();
Assert.assertEquals(2, factory.listFiles("tt").size());
UnitTestLogger.LOGGER.debug("Initial:--> " + journalImpl.debug());
UnitTestLogger.LOGGER.debug("_______________________________");
for (int i = 0; i < 50; i++) {
journalImpl.appendAddRecord(i, (byte) 1, new SimpleEncoding(1, (byte) 'x'), false);
}
// as the request to a new file is asynchronous, we need to make sure the
// async requests are done
journalImpl.debugWait();
Assert.assertEquals(2, factory.listFiles("tt").size());
for (int i = 0; i < 50; i++) {
journalImpl.appendDeleteRecord(i, false);
}
journalImpl.forceMoveNextFile();
journalImpl.appendAddRecord(1000, (byte) 1, new SimpleEncoding(1, (byte) 'x'), false);
journalImpl.debugWait();
Assert.assertEquals(3, factory.listFiles("tt").size());
setupAndLoadJournal(JOURNAL_SIZE, 1);
Assert.assertEquals(1, records.size());
Assert.assertEquals(1000, records.get(0).id);
journalImpl.checkReclaimStatus();
UnitTestLogger.LOGGER.debug(journalImpl.debug());
journalImpl.debugWait();
UnitTestLogger.LOGGER.debug("Final:--> " + journalImpl.debug());
UnitTestLogger.LOGGER.debug("_______________________________");
UnitTestLogger.LOGGER.debug("Files bufferSize:" + factory.listFiles("tt").size());
Assert.assertEquals(2, factory.listFiles("tt").size());
}
@Test
public void testReloadWithTransaction() throws Exception {
final int JOURNAL_SIZE = 2000;
setupAndLoadJournal(JOURNAL_SIZE, 100);
Assert.assertEquals(0, records.size());
Assert.assertEquals(0, transactions.size());
journalImpl.appendAddRecordTransactional(1, 1, (byte) 1, new SimpleEncoding(1, (byte) 1));
setupAndLoadJournal(JOURNAL_SIZE, 100);
Assert.assertEquals(0, records.size());
Assert.assertEquals(0, transactions.size());
try {
journalImpl.appendCommitRecord(1L, true);
// This was supposed to throw an exception, as the transaction was
// forgotten (interrupted by a reload).
Assert.fail("Supposed to throw exception");
} catch (Exception e) {
UnitTestLogger.LOGGER.warn(e);
}
setupAndLoadJournal(JOURNAL_SIZE, 100);
Assert.assertEquals(0, records.size());
Assert.assertEquals(0, transactions.size());
}
@Test
public void testReloadWithInterruptedTransaction() throws Exception {
final int JOURNAL_SIZE = 1100;
setupAndLoadJournal(JOURNAL_SIZE, 100);
journalImpl.setAutoReclaim(false);
Assert.assertEquals(0, records.size());
Assert.assertEquals(0, transactions.size());
for (int i = 0; i < 10; i++) {
journalImpl.appendAddRecordTransactional(77L, 1, (byte) 1, new SimpleEncoding(1, (byte) 1));
journalImpl.forceMoveNextFile();
}
journalImpl.debugWait();
Assert.assertEquals(12, factory.listFiles("tt").size());
journalImpl.appendAddRecordTransactional(78L, 1, (byte) 1, new SimpleEncoding(1, (byte) 1));
Assert.assertEquals(12, factory.listFiles("tt").size());
setupAndLoadJournal(JOURNAL_SIZE, 100);
Assert.assertEquals(0, records.size());
Assert.assertEquals(0, transactions.size());
Assert.assertEquals(2, incompleteTransactions.size());
Assert.assertEquals((Long) 77L, incompleteTransactions.get(0));
Assert.assertEquals((Long) 78L, incompleteTransactions.get(1));
try {
journalImpl.appendCommitRecord(77L, true);
// This was supposed to throw an exception, as the transaction was
// forgotten (interrupted by a reload).
Assert.fail("Supposed to throw exception");
} catch (Exception e) {
UnitTestLogger.LOGGER.debug("Expected exception " + e, e);
}
setupAndLoadJournal(JOURNAL_SIZE, 100);
journalImpl.forceMoveNextFile();
journalImpl.checkReclaimStatus();
Assert.assertEquals(0, records.size());
Assert.assertEquals(0, transactions.size());
}
@Test
public void testReloadWithCompletedTransaction() throws Exception {
final int JOURNAL_SIZE = 2000;
setupAndLoadJournal(JOURNAL_SIZE, 100);
Assert.assertEquals(0, records.size());
Assert.assertEquals(0, transactions.size());
for (int i = 0; i < 10; i++) {
journalImpl.appendAddRecordTransactional(1, i, (byte) 1, new SimpleEncoding(1, (byte) 1));
journalImpl.forceMoveNextFile();
}
journalImpl.appendCommitRecord(1L, false);
journalImpl.debugWait();
Assert.assertEquals(12, factory.listFiles("tt").size());
setupAndLoadJournal(JOURNAL_SIZE, 100);
Assert.assertEquals(10, records.size());
Assert.assertEquals(0, transactions.size());
journalImpl.checkReclaimStatus();
Assert.assertEquals(10, journalImpl.getDataFilesCount());
Assert.assertEquals(12, factory.listFiles("tt").size());
for (int i = 0; i < 10; i++) {
journalImpl.appendDeleteRecordTransactional(2L, i);
journalImpl.forceMoveNextFile();
}
journalImpl.appendCommitRecord(2L, false);
journalImpl.appendAddRecord(100, (byte) 1, new SimpleEncoding(5, (byte) 1), false);
journalImpl.forceMoveNextFile();
journalImpl.appendAddRecord(101, (byte) 1, new SimpleEncoding(5, (byte) 1), false);
journalImpl.checkReclaimStatus();
Assert.assertEquals(1, journalImpl.getDataFilesCount());
setupAndLoadJournal(JOURNAL_SIZE, 100);
Assert.assertEquals(1, journalImpl.getDataFilesCount());
Assert.assertEquals(3, factory.listFiles("tt").size());
}
@Test
public void testTotalSize() throws Exception {
final int JOURNAL_SIZE = 2000;
setupAndLoadJournal(JOURNAL_SIZE, 100);
Assert.assertEquals(0, records.size());
Assert.assertEquals(0, transactions.size());
journalImpl.appendAddRecordTransactional(1L, 2L, (byte) 3, new SimpleEncoding(1900 - JournalImpl.SIZE_ADD_RECORD_TX - 1, (byte) 4));
journalImpl.appendCommitRecord(1L, false);
journalImpl.debugWait();
setupAndLoadJournal(JOURNAL_SIZE, 100);
Assert.assertEquals(1, records.size());
}
@Test
public void testReloadInvalidCheckSizeOnTransaction() throws Exception {
final int JOURNAL_SIZE = 2000;
setupAndLoadJournal(JOURNAL_SIZE, 100);
Assert.assertEquals(2, factory.listFiles("tt").size());
Assert.assertEquals(0, records.size());
Assert.assertEquals(0, transactions.size());
for (int i = 0; i < 2; i++) {
journalImpl.appendAddRecordTransactional(1L, i, (byte) 0, new SimpleEncoding(1, (byte) 15));
}
journalImpl.appendCommitRecord(1L, false);
journalImpl.debugWait();
System.out.println("Files = " + factory.listFiles("tt"));
SequentialFile file = factory.createSequentialFile("tt-1.tt");
file.open();
ByteBuffer buffer = ByteBuffer.allocate(100);
// Messing up with the first record (removing the position)
file.position(100);
file.read(buffer);
// jumping RecordType, FileId, TransactionID, RecordID, VariableSize,
// RecordType, RecordBody (that we know it is 1 )
buffer.position(1 + 4 + 8 + 8 + 4 + 1 + 1 + 1);
int posCheckSize = buffer.position();
Assert.assertEquals(JournalImpl.SIZE_ADD_RECORD_TX + 2, buffer.getInt());
buffer.position(posCheckSize);
buffer.putInt(-1);
buffer.rewind();
// Changing the check bufferSize, so reload will ignore this record
file.position(100);
file.writeDirect(buffer, true);
file.close();
setupAndLoadJournal(JOURNAL_SIZE, 100);
Assert.assertEquals(0, records.size());
journalImpl.checkReclaimStatus();
Assert.assertEquals(0, journalImpl.getDataFilesCount());
Assert.assertEquals(2, factory.listFiles("tt").size());
}
@Test
public void testPartiallyBrokenFile() throws Exception {
final int JOURNAL_SIZE = 20000;
setupAndLoadJournal(JOURNAL_SIZE, 100);
Assert.assertEquals(2, factory.listFiles("tt").size());
Assert.assertEquals(0, records.size());
Assert.assertEquals(0, transactions.size());
for (int i = 0; i < 20; i++) {
journalImpl.appendAddRecordTransactional(1L, i, (byte) 0, new SimpleEncoding(1, (byte) 15));
journalImpl.appendAddRecordTransactional(2L, i + 20L, (byte) 0, new SimpleEncoding(1, (byte) 15));
}
journalImpl.appendCommitRecord(1L, false);
journalImpl.appendCommitRecord(2L, false);
journalImpl.debugWait();
SequentialFile file = factory.createSequentialFile("tt-1.tt");
file.open();
ByteBuffer buffer = ByteBuffer.allocate(100);
// Messing up with the first record (removing the position)
file.position(100);
file.read(buffer);
// jumping RecordType, FileId, TransactionID, RecordID, VariableSize,
// RecordType, RecordBody (that we know it is 1 )
buffer.position(1 + 4 + 8 + 8 + 4 + 1 + 1 + 1);
int posCheckSize = buffer.position();
Assert.assertEquals(JournalImpl.SIZE_ADD_RECORD_TX + 2, buffer.getInt());
buffer.position(posCheckSize);
buffer.putInt(-1);
buffer.rewind();
// Changing the check bufferSize, so reload will ignore this record
file.position(100);
file.writeDirect(buffer, true);
file.close();
setupAndLoadJournal(JOURNAL_SIZE, 100);
Assert.assertEquals(20, records.size());
journalImpl.checkReclaimStatus();
}
@Test
public void testReduceFreeFiles() throws Exception {
final int JOURNAL_SIZE = 2000;
setupAndLoadJournal(JOURNAL_SIZE, 100, 10);
Assert.assertEquals(10, factory.listFiles("tt").size());
setupAndLoadJournal(JOURNAL_SIZE, 100, 2);
Assert.assertEquals(10, factory.listFiles("tt").size());
for (int i = 0; i < 10; i++) {
journalImpl.appendAddRecord(i, (byte) 0, new SimpleEncoding(1, (byte) 0), false);
journalImpl.forceMoveNextFile();
}
setupAndLoadJournal(JOURNAL_SIZE, 100, 2);
Assert.assertEquals(10, records.size());
Assert.assertEquals(12, factory.listFiles("tt").size());
for (int i = 0; i < 10; i++) {
journalImpl.appendDeleteRecord(i, false);
}
journalImpl.forceMoveNextFile();
journalImpl.checkReclaimStatus();
setupAndLoadJournal(JOURNAL_SIZE, 100, 2);
Assert.assertEquals(0, records.size());
Assert.assertEquals(2, factory.listFiles("tt").size());
}
@Test
public void testReloadIncompleteTransaction() throws Exception {
final int JOURNAL_SIZE = 2000;
setupAndLoadJournal(JOURNAL_SIZE, 1);
Assert.assertEquals(2, factory.listFiles("tt").size());
Assert.assertEquals(0, records.size());
Assert.assertEquals(0, transactions.size());
for (int i = 0; i < 10; i++) {
journalImpl.appendAddRecordTransactional(1L, i, (byte) 0, new SimpleEncoding(1, (byte) 15));
}
for (int i = 10; i < 20; i++) {
journalImpl.appendAddRecordTransactional(1L, i, (byte) 0, new SimpleEncoding(1, (byte) 15));
}
journalImpl.appendCommitRecord(1L, false);
journalImpl.debugWait();
SequentialFile file = factory.createSequentialFile("tt-1.tt");
file.open();
ByteBuffer buffer = ByteBuffer.allocate(100);
// Messing up with the first record (removing the position)
file.position(100);
file.read(buffer);
buffer.position(1);
buffer.putInt(-1);
buffer.rewind();
// Messing up with the first record (changing the fileID, so Journal
// reload will think the record came from a different journal usage)
file.position(100);
buffer.rewind();
file.writeDirect(buffer, true);
file.close();
setupAndLoadJournal(JOURNAL_SIZE, 100);
Assert.assertEquals(0, records.size());
journalImpl.checkReclaimStatus();
Assert.assertEquals(0, journalImpl.getDataFilesCount());
Assert.assertEquals(2, factory.listFiles("tt").size());
}
@Test
public void testPrepareAloneOnSeparatedFile() throws Exception {
final int JOURNAL_SIZE = 20000;
setupAndLoadJournal(JOURNAL_SIZE, 100);
Assert.assertEquals(0, records.size());
Assert.assertEquals(0, transactions.size());
for (int i = 0; i < 10; i++) {
journalImpl.appendAddRecordTransactional(1L, i, (byte) 0, new SimpleEncoding(1, (byte) 15));
}
journalImpl.forceMoveNextFile();
SimpleEncoding xidEncoding = new SimpleEncoding(10, (byte) 'a');
journalImpl.appendPrepareRecord(1L, xidEncoding, false);
journalImpl.appendCommitRecord(1L, false);
for (int i = 0; i < 10; i++) {
journalImpl.appendDeleteRecordTransactional(2L, i);
}
journalImpl.appendCommitRecord(2L, false);
journalImpl.appendAddRecord(100L, (byte) 0, new SimpleEncoding(1, (byte) 10), false); // Add
// anything
// to
// keep
// holding
// the
// file
journalImpl.forceMoveNextFile();
journalImpl.checkReclaimStatus();
setupAndLoadJournal(JOURNAL_SIZE, 100);
Assert.assertEquals(1, records.size());
}
@Test
public void testCommitWithMultipleFiles() throws Exception {
final int JOURNAL_SIZE = 20000;
setupAndLoadJournal(JOURNAL_SIZE, 100);
Assert.assertEquals(0, records.size());
Assert.assertEquals(0, transactions.size());
for (int i = 0; i < 50; i++) {
if (i == 10) {
journalImpl.forceMoveNextFile();
}
journalImpl.appendAddRecordTransactional(1L, i, (byte) 0, new SimpleEncoding(1, (byte) 15));
}
journalImpl.appendCommitRecord(1L, false);
for (int i = 0; i < 10; i++) {
if (i == 5) {
journalImpl.forceMoveNextFile();
}
journalImpl.appendDeleteRecordTransactional(2L, i);
}
journalImpl.appendCommitRecord(2L, false);
journalImpl.forceMoveNextFile();
journalImpl.checkReclaimStatus();
setupAndLoadJournal(JOURNAL_SIZE, 100);
Assert.assertEquals(40, records.size());
}
@Test
public void testSimplePrepare() throws Exception {
final int JOURNAL_SIZE = 3 * 1024;
setupAndLoadJournal(JOURNAL_SIZE, 1);
Assert.assertEquals(0, records.size());
Assert.assertEquals(0, transactions.size());
SimpleEncoding xid = new SimpleEncoding(10, (byte) 1);
journalImpl.appendAddRecord(10L, (byte) 0, new SimpleEncoding(10, (byte) 0), false);
journalImpl.appendDeleteRecordTransactional(1L, 10L, new SimpleEncoding(100, (byte) 'j'));
journalImpl.appendPrepareRecord(1, xid, false);
journalImpl.debugWait();
setupAndLoadJournal(JOURNAL_SIZE, 1);
Assert.assertEquals(1, transactions.size());
Assert.assertEquals(1, transactions.get(0).getRecordsToDelete().size());
Assert.assertEquals(1, records.size());
for (RecordInfo record : transactions.get(0).getRecordsToDelete()) {
byte[] data = record.data;
Assert.assertEquals(100, data.length);
for (byte element : data) {
Assert.assertEquals((byte) 'j', element);
}
}
Assert.assertEquals(10, transactions.get(0).getExtraData().length);
for (int i = 0; i < 10; i++) {
Assert.assertEquals((byte) 1, transactions.get(0).getExtraData()[i]);
}
journalImpl.appendCommitRecord(1L, false);
journalImpl.debugWait();
setupAndLoadJournal(JOURNAL_SIZE, 1);
Assert.assertEquals(0, transactions.size());
Assert.assertEquals(0, records.size());
}
@Test
public void testReloadWithPreparedTransaction() throws Exception {
final int JOURNAL_SIZE = 3 * 1024;
setupAndLoadJournal(JOURNAL_SIZE, 1);
Assert.assertEquals(0, records.size());
Assert.assertEquals(0, transactions.size());
for (int i = 0; i < 10; i++) {
journalImpl.appendAddRecordTransactional(1, i, (byte) 1, new SimpleEncoding(50, (byte) 1));
journalImpl.forceMoveNextFile();
}
journalImpl.debugWait();
SimpleEncoding xid1 = new SimpleEncoding(10, (byte) 1);
journalImpl.appendPrepareRecord(1L, xid1, false);
Assert.assertEquals(12, factory.listFiles("tt").size());
setupAndLoadJournal(JOURNAL_SIZE, 1024);
Assert.assertEquals(0, records.size());
Assert.assertEquals(1, transactions.size());
Assert.assertEquals(10, transactions.get(0).getExtraData().length);
for (int i = 0; i < 10; i++) {
Assert.assertEquals((byte) 1, transactions.get(0).getExtraData()[i]);
}
journalImpl.checkReclaimStatus();
Assert.assertEquals(10, journalImpl.getDataFilesCount());
Assert.assertEquals(12, factory.listFiles("tt").size());
journalImpl.appendCommitRecord(1L, false);
setupAndLoadJournal(JOURNAL_SIZE, 1024);
Assert.assertEquals(10, records.size());
journalImpl.checkReclaimStatus();
for (int i = 0; i < 10; i++) {
journalImpl.appendDeleteRecordTransactional(2L, i);
}
SimpleEncoding xid2 = new SimpleEncoding(15, (byte) 2);
journalImpl.appendPrepareRecord(2L, xid2, false);
setupAndLoadJournal(JOURNAL_SIZE, 1);
Assert.assertEquals(1, transactions.size());
Assert.assertEquals(15, transactions.get(0).getExtraData().length);
for (byte element : transactions.get(0).getExtraData()) {
Assert.assertEquals(2, element);
}
Assert.assertEquals(10, journalImpl.getDataFilesCount());
Assert.assertEquals(12, factory.listFiles("tt").size());
journalImpl.appendCommitRecord(2L, false);
setupAndLoadJournal(JOURNAL_SIZE, 1);
Assert.assertEquals(0, records.size());
Assert.assertEquals(0, transactions.size());
journalImpl.forceMoveNextFile();
// Reclaiming should still be able to reclaim a file if a transaction was ignored
journalImpl.checkReclaimStatus();
journalImpl.flush();
}
@Test
public void testReloadInvalidPrepared() throws Exception {
final int JOURNAL_SIZE = 3000;
setupAndLoadJournal(JOURNAL_SIZE, 100);
Assert.assertEquals(0, records.size());
Assert.assertEquals(0, transactions.size());
for (int i = 0; i < 10; i++) {
journalImpl.appendAddRecordTransactional(1, i, (byte) 1, new SimpleEncoding(50, (byte) 1));
}
journalImpl.appendPrepareRecord(1L, new SimpleEncoding(13, (byte) 0), false);
setupAndLoadJournal(JOURNAL_SIZE, 100);
Assert.assertEquals(0, records.size());
Assert.assertEquals(1, transactions.size());
SequentialFile file = factory.createSequentialFile("tt-1.tt");
file.open();
ByteBuffer buffer = ByteBuffer.allocate(100);
// Messing up with the first record (removing the position)
file.position(100);
file.read(buffer);
buffer.position(1);
buffer.putInt(-1);
buffer.rewind();
// Messing up with the first record (changing the fileID, so Journal
// reload will think the record came from a different journal usage)
file.position(100);
file.writeDirect(buffer, true);
file.close();
setupAndLoadJournal(JOURNAL_SIZE, 100);
Assert.assertEquals(0, records.size());
Assert.assertEquals(0, transactions.size());
}
@Test
public void testReclaimAfterRollabck() throws Exception {
final int JOURNAL_SIZE = 2000;
setupAndLoadJournal(JOURNAL_SIZE, 1);
for (int i = 0; i < 10; i++) {
journalImpl.appendAddRecordTransactional(1L, i, (byte) 0, new SimpleEncoding(1, (byte) 0));
journalImpl.forceMoveNextFile();
}
journalImpl.appendRollbackRecord(1L, false);
journalImpl.forceMoveNextFile();
journalImpl.checkReclaimStatus();
Assert.assertEquals(0, journalImpl.getDataFilesCount());
setupAndLoadJournal(JOURNAL_SIZE, 1);
Assert.assertEquals(0, journalImpl.getDataFilesCount());
Assert.assertEquals(2, factory.listFiles("tt").size());
}
// It should be ok to write records on AIO, and later read then on NIO
@Test
public void testDecreaseAlignment() throws Exception {
final int JOURNAL_SIZE = 512 * 4;
setupAndLoadJournal(JOURNAL_SIZE, 512);
for (int i = 0; i < 10; i++) {
journalImpl.appendAddRecordTransactional(1L, i, (byte) 0, new SimpleEncoding(1, (byte) 0));
}
journalImpl.appendCommitRecord(1L, false);
setupAndLoadJournal(JOURNAL_SIZE, 100);
Assert.assertEquals(10, records.size());
setupAndLoadJournal(JOURNAL_SIZE, 1);
Assert.assertEquals(10, records.size());
}
// It should be ok to write records on NIO, and later read then on AIO
@Test
public void testIncreaseAlignment() throws Exception {
final int JOURNAL_SIZE = 512 * 4;
setupAndLoadJournal(JOURNAL_SIZE, 1);
for (int i = 0; i < 10; i++) {
journalImpl.appendAddRecordTransactional(1L, i, (byte) 0, new SimpleEncoding(1, (byte) 0));
}
journalImpl.appendCommitRecord(1L, false);
setupAndLoadJournal(JOURNAL_SIZE, 100);
Assert.assertEquals(10, records.size());
setupAndLoadJournal(JOURNAL_SIZE, 512);
Assert.assertEquals(10, records.size());
}
@Test
public void testEmptyPrepare() throws Exception {
final int JOURNAL_SIZE = 512 * 4;
setupAndLoadJournal(JOURNAL_SIZE, 1);
journalImpl.appendPrepareRecord(2L, new SimpleEncoding(10, (byte) 'j'), false);
journalImpl.forceMoveNextFile();
journalImpl.appendAddRecord(1L, (byte) 0, new SimpleEncoding(10, (byte) 'k'), false);
setupAndLoadJournal(JOURNAL_SIZE, 1);
Assert.assertEquals(1, journalImpl.getDataFilesCount());
Assert.assertEquals(1, transactions.size());
journalImpl.forceMoveNextFile();
setupAndLoadJournal(JOURNAL_SIZE, 1);
Assert.assertEquals(1, journalImpl.getDataFilesCount());
Assert.assertEquals(1, transactions.size());
journalImpl.appendCommitRecord(2L, false);
journalImpl.appendDeleteRecord(1L, false);
journalImpl.forceMoveNextFile();
setupAndLoadJournal(JOURNAL_SIZE, 0);
journalImpl.forceMoveNextFile();
journalImpl.debugWait();
journalImpl.checkReclaimStatus();
Assert.assertEquals(0, transactions.size());
Assert.assertEquals(0, journalImpl.getDataFilesCount());
}
@Test
public void testReclaimingAfterConcurrentAddsAndDeletesTx() throws Exception {
testReclaimingAfterConcurrentAddsAndDeletes(true);
}
@Test
public void testReclaimingAfterConcurrentAddsAndDeletesNonTx() throws Exception {
testReclaimingAfterConcurrentAddsAndDeletes(false);
}
public void testReclaimingAfterConcurrentAddsAndDeletes(final boolean transactional) throws Exception {
final int JOURNAL_SIZE = 10 * 1024;
setupAndLoadJournal(JOURNAL_SIZE, 1);
Assert.assertEquals(0, records.size());
Assert.assertEquals(0, transactions.size());
final CountDownLatch latchReady = new CountDownLatch(2);
final CountDownLatch latchStart = new CountDownLatch(1);
final AtomicInteger finishedOK = new AtomicInteger(0);
final BlockingQueue<Integer> queueDelete = new LinkedBlockingQueue<>();
final int NUMBER_OF_ELEMENTS = 500;
Thread t1 = new Thread() {
@Override
public void run() {
try {
latchReady.countDown();
ActiveMQTestBase.waitForLatch(latchStart);
for (int i = 0; i < NUMBER_OF_ELEMENTS; i++) {
if (transactional) {
journalImpl.appendAddRecordTransactional(i, i, (byte) 1, new SimpleEncoding(50, (byte) 1));
journalImpl.appendCommitRecord(i, false);
} else {
journalImpl.appendAddRecord(i, (byte) 1, new SimpleEncoding(50, (byte) 1), false);
}
queueDelete.offer(i);
}
finishedOK.incrementAndGet();
} catch (Exception e) {
e.printStackTrace();
}
}
};
Thread t2 = new Thread() {
@Override
public void run() {
try {
latchReady.countDown();
ActiveMQTestBase.waitForLatch(latchStart);
for (int i = 0; i < NUMBER_OF_ELEMENTS; i++) {
Integer toDelete = queueDelete.poll(10, TimeUnit.SECONDS);
if (toDelete == null) {
break;
}
if (transactional) {
journalImpl.appendDeleteRecordTransactional(toDelete, toDelete, new SimpleEncoding(50, (byte) 1));
journalImpl.appendCommitRecord(i, false);
} else {
journalImpl.appendDeleteRecord(toDelete, false);
}
}
finishedOK.incrementAndGet();
} catch (Exception e) {
e.printStackTrace();
}
}
};
t1.start();
t2.start();
ActiveMQTestBase.waitForLatch(latchReady);
latchStart.countDown();
t1.join();
t2.join();
Assert.assertEquals(2, finishedOK.intValue());
journalImpl.debugWait();
journalImpl.forceMoveNextFile();
journalImpl.debugWait();
journalImpl.checkReclaimStatus();
Assert.assertEquals(0, journalImpl.getDataFilesCount());
Assert.assertEquals(2, factory.listFiles("tt").size());
}
@Test
public void testAlignmentOverReload() throws Exception {
factory = new FakeSequentialFileFactory(512, false);
journalImpl = new JournalImpl(512 + 512 * 3, 20, 20, 0, 0, factory, "amq", "amq", 1000);
journalImpl.start();
journalImpl.load(AlignedJournalImplTest.dummyLoader);
journalImpl.appendAddRecord(1L, (byte) 0, new SimpleEncoding(100, (byte) 'a'), false);
journalImpl.appendAddRecord(2L, (byte) 0, new SimpleEncoding(100, (byte) 'b'), false);
journalImpl.appendAddRecord(3L, (byte) 0, new SimpleEncoding(100, (byte) 'b'), false);
journalImpl.appendAddRecord(4L, (byte) 0, new SimpleEncoding(100, (byte) 'b'), false);
journalImpl.stop();
journalImpl = new JournalImpl(512 + 1024 + 512, 20, 20, 0, 0, factory, "amq", "amq", 1000);
addActiveMQComponent(journalImpl);
journalImpl.start();
journalImpl.load(AlignedJournalImplTest.dummyLoader);
// It looks silly, but this forceMoveNextFile is in place to replicate one
// specific bug caught during development
journalImpl.forceMoveNextFile();
journalImpl.appendDeleteRecord(1L, false);
journalImpl.appendDeleteRecord(2L, false);
journalImpl.appendDeleteRecord(3L, false);
journalImpl.appendDeleteRecord(4L, false);
journalImpl.stop();
journalImpl = new JournalImpl(512 + 1024 + 512, 20, 20, 0, 0, factory, "amq", "amq", 1000);
addActiveMQComponent(journalImpl);
journalImpl.start();
ArrayList<RecordInfo> info = new ArrayList<>();
ArrayList<PreparedTransactionInfo> trans = new ArrayList<>();
journalImpl.load(info, trans, null);
Assert.assertEquals(0, info.size());
Assert.assertEquals(0, trans.size());
}
// Package protected ---------------------------------------------
// Protected -----------------------------------------------------
@Override
@Before
public void setUp() throws Exception {
super.setUp();
records = new ArrayList<>();
transactions = new ArrayList<>();
incompleteTransactions = new ArrayList<>();
factory = null;
journalImpl = null;
}
@Override
@After
public void tearDown() throws Exception {
stopComponent(journalImpl);
if (factory != null)
factory.stop();
records = null;
transactions = null;
incompleteTransactions = null;
factory = null;
journalImpl = null;
super.tearDown();
}
// Private -------------------------------------------------------
private void setupAndLoadJournal(final int journalSize, final int alignment) throws Exception {
setupAndLoadJournal(journalSize, alignment, 2);
}
private void setupAndLoadJournal(final int journalSize,
final int alignment,
final int numberOfMinimalFiles) throws Exception {
if (factory == null) {
factory = new FakeSequentialFileFactory(alignment, true);
}
if (journalImpl != null) {
journalImpl.stop();
}
journalImpl = new JournalImpl(journalSize, numberOfMinimalFiles, numberOfMinimalFiles, 0, 0, factory, "tt", "tt", 1000);
addActiveMQComponent(journalImpl);
journalImpl.start();
records.clear();
transactions.clear();
incompleteTransactions.clear();
journalImpl.load(records, transactions, new TransactionFailureCallback() {
@Override
public void failedTransaction(final long transactionID,
final List<RecordInfo> records,
final List<RecordInfo> recordsToDelete) {
System.out.println("records.length = " + records.size());
incompleteTransactions.add(transactionID);
}
});
}
// Inner classes -------------------------------------------------
}