/**
* Copyright 2012 Akiban Technologies, Inc.
*
* Licensed 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 com.persistit;
import static com.persistit.unit.UnitTestProperties.VOLUME_NAME;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import org.junit.Test;
import com.persistit.exception.PersistitException;
/**
* https://bugs.launchpad.net/akiban-persistit/+bug/1065677
*
* Doing a long delete in OLB_latest dataset, my Mac crashed, taking my VM with
* it. The Vm was running akiban. It was via a local psql client.
*
* <pre>
* INFO 15:14:05,258 Starting services.
* Exception in thread "main" java.lang.AssertionError
* at com.persistit.JournalManager$PageNode.setPrevious(JournalManager.java:1918)
* at com.persistit.RecoveryManager.scanLoadPageMap(RecoveryManager.java:1150)
* at com.persistit.RecoveryManager.scanOneRecord(RecoveryManager.java:937)
* at com.persistit.RecoveryManager.findAndValidateKeystone(RecoveryManager.java:784)
* at com.persistit.RecoveryManager.buildRecoveryPlan(RecoveryManager.java:
* </pre>
*
*/
public class Bug1065677Test extends PersistitUnitTestCase {
private final static String TREE_NAME = "Bug1065677Test";
private Exchange getExchange() throws PersistitException {
return _persistit.getExchange(VOLUME_NAME, TREE_NAME, true);
}
/**
* This method tries to recreate the state at which the journal files in bug
* 1065677 arrived. Plan:
*
* 1. Write a transaction that updates multiple pages.
*
* 2. Flush all buffers so that there are pages to recover and then crash
* Persistit so there is no checkpoint.
*
* 3. Restart Persistit, but advance the system timestamp to simulate
* somewhat chaotic ordering of page writes created by reapplying a huge
* delete operation in the transaction.
*
* 4. Crash once again. This will leave pages from the original epoch in the
* page map and will add versions of those pages with larger timestamps.
* This crash simulates the JVM being killed during recovery processing. In
* the actual case it appears there is no post-recovery checkpoint, merely
* lots of dirty pages. To simulate this outcome we truncate about 50K of
* bytes form the end of the journal file as a surrogate for the system
* having not completed recovery.
*
* 5. Restart the system once again. Now do some normal processing work,
* simulated here by adding another transaction.
*
* 6. Magic happens here: we now perform a rollover, which writes both the
* branch map and the page map into a single PM record. Because the PM
* record is written by two separate loops, some page P found in both the
* branch map and the page map is written into two separate PM sub-records.
* As it turns out, the version in the branch map has a smaller timestamp
* than the one in the page map. This sets up the AssertionError.
*
* 7. Restart Persistit to exploit the bug during scanLoadPageMap.
*
* @throws Exception
*/
@Test
public void breakTimestampSequence() throws Exception {
doTransaction();
final Configuration config = _persistit.getConfiguration();
final long lastTimestamp = _persistit.getCurrentTimestamp();
_persistit.flush();
_persistit.crash();
_persistit = new Persistit();
_persistit.getTimestampAllocator().updateTimestamp(lastTimestamp + 1000);
_persistit.setConfiguration(config);
_persistit.initialize();
_persistit.crash();
truncate();
config.setAppendOnly(true);
_persistit = new Persistit(config);
doTransaction();
final JournalManager jman = _persistit.getJournalManager();
jman.rollover();
_persistit.close();
_persistit = new Persistit(config);
_persistit.close();
}
private void doTransaction() throws Exception {
final Exchange ex = getExchange();
final Transaction txn = ex.getTransaction();
txn.begin();
ex.getValue().put(RED_FOX);
for (int i = 0; i < 10000; i++) {
ex.to(i).store();
}
txn.commit();
txn.end();
}
private void truncate() throws IOException {
final JournalManager jman = _persistit.getJournalManager();
final long lastAddress = jman.getCurrentAddress();
final File file = jman.addressToFile(lastAddress);
final RandomAccessFile raf = new RandomAccessFile(file, "rw");
try {
final long length = raf.length();
raf.setLength(length - 50000);
} finally {
raf.close();
}
}
}