1 /*- 2 * See the file LICENSE for redistribution information. 3 * 4 * Copyright (c) 2004, 2010 Oracle and/or its affiliates. All rights reserved. 5 * 6 */ 7 8 package com.sleepycat.je.recovery; 9 10 import static org.junit.Assert.assertEquals; 11 12 import java.io.File; 13 14 import org.junit.Test; 15 16 import com.sleepycat.bind.tuple.IntegerBinding; 17 import com.sleepycat.bind.tuple.StringBinding; 18 import com.sleepycat.je.CheckpointConfig; 19 import com.sleepycat.je.Cursor; 20 import com.sleepycat.je.Database; 21 import com.sleepycat.je.DatabaseConfig; 22 import com.sleepycat.je.DatabaseEntry; 23 import com.sleepycat.je.DbInternal; 24 import com.sleepycat.je.Durability; 25 import com.sleepycat.je.Environment; 26 import com.sleepycat.je.EnvironmentConfig; 27 import com.sleepycat.je.OperationStatus; 28 import com.sleepycat.je.Transaction; 29 import com.sleepycat.je.dbi.EnvironmentImpl; 30 import com.sleepycat.util.test.SharedTestUtils; 31 import com.sleepycat.util.test.TestBase; 32 33 /** 34 * Test recovery redo of a LN, when the redo provokes slot reuse. 35 */ 36 public class LNSlotReuseTest extends TestBase { 37 private final File envHome; 38 LNSlotReuseTest()39 public LNSlotReuseTest() { 40 envHome = SharedTestUtils.getTestDir(); 41 } 42 43 /** 44 * This test was motivated by SR [#17770], which had to do with the 45 * fact recovery redos were not appropriately clearing the known deleted 46 * and pending deleted fields in the BIN. When a slot is reused, those 47 * bits must be initialized properly so the LN does not erroneously seem 48 * be deleted. 49 * 50 * This unit test is trying to generate the following log sequence: 51 * 100 LNA (key xxx) is inserted 52 * 110 txn commit for insert of LNA 53 * 120 LNA is deleted 54 * 125 checkpoint start 55 * 130 BIN for key xxx, pending deleted bit for LNA is set, slot 56 * points to lsn 120. 57 * 135 checkpoint end 58 * 140 txn commit for delete of LNA (in memory, BIN's known deleted bit 59 is set, but it's not set in the log) 60 * 150 LNB (key xxx) is inserted, goes into slot for LNA. 61 * 160 txn commit for LNB. 62 * 63 * The goal is to provoke a recovery that runs from lsn 125->160. LNB is 64 * committed, but goes into a slot previously occupied by LNA. Since LNB's 65 * pending deleted state is incorrectly set, a call to Database.count() 66 * skips over the slot. 67 */ 68 @Test testLNSlotReuse()69 public void testLNSlotReuse() 70 throws Exception { 71 72 EnvironmentConfig envConfig = new EnvironmentConfig(); 73 envConfig.setAllowCreate(true); 74 envConfig.setTransactional(true); 75 envConfig.setDurability(Durability.COMMIT_WRITE_NO_SYNC); 76 77 DatabaseConfig dbConfig = new DatabaseConfig(); 78 dbConfig.setAllowCreate(true); 79 dbConfig.setTransactional(true); 80 81 Environment env = null; 82 Database db = null; 83 84 try { 85 env = new Environment(envHome, envConfig); 86 db = env.openDatabase(null, "testDB", dbConfig); 87 88 DatabaseEntry key = new DatabaseEntry(); 89 DatabaseEntry data = new DatabaseEntry(); 90 IntegerBinding.intToEntry(1024, key); 91 StringBinding.stringToEntry("herococo", data); 92 93 /* 94 * Insert and delete a record, so our BIN will have a slot with 95 * pending deleted set. 96 */ 97 Transaction txn = env.beginTransaction(null, null); 98 db.put(txn, key, data); // insert record A 99 txn.commit(); 100 txn = env.beginTransaction(null, null); 101 db.delete(txn, key); // delete record A 102 103 /* Checkpoint to flush our target BIN out to disk. */ 104 CheckpointConfig ckptConfig = new CheckpointConfig(); 105 ckptConfig.setForce(true); 106 env.checkpoint(ckptConfig); 107 108 /* 109 * Committing the deletion after the checkpoint means the BIN will 110 * go out with Pending Deleted set. If we commit before the 111 * checkpoint, the BIN will be compressed, the slot will be 112 * deleted, and we won't exercise slot reuse. 113 */ 114 txn.commit(); 115 116 /* Insert record B and reuse the slot previously held by A */ 117 txn = env.beginTransaction(null, null); 118 db.put(txn, key, data); 119 txn.commit(); 120 db.close(); 121 122 /* Simulate a crash. */ 123 EnvironmentImpl envImpl = DbInternal.getEnvironmentImpl(env); 124 envImpl.close(false); 125 126 /* Do a recovery */ 127 env = new Environment(envHome, envConfig); 128 db = env.openDatabase(null, "testDB", dbConfig); 129 130 /* 131 * Compare counts obtained via a cursor traveral to a count from 132 * Database.count() The expected value is 1. 133 */ 134 Cursor cursor = db.openCursor(null, null); 135 int counter = 0; 136 while (OperationStatus.SUCCESS == 137 cursor.getNext(key, data, null)) { 138 counter++; 139 } 140 cursor.close(); 141 142 /* 143 * We expect the count to be 1, and we expect the two methods to 144 * be equal. 145 */ 146 assertEquals(1, counter); 147 assertEquals(counter, db.count()); 148 } finally { 149 if (db != null) { 150 db.close(); 151 } 152 153 if (env != null) { 154 env.close(); 155 } 156 } 157 } 158 } 159