KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > sleepycat > je > test > SecondaryDirtyReadTest


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

8
9 package com.sleepycat.je.test;
10
11 import junit.framework.Test;
12
13 import com.sleepycat.je.Database;
14 import com.sleepycat.je.DatabaseConfig;
15 import com.sleepycat.je.DatabaseEntry;
16 import com.sleepycat.je.DatabaseException;
17 import com.sleepycat.je.LockMode;
18 import com.sleepycat.je.OperationStatus;
19 import com.sleepycat.je.SecondaryConfig;
20 import com.sleepycat.je.SecondaryCursor;
21 import com.sleepycat.je.SecondaryDatabase;
22 import com.sleepycat.je.SecondaryKeyCreator;
23 import com.sleepycat.je.Transaction;
24 import com.sleepycat.je.junit.JUnitMethodThread;
25 import com.sleepycat.je.util.TestUtils;
26
27 /**
28  * Tests for multithreading problems when using read-uncommitted with
29  * secondaries. If a primary record is updated while performing a
30  * read-uncommitted (in between reading the secondary and the primary), we need
31  * to be sure that we don't return inconsistent results to the user. For
32  * example, we should not return a primary data value that no longer contains
33  * the secondary key. We also need to ensure that deleting a primary record in
34  * the middle of a secondary read does not appear as a corrupt secondary. In
35  * both of these cases it should appear that the record does not exist, from
36  * the viewpoint of an application using a cursor.
37  *
38  * <p>These tests create two threads, one reading and the other deleting or
39  * updating. The intention is for reading thread and the delete/update thread
40  * to race in operating on the same key (nextKey). If the reading thread reads
41  * the secondary, then the other thread deletes the primary, then the reading
42  * thread tries to read the primary, we've accomplished our goal. Prior to
43  * when we handled that case in SecondaryCursor, that situation would cause a
44  * "secondary corrupt" exception.</p>
45  */

46 public class SecondaryDirtyReadTest extends MultiKeyTxnTestCase {
47
48     private static final int MAX_KEY = 1000;
49
50     public static Test suite() {
51         return multiKeyTxnTestSuite(SecondaryDirtyReadTest.class, null,
52                                     null);
53                                     //new String[] {TxnTestCase.TXN_NULL});
54
}
55
56     private int nextKey;
57     private Database priDb;
58     private SecondaryDatabase secDb;
59     private LockMode lockMode = LockMode.READ_UNCOMMITTED;
60
61     /**
62      * Closes databases, then calls the super.tearDown to close the env.
63      */

64     public void tearDown()
65         throws Exception JavaDoc {
66
67         if (secDb != null) {
68             try {
69                 secDb.close();
70             } catch (Exception JavaDoc e) {}
71             secDb = null;
72         }
73         if (priDb != null) {
74             try {
75                 priDb.close();
76             } catch (Exception JavaDoc e) {}
77             priDb = null;
78         }
79         super.tearDown();
80     }
81
82     /**
83      * Tests that deleting primary records does not cause secondary
84      * read-uncommitted to throw a "secondary corrupt" exception.
85      */

86     public void testDeleteWhileReadingByKey()
87     throws Throwable JavaDoc {
88
89         doTest("runReadUncommittedByKey", "runPrimaryDelete");
90     }
91
92     /**
93      * Same as testDeleteWhileReadingByKey but does a scan. Read-uncommitted
94      * for scan and keyed reads are implemented differently, since scanning
95      * moves to the next record when a deletion is detected while a keyed read
96      * returns NOTFOUND.
97      */

98     public void testDeleteWhileScanning()
99     throws Throwable JavaDoc {
100
101         doTest("runReadUncommittedScan", "runPrimaryDelete");
102     }
103
104     /**
105      * Tests that updating primary records, to cause deletion of the secondary
106      * key record, does not cause secondary read-uncommitted to return
107      * inconsistent data (a primary datum without a secondary key value).
108      */

109     public void testUpdateWhileReadingByKey()
110     throws Throwable JavaDoc {
111
112         doTest("runReadUncommittedByKey", "runPrimaryUpdate");
113     }
114
115     /**
116      * Same as testUpdateWhileReadingByKey but does a scan.
117      */

118     public void testUpdateWhileScanning()
119     throws Throwable JavaDoc {
120
121         doTest("runReadUncommittedScan", "runPrimaryUpdate");
122     }
123
124     /**
125      * Runs two threads for the given method names, after populating the
126      * database.
127      */

128     public void doTest(String JavaDoc method1, String JavaDoc method2)
129     throws Throwable JavaDoc {
130
131         JUnitMethodThread tester1 = new JUnitMethodThread(method1 + "-t1",
132                                                           method1, this);
133         JUnitMethodThread tester2 = new JUnitMethodThread(method2 + "-t2",
134                                                           method2, this);
135         priDb = openPrimary("testDB");
136         secDb = openSecondary(priDb, "testSecDB", false);
137         addRecords();
138         tester1.start();
139         tester2.start();
140         tester1.finishTest();
141         tester2.finishTest();
142         secDb.close();
143         secDb = null;
144         priDb.close();
145         priDb = null;
146     }
147
148     /**
149      * Deletes the key that is being read by the other thread.
150      */

151     public void runPrimaryDelete()
152         throws DatabaseException {
153
154         DatabaseEntry key = new DatabaseEntry();
155         while (nextKey < MAX_KEY - 1) {
156             Transaction txn = txnBegin();
157             key.setData(TestUtils.getTestArray(nextKey));
158             OperationStatus status = priDb.delete(txn, key);
159             if (status != OperationStatus.SUCCESS) {
160                 assertEquals(OperationStatus.NOTFOUND, status);
161             }
162             txnCommit(txn);
163         }
164     }
165
166     /**
167      * Updates the record for the key that is being read by the other thread,
168      * changing the datum to -1 so it will cause the secondary key record to
169      * be deleted.
170      */

171     public void runPrimaryUpdate()
172         throws DatabaseException {
173
174         DatabaseEntry key = new DatabaseEntry();
175         DatabaseEntry data = new DatabaseEntry();
176         while (nextKey < MAX_KEY - 1) {
177             Transaction txn = txnBegin();
178             key.setData(TestUtils.getTestArray(nextKey));
179             data.setData(TestUtils.getTestArray(-1));
180             OperationStatus status = priDb.put(txn, key, data);
181             assertEquals(OperationStatus.SUCCESS, status);
182             txnCommit(txn);
183         }
184     }
185
186     /**
187      * Does a read-uncommitted by key, retrying until it is deleted by the
188      * delete/update thread, then moves to the next key. We shouldn't get an
189      * exception, just a NOTFOUND when it is deleted.
190      */

191     public void runReadUncommittedByKey()
192         throws DatabaseException {
193
194         DatabaseEntry key = new DatabaseEntry();
195         DatabaseEntry pKey = new DatabaseEntry();
196         DatabaseEntry data = new DatabaseEntry();
197         while (nextKey < MAX_KEY - 1) {
198             key.setData(TestUtils.getTestArray(nextKey));
199             OperationStatus status = secDb.get(null, key, pKey, data,
200                                                lockMode);
201             if (status != OperationStatus.SUCCESS) {
202                 assertEquals(OperationStatus.NOTFOUND, status);
203                 nextKey++;
204             } else {
205                 assertEquals(nextKey, TestUtils.getTestVal(key.getData()));
206                 assertEquals(nextKey, TestUtils.getTestVal(pKey.getData()));
207                 assertEquals(nextKey, TestUtils.getTestVal(data.getData()));
208             }
209         }
210     }
211
212     /**
213      * Does a read-uncommitted scan through the whole key range, but moves
214      * forward only after the key is deleted by the delete/update thread. We
215      * shouldn't get an exception or a NOTFOUND, but we may skip values when a
216      * key is deleted.
217      */

218     public void runReadUncommittedScan()
219         throws DatabaseException {
220
221         DatabaseEntry key = new DatabaseEntry();
222         DatabaseEntry pKey = new DatabaseEntry();
223         DatabaseEntry data = new DatabaseEntry();
224         SecondaryCursor cursor = secDb.openSecondaryCursor(null, null);
225         while (nextKey < MAX_KEY - 1) {
226             OperationStatus status = cursor.getNext(key, pKey, data,
227                                                     lockMode);
228             assertEquals("nextKey=" + nextKey,
229                          OperationStatus.SUCCESS, status);
230             int keyFound = TestUtils.getTestVal(key.getData());
231             assertEquals(keyFound, TestUtils.getTestVal(pKey.getData()));
232             assertEquals(keyFound, TestUtils.getTestVal(data.getData()));
233             /* Let the delete/update thread catch up. */
234             nextKey = keyFound;
235             if (nextKey < MAX_KEY - 1) {
236                 while (status != OperationStatus.KEYEMPTY) {
237                     assertEquals(OperationStatus.SUCCESS, status);
238                     status = cursor.getCurrent(key, pKey, data,
239                                                lockMode);
240                 }
241                 nextKey = keyFound + 1;
242             }
243         }
244         cursor.close();
245     }
246
247     /**
248      * Adds records for the entire key range.
249      */

250     private void addRecords()
251         throws DatabaseException {
252
253         DatabaseEntry key = new DatabaseEntry();
254         DatabaseEntry data = new DatabaseEntry();
255         Transaction txn = txnBegin();
256         for (int i = 0; i < MAX_KEY; i += 1) {
257             byte[] val = TestUtils.getTestArray(i);
258             key.setData(val);
259             data.setData(val);
260             OperationStatus status = priDb.putNoOverwrite(txn, key, data);
261             assertEquals(OperationStatus.SUCCESS, status);
262         }
263         txnCommit(txn);
264     }
265
266     /**
267      * Opens the primary database.
268      */

269     private Database openPrimary(String JavaDoc name)
270         throws DatabaseException {
271
272         DatabaseConfig dbConfig = new DatabaseConfig();
273         dbConfig.setTransactional(isTransactional);
274         dbConfig.setAllowCreate(true);
275         Transaction txn = txnBegin();
276         Database priDb;
277         try {
278             priDb = env.openDatabase(txn, name, dbConfig);
279         } finally {
280             txnCommit(txn);
281         }
282         assertNotNull(priDb);
283         return priDb;
284     }
285
286     /**
287      * Opens the secondary database.
288      */

289     private SecondaryDatabase openSecondary(Database priDb, String JavaDoc dbName,
290                                             boolean allowDuplicates)
291         throws DatabaseException {
292
293         SecondaryConfig dbConfig = new SecondaryConfig();
294         dbConfig.setTransactional(isTransactional);
295         dbConfig.setAllowCreate(true);
296         dbConfig.setSortedDuplicates(allowDuplicates);
297         if (useMultiKey) {
298             dbConfig.setMultiKeyCreator
299                 (new SimpleMultiKeyCreator(new MyKeyCreator()));
300         } else {
301             dbConfig.setKeyCreator(new MyKeyCreator());
302         }
303         Transaction txn = txnBegin();
304         SecondaryDatabase secDb;
305         try {
306             secDb = env.openSecondaryDatabase(txn, dbName, priDb, dbConfig);
307         } finally {
308             txnCommit(txn);
309         }
310         return secDb;
311     }
312
313     /**
314      * Creates secondary keys for a primary datum with a non-negative value.
315      */

316     private static class MyKeyCreator implements SecondaryKeyCreator {
317
318         public boolean createSecondaryKey(SecondaryDatabase secondary,
319                                           DatabaseEntry key,
320                                           DatabaseEntry data,
321                                           DatabaseEntry result)
322             throws DatabaseException {
323
324             int val = TestUtils.getTestVal(data.getData());
325             if (val >= 0) {
326                 result.setData(TestUtils.getTestArray(val));
327                 return true;
328             } else {
329                 return false;
330             }
331         }
332     }
333 }
334
Popular Tags