/** * 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.util.SequencerConstants.LONG_RECORD_ALLOCATE_B; import static com.persistit.util.SequencerConstants.LONG_RECORD_ALLOCATE_SCHEDULED; import static com.persistit.util.ThreadSequencer.addSchedules; import static com.persistit.util.ThreadSequencer.disableSequencer; import static com.persistit.util.ThreadSequencer.enableSequencer; import static com.persistit.util.ThreadSequencer.sequence; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import org.junit.Test; import com.persistit.exception.PersistitException; /** * https://bugs.launchpad.net/akiban-persistit/+bug/1003578 * * In a recent branch, lp:~pbeaman/akiban-persistit/fix_959456, a new assert was * added in JournalManager#writePageToJournal checking an invariant that was * suppose to be in place for quite some time. The assert checks that a new * entry to the pageMap has a timestamp greater than or equal to the previous * entry. * * This assert fired in a server-daily run, on LotsOfTablesDT: * http://172.16.20.104 * :8080/view/daily/job/server-daily/com.akiban$akiban-server/1018/ * java.lang.AssertionError * * <pre> * at com.persistit.JournalManager.writePageToJournal(JournalManager.java:1020) * at com.persistit.VolumeStorageV2.writePage(VolumeStorageV2.java:477) * at com.persistit.Buffer.writePage(Buffer.java:538) * at com.persistit.Exchange.storeOverlengthRecord(Exchange.java:3850) * at com.persistit.Exchange.storeInternal(Exchange.java:1382) * at com.persistit.Exchange.store(Exchange.java:1307) * at com.persistit.Exchange.store(Exchange.java:2551) * at com.akiban.server.store.PersistitStoreSchemaManager.saveProtobuf(PersistitStoreSchemaManager.java:869) * at com.akiban.server.store.PersistitStoreSchemaManager.saveAISToStorage(PersistitStoreSchemaManager.java:830) * at com.akiban.server.store.PersistitStoreSchemaManager.commitAISChange(PersistitStoreSchemaManager.java:896) * at com.akiban.server.store.PersistitStoreSchemaManager.createTableDefinition(PersistitStoreSchemaManager.java:187) * at com.akiban.server.service.dxl.BasicDDLFunctions.createTable(BasicDDLFunctions.java:85) * at com.akiban.server.service.dxl.HookableDDLFunctions.createTable(HookableDDLFunctions.java:65) * at com.akiban.server.test.ApiTestBase.createTable(ApiTestBase.java:442) * at com.akiban.server.test.daily.slap.LotsOfTablesDT.createLotsOfTablesTest(LotsOfTablesDT.java:62) * </pre> * * Which corresponds to this check (line 1020 of r300, 1030 of r302): * * <pre> * if (oldPageNode != null) { * assert oldPageNode.getTimestamp() <= pageNode.getTimestamp(); * } * </pre> * * Bug theory: * * Bug is caused by the violation in LongRecordHelper#storeLongRecord(): * * - Test thread calls storeLongRecord with some timestamp T1. * * - CLEANUP_MANAGER processes a CleanupAntiValue and adds a page to the garbage * chain with timestamp T2 > T1. * * - PAGE_WRITER writes that dirty page with timestamp T2. * * - Test thread allocates a new page from the garbage chain, a page that is * already dirty and in the PagePap with timestamp T2, but storeOverlengthRecord * marks it dirty with timestamp T1. * * This analysis works both before and after branch * lp:~pbeaman/akiban-persistit/fix_959456. Prior to that branch the guilty * method is Exchange#storeOverlengthRecord as shown in the stack trace above. */ public class Bug1003578Test extends PersistitUnitTestCase { @Test public void storeLongRecordFromDeallocatedPages() throws Exception { /* * Create a tree with a few data pages */ final Exchange ex1 = _persistit.getExchange("persistit", "Bug1003578Test", true); final Transaction txn = ex1.getTransaction(); ex1.getValue().put(RED_FOX); for (int i = 0; i < 10000; i++) { ex1.to(i).store(); } /* * Temporarily suspect the cleanup manager */ _persistit.getCleanupManager().setPollInterval(-1); enableSequencer(true); addSchedules(LONG_RECORD_ALLOCATE_SCHEDULED); /* * Now transactionally delete the pages; this will create AntiValues */ txn.begin(); ex1.clear().removeAll(); txn.commit(); txn.end(); /* * Traverse the AntiValues. This will enqueue the pages for pruning. */ assertFalse("Should have no visible keys", ex1.to(Key.BEFORE).next()); final String longString = createString(1000000); final Thread t = new Thread(new Runnable() { @Override public void run() { final Exchange ex2 = new Exchange(ex1); try { /* * Now create long records */ ex2.getValue().put(longString); ex2.to("longrec").store(); } catch (final PersistitException e) { e.printStackTrace(); } } }); t.start(); /* * Clean up the non-edge AntiValues */ _persistit.getCleanupManager().poll(); /* * Clean up the left-edge AntiValues */ _persistit.getCleanupManager().poll(); sequence(LONG_RECORD_ALLOCATE_B); disableSequencer(); t.join(); ex1.to("longrec").fetch(); assertEquals("Should have stored the long record string", longString, ex1.getValue().getString()); } }