KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > sleepycat > je > cleaner > SR12885Test


1 /*-
2  * See the file LICENSE for redistribution information.
3  *
4  * Copyright (c) 2002,2006 Oracle. All rights reserved.
5  *
6  * $Id: SR12885Test.java,v 1.5 2006/10/30 21:14:42 bostic Exp $
7  */

8
9 package com.sleepycat.je.cleaner;
10
11 import java.io.File JavaDoc;
12 import java.io.IOException JavaDoc;
13
14 import junit.framework.TestCase;
15
16 import com.sleepycat.je.CheckpointConfig;
17 import com.sleepycat.je.Database;
18 import com.sleepycat.je.DatabaseConfig;
19 import com.sleepycat.je.DatabaseEntry;
20 import com.sleepycat.je.DatabaseException;
21 import com.sleepycat.je.DbInternal;
22 import com.sleepycat.je.Environment;
23 import com.sleepycat.je.EnvironmentConfig;
24 import com.sleepycat.je.OperationStatus;
25 import com.sleepycat.je.Transaction;
26 import com.sleepycat.je.config.EnvironmentParams;
27 import com.sleepycat.je.log.FileManager;
28 import com.sleepycat.je.util.TestUtils;
29
30 /**
31  * Reproduces a problem found in SR12885 where we failed to migrate a pending
32  * LN if the slot was reused by an active transaction and that transaction was
33  * later aborted.
34  *
35  * This bug can manifest as a LogNotFoundException. However, there was another
36  * bug that caused this bug to manifest sometimes as a NOTFOUND return value.
37  * This secondary problem -- more sloppyness than a real bug -- was that the
38  * PendingDeleted flag was not cleared during an abort. If the PendingDeleted
39  * flag is set, the low level fetch method will return null rather than
40  * throwing a LogFileNotFoundException. This caused a NOTFOUND in some cases.
41  *
42  * The sequence that causes the bug is:
43  *
44  * 1) The cleaner processes a file containing LN-A (node A) for key X. Key X
45  * is a non-deleted LN.
46  *
47  * 2) The cleaner sets the migrate flag on the BIN entry for LN-A.
48  *
49  * 3) In transaction T-1, LN-A is deleted and replaced by LN-B with key X,
50  * reusing the same slot but assigning a new node ID. At this point both node
51  * IDs (LN-A and LN-B) are locked.
52  *
53  * 4) The cleaner (via a checkpoint or eviction that logs the BIN) tries to
54  * migrate LN-B, the current LN in the BIN, but finds it locked. It adds LN-B
55  * to the pending LN list.
56  *
57  * 5) T-1 aborts, putting the LSN of LN-A back into the BIN slot.
58  *
59  * 6) In transaction T-2, LN-A is deleted and replaced by LN-C with key X,
60  * reusing the same slot but assigning a new node ID. At this point both node
61  * IDs (LN-A and LN-C) are locked.
62  *
63  * 7) The cleaner (via a checkpoint or wakeup) processes the pending LN-B. It
64  * first gets a lock on node B, then does the tree lookup. It finds LN-C in
65  * the tree, but it doesn't notice that it has a different node ID than the
66  * node it locked.
67  *
68  * 8) The cleaner sees that LN-C is deleted, and therefore no migration is
69  * necessary -- this is incorrect. It removes LN-B from the pending list,
70  * allowing the cleaned file to be deleted.
71  *
72  * 9) T-2 aborts, putting the LSN of LN-A back into the BIN slot.
73  *
74  * 10) A fetch of key X will fail, since the file containing the LSN for LN-A
75  * has been deleted. If we didn't clear the PendingDeleted flag, this will
76  * cause a NOTFOUND error instead of a LogFileNotFoundException.
77  */

78 public class SR12885Test extends TestCase {
79
80     private static final String JavaDoc DB_NAME = "foo";
81
82     private static final CheckpointConfig forceConfig = new CheckpointConfig();
83     static {
84         forceConfig.setForce(true);
85     }
86
87     private File JavaDoc envHome;
88     private Environment env;
89     private Database db;
90
91     public SR12885Test() {
92         envHome = new File JavaDoc(System.getProperty(TestUtils.DEST_DIR));
93     }
94
95     public void setUp()
96         throws IOException JavaDoc, DatabaseException {
97
98         TestUtils.removeLogFiles("Setup", envHome, false);
99         TestUtils.removeFiles("Setup", envHome, FileManager.DEL_SUFFIX);
100     }
101
102     public void tearDown()
103         throws IOException JavaDoc, DatabaseException {
104
105         try {
106             if (env != null) {
107                 env.close();
108             }
109         } catch (Throwable JavaDoc e) {
110             System.out.println("tearDown: " + e);
111         }
112                 
113         try {
114             TestUtils.removeLogFiles("tearDown", envHome, true);
115             TestUtils.removeFiles("tearDown", envHome, FileManager.DEL_SUFFIX);
116         } catch (Throwable JavaDoc e) {
117             System.out.println("tearDown: " + e);
118         }
119
120         db = null;
121         env = null;
122         envHome = null;
123     }
124
125     /**
126      * Opens the environment and database.
127      */

128     private void openEnv()
129         throws DatabaseException {
130
131         EnvironmentConfig config = TestUtils.initEnvConfig();
132     DbInternal.disableParameterValidation(config);
133         config.setTransactional(true);
134         config.setAllowCreate(true);
135         /* Do not run the daemons. */
136         config.setConfigParam
137             (EnvironmentParams.ENV_RUN_CLEANER.getName(), "false");
138         config.setConfigParam
139             (EnvironmentParams.ENV_RUN_EVICTOR.getName(), "false");
140         config.setConfigParam
141         (EnvironmentParams.ENV_RUN_CHECKPOINTER.getName(), "false");
142         config.setConfigParam
143             (EnvironmentParams.ENV_RUN_INCOMPRESSOR.getName(), "false");
144         /* Use a small log file size to make cleaning more frequent. */
145         config.setConfigParam(EnvironmentParams.LOG_FILE_MAX.getName(),
146                               Integer.toString(1024));
147         env = new Environment(envHome, config);
148
149         openDb();
150     }
151
152     /**
153      * Opens that database.
154      */

155     private void openDb()
156         throws DatabaseException {
157
158         DatabaseConfig dbConfig = new DatabaseConfig();
159         dbConfig.setTransactional(true);
160         dbConfig.setAllowCreate(true);
161         db = env.openDatabase(null, DB_NAME, dbConfig);
162     }
163
164     /**
165      * Closes the environment and database.
166      */

167     private void closeEnv()
168         throws DatabaseException {
169
170         if (db != null) {
171             db.close();
172             db = null;
173         }
174         if (env != null) {
175             env.close();
176             env = null;
177         }
178     }
179
180     public void testSR12885()
181         throws DatabaseException {
182
183         openEnv();
184
185         final int COUNT = 10;
186         DatabaseEntry key = new DatabaseEntry();
187         DatabaseEntry data = new DatabaseEntry(TestUtils.getTestArray(0));
188         OperationStatus status;
189
190         /* Add some records, enough to fill a log file. */
191         for (int i = 0; i < COUNT; i += 1) {
192             key.setData(TestUtils.getTestArray(i));
193             status = db.putNoOverwrite(null, key, data);
194             assertEquals(OperationStatus.SUCCESS, status);
195         }
196
197         /*
198          * Delete all but key 0, so the first file can be cleaned but key 0
199          * will need to be migrated.
200          */

201         for (int i = 1; i < COUNT; i += 1) {
202             key.setData(TestUtils.getTestArray(i));
203             status = db.delete(null, key);
204             assertEquals(OperationStatus.SUCCESS, status);
205         }
206
207         /*
208          * Checkpoint and clean to set the migrate flag for key 0. This must
209          * be done when key 0 is not locked, so that it will not be put onto
210          * the pending list yet. Below we cause it to be put onto the pending
211          * list with a different node ID.
212          */

213         env.checkpoint(forceConfig);
214         int cleaned = env.cleanLog();
215         assertTrue("cleaned=" + cleaned, cleaned > 0);
216
217         /*
218          * Using a transaction, delete then insert key 0, reusing the slot.
219          * The insertion assigns a new node ID. Don't abort the transaction
220          * until after the cleaner migration is finished.
221          */

222         Transaction txn = env.beginTransaction(null, null);
223         key.setData(TestUtils.getTestArray(0));
224         status = db.delete(txn, key);
225         assertEquals(OperationStatus.SUCCESS, status);
226         status = db.putNoOverwrite(txn, key, data);
227         assertEquals(OperationStatus.SUCCESS, status);
228
229         /*
230          * Checkpoint again to perform LN migration. LN migration will not
231          * migrate key 0 because it is locked -- it will be put onto the
232          * pending list. But the LN put on the pending list will be the newly
233          * inserted node, which has a different node ID than the LN that needs
234          * to be migrated -- this is the first condition for the bug.
235          */

236         env.checkpoint(forceConfig);
237
238         /*
239          * Abort the transaction to revert to the original node ID for key 0.
240          * Then perform a delete with a new transaction. This makes the
241          * current LN for key 0 deleted.
242          */

243         txn.abort();
244         txn = env.beginTransaction(null, null);
245         key.setData(TestUtils.getTestArray(0));
246         status = db.delete(txn, key);
247         assertEquals(OperationStatus.SUCCESS, status);
248
249         /*
250          * The current state of key 0 is that the BIN contains a deleted LN,
251          * and that LN has a node ID that is different than the one in the
252          * pending LN list. This node is the one that needs to be migrated.
253          *
254          * Perform a checkpoint to cause pending LNs to be processed and then
255          * delete the cleaned file. When we process the pending LN, we'll lock
256          * the pending LN's node ID (the one we inserted and aborted), which is
257          * the wrong node ID. We'll then examine the current LN, find it
258          * deleted, and neglect to migrate the LN that needs to be migrated.
259          * The error is that we don't lock the node ID of the current LN.
260          *
261          * Then abort the delete transaction. That will revert the BIN entry
262          * to the node we failed to migrate. If we then try to fetch key 0,
263          * we'll get LogNotFoundException.
264          */

265         env.checkpoint(forceConfig);
266         txn.abort();
267         status = db.get(null, key, data, null);
268         assertEquals(OperationStatus.SUCCESS, status);
269
270         /* If we get this far without LogNotFoundException, it's fixed. */
271
272         closeEnv();
273     }
274 }
275
Popular Tags