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