KickJava   Java API By Example, From Geeks To Geeks.

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


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

8
9 package com.sleepycat.je.test;
10
11 import java.io.File JavaDoc;
12 import java.io.IOException JavaDoc;
13
14 import junit.framework.Test;
15 import junit.framework.TestCase;
16 import junit.framework.TestSuite;
17
18 import com.sleepycat.bind.tuple.IntegerBinding;
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.DatabaseException;
24 import com.sleepycat.je.DeadlockException;
25 import com.sleepycat.je.Environment;
26 import com.sleepycat.je.EnvironmentConfig;
27 import com.sleepycat.je.LockStats;
28 import com.sleepycat.je.OperationStatus;
29 import com.sleepycat.je.Transaction;
30 import com.sleepycat.je.config.EnvironmentParams;
31 import com.sleepycat.je.junit.JUnitThread;
32 import com.sleepycat.je.log.FileManager;
33 import com.sleepycat.je.util.TestUtils;
34
35 /**
36  * Tests read operation restarts that are the by product of phantom prevention
37  * (range locking) added in SR [#10477].
38  */

39 public class PhantomRestartTest extends TestCase {
40
41     /*
42      * Spec Parameters: Oper name, InsertKey1, InsertKey2, Oper instance
43      *
44      * A- InsertKey1 is inserted in transaction T0 and committed.
45      * B- T1 starts and performs Oper passing InsertKey1; it finishes the
46      * operation, but doesn't commit.
47      * C- T2 starts and attempts to insert InsertKey2, but is blocked by T1.
48      * D- T3 starts and performs Oper passing InsertKey2, but is restarted
49      * because it is blocked by T2.
50      * E- T1 is committed, allowing T2 and T3 to finish also.
51      * F- T4 performs Oper a final time passing InsertKey2.
52      *
53      * For each Spec below the Lock owners and waiters are described in between
54      * steps D and E above. This state describes the condition where the read
55      * operation (Oper) is performing restarts because it is blocked by a
56      * RANGE_INSERT.
57      *
58      * To understand how read operation restarts work consider the "First"
59      * Spec below. When T1 releases K2, T2 should finish, and T3 should read
60      * K1. If restart were not implemented in the lock manager, T3 would read
61      * K2 instead of K1; K1 would then be a phantom with respect to T3. If
62      * search restarts were not implemented, a RangeRestartException would
63      * surface at the user level. These errors were observed when running this
64      * test before search restarts were fully implemented.
65      */

66     private static Spec[] SPECS = {
67
68         /*
69          * T1 calls getFirst -- owns RANGE_READ on K2.
70          * T2 inserts K1 -- waits for RANGE_INSERT on K2.
71          * T3 calls getFirst -- requests RANGE_READ on K2: restarts.
72          */

73         new Spec("First", 2, 1, new Oper() {
74             void doOper(int insertedKey) throws DatabaseException {
75                 status = cursor.getFirst(key, data, null);
76                 checkStatus(OperationStatus.SUCCESS);
77                 checkKey(insertedKey);
78             }
79         }),
80
81         /*
82          * T1 calls getLast -- owns RANGE_READ on EOF.
83          * T2 inserts K2 -- waits for RANGE_INSERT on EOF.
84          * T3 calls getLast -- requests RANGE_READ on EOF: restarts.
85          */

86         new Spec("Last", 1, 2, new Oper() {
87             void doOper(int insertedKey) throws DatabaseException {
88                 status = cursor.getLast(key, data, null);
89                 checkStatus(OperationStatus.SUCCESS);
90                 checkKey(insertedKey);
91             }
92         }),
93
94         /*
95          * T1 calls getSearchKey on K1 -- owns RANGE_READ on K2.
96          * T2 inserts K1 -- waits for RANGE_INSERT on K2.
97          * T3 calls getSearchKey on K1 -- requests RANGE_READ on K2: restarts.
98          */

99         new Spec("Search", 2, 1, new Oper() {
100             void doOper(int insertedKey) throws DatabaseException {
101                 setKey(1);
102                 status = dups ? cursor.getSearchBoth(key, data, null)
103                               : cursor.getSearchKey(key, data, null);
104                 checkStatus((insertedKey == 1) ? OperationStatus.SUCCESS
105                                                : OperationStatus.NOTFOUND);
106             }
107         }),
108
109         /*
110          * T1 calls getSearchKeyRange on K0 -- owns RANGE_READ on K2.
111          * T2 inserts K1 -- waits for RANGE_INSERT on K2.
112          * T3 calls getSearchKeyRange on K0 -- requests RANGE_READ on K2:
113          * restarts.
114          */

115         new Spec("SearchRange", 2, 1, new Oper() {
116             void doOper(int insertedKey) throws DatabaseException {
117                 setKey(0);
118                 status = dups ? cursor.getSearchBothRange(key, data, null)
119                               : cursor.getSearchKeyRange(key, data, null);
120                 checkStatus(OperationStatus.SUCCESS);
121                 checkKey(insertedKey);
122             }
123         }),
124
125         /*
126          * T1 calls getNext from K1 -- owns RANGE_READ on EOF.
127          * T2 inserts K2 -- waits for RANGE_INSERT on EOF.
128          * T3 calls getNext from K1 -- requests RANGE_READ on EOF: restarts.
129          */

130         new Spec("Next", 1, 2, new Oper() {
131             void doOper(int insertedKey) throws DatabaseException {
132                 status = cursor.getFirst(key, data, null);
133                 checkStatus(OperationStatus.SUCCESS);
134                 checkKey(1);
135                 status = cursor.getNext(key, data, null);
136                 checkStatus((insertedKey == 2) ? OperationStatus.SUCCESS
137                                                : OperationStatus.NOTFOUND);
138             }
139         }),
140
141         /*
142          * T1 calls getPrev from K2 -- owns RANGE_READ on K2.
143          * T2 inserts K1 -- waits for RANGE_INSERT on K2.
144          * T3 calls getPrev from K2 -- requests RANGE_READ on K2: restarts.
145          */

146         new Spec("Prev", 2, 1, new Oper() {
147             void doOper(int insertedKey) throws DatabaseException {
148                 status = cursor.getLast(key, data, null);
149                 checkStatus(OperationStatus.SUCCESS);
150                 checkKey(2);
151                 status = cursor.getPrev(key, data, null);
152                 checkStatus((insertedKey == 1) ? OperationStatus.SUCCESS
153                                                : OperationStatus.NOTFOUND);
154             }
155         }),
156
157         /*
158          * NextDup, NextNoDup, PrevDup and PrevNoDup are not tested here.
159          * Restarts for these operations are implemented together with Next and
160          * Prev operations, so testing was skipped.
161          */

162     };
163
164     private static abstract class Oper {
165
166         PhantomRestartTest test;
167         boolean dups;
168         Cursor cursor;
169         DatabaseEntry key;
170         DatabaseEntry data;
171         OperationStatus status;
172
173         void init(PhantomRestartTest test, Cursor cursor) {
174             this.test = test;
175             this.cursor = cursor;
176             this.dups = test.dups;
177             this.key = new DatabaseEntry();
178             this.data = new DatabaseEntry();
179             this.status = null;
180         }
181
182         void checkStatus(OperationStatus expected) {
183             TestCase.assertEquals(expected, status);
184         }
185
186         void setKey(int val) {
187             if (dups) {
188                 IntegerBinding.intToEntry(100, key);
189                 IntegerBinding.intToEntry(val, data);
190             } else {
191                 IntegerBinding.intToEntry(val, key);
192             }
193         }
194
195         void checkKey(int expected) {
196             if (dups) {
197                 TestCase.assertEquals(100, IntegerBinding.entryToInt(key));
198                 TestCase.assertEquals
199                     (expected, IntegerBinding.entryToInt(data));
200             } else {
201                 TestCase.assertEquals
202                     (expected, IntegerBinding.entryToInt(key));
203             }
204         }
205
206         abstract void doOper(int insertedKey)
207             throws DatabaseException;
208     }
209
210     private static class Spec {
211
212         String JavaDoc name;
213         int insertKey1;
214         int insertKey2;
215         Oper oper;
216
217         Spec(String JavaDoc name, int insertKey1, int insertKey2, Oper oper) {
218             this.name = name;
219             this.insertKey1 = insertKey1;
220             this.insertKey2 = insertKey2;
221             this.oper = oper;
222         }
223     }
224
225     public static Test suite()
226         throws Exception JavaDoc {
227
228         TestSuite suite = new TestSuite();
229         for (int i = 0; i < SPECS.length; i += 1) {
230             for (int j = 0; j < 2; j += 1) {
231                 boolean dups = (j != 0);
232                 suite.addTest(new PhantomRestartTest(SPECS[i], dups));
233             }
234         }
235         return suite;
236     }
237
238     private static final int MAX_INSERT_MILLIS = 5000;
239
240     private File JavaDoc envHome;
241     private Environment env;
242     private Database db;
243     private JUnitThread writerThread;
244     private JUnitThread readerThread;
245     private boolean dups;
246     private Spec spec;
247
248     public PhantomRestartTest(Spec spec, boolean dups) {
249         super(spec.name + (dups ? "-Dups" : ""));
250         this.spec = spec;
251         this.dups = dups;
252         envHome = new File JavaDoc(System.getProperty(TestUtils.DEST_DIR));
253     }
254
255     public void setUp()
256         throws IOException JavaDoc {
257
258         TestUtils.removeLogFiles("Setup", envHome, false);
259         TestUtils.removeFiles("Setup", envHome, FileManager.DEL_SUFFIX);
260     }
261     
262     public void tearDown()
263         throws Exception JavaDoc {
264
265         try {
266             if (env != null) {
267                 env.close();
268             }
269         } catch (Throwable JavaDoc e) {
270             System.out.println("tearDown: " + e);
271         }
272                 
273         try {
274             //*
275
TestUtils.removeLogFiles("tearDown", envHome, true);
276             TestUtils.removeFiles("tearDown", envHome, FileManager.DEL_SUFFIX);
277             //*/
278
} catch (Throwable JavaDoc e) {
279             System.out.println("tearDown: " + e);
280         }
281
282         envHome = null;
283         env = null;
284         db = null;
285
286         if (writerThread != null) {
287             while (writerThread.isAlive()) {
288                 writerThread.interrupt();
289                 Thread.yield();
290             }
291             writerThread = null;
292         }
293
294         if (readerThread != null) {
295             while (readerThread.isAlive()) {
296                 readerThread.interrupt();
297                 Thread.yield();
298             }
299             readerThread = null;
300         }
301     }
302
303     /**
304      * Opens the environment and database.
305      */

306     private void openEnv()
307         throws DatabaseException {
308
309         EnvironmentConfig envConfig = TestUtils.initEnvConfig();
310         envConfig.setAllowCreate(true);
311         envConfig.setTransactional(true);
312         envConfig.setTxnSerializableIsolation(true);
313         
314         /* Disable the daemons so the don't interfere with stats. */
315         envConfig.setConfigParam
316             (EnvironmentParams.ENV_RUN_EVICTOR.getName(), "false");
317         envConfig.setConfigParam
318             (EnvironmentParams.ENV_RUN_CLEANER.getName(), "false");
319         envConfig.setConfigParam
320             (EnvironmentParams.ENV_RUN_CHECKPOINTER.getName(), "false");
321         envConfig.setConfigParam
322             (EnvironmentParams.ENV_RUN_INCOMPRESSOR.getName(), "false");
323        
324         env = new Environment(envHome, envConfig);
325
326         DatabaseConfig dbConfig = new DatabaseConfig();
327         dbConfig.setAllowCreate(true);
328         dbConfig.setTransactional(true);
329         dbConfig.setSortedDuplicates(dups);
330         db = env.openDatabase(null, "PhantomRestartTest", dbConfig);
331     }
332
333     /**
334      * Closes the environment and database.
335      */

336     private void closeEnv()
337         throws DatabaseException {
338
339         if (db != null) {
340             db.close();
341             db = null;
342         }
343         if (env != null) {
344             env.close();
345             env = null;
346         }
347     }
348
349     public void runTest()
350         throws DatabaseException, InterruptedException JavaDoc {
351
352         openEnv();
353
354         /* T0 inserts first key. */
355         if (dups) {
356
357             /*
358              * Create a dup tree and delete it to avoid deadlocking. Note that
359              * we have the compressor disabled to make this work. Deadlocking
360              * occurs without a dup tree because insertion locks the sibling
361              * key when creating a dup tree from a single LN. This extra
362              * locking throws off our test.
363              */

364             insert(100, 0);
365             insert(100, 1);
366             DatabaseEntry key = new DatabaseEntry();
367             IntegerBinding.intToEntry(100, key);
368             db.delete(null, key);
369
370             /* Insert the dup key we're testing with. */
371             insert(100, spec.insertKey1);
372         } else {
373             insert(spec.insertKey1, 0);
374         }
375
376         /* T1 performs Oper. */
377         Transaction readerTxn = env.beginTransaction(null, null);
378         Cursor cursor = db.openCursor(readerTxn, null);
379         spec.oper.init(this, cursor);
380         spec.oper.doOper(spec.insertKey1);
381
382         /* T2 starts to insert second key, waits on T1. */
383         if (dups) {
384             startInsert(100, spec.insertKey2);
385         } else {
386             startInsert(spec.insertKey2, 0);
387         }
388
389         /* T3 performs Oper. */
390         startReadOper(spec.insertKey2);
391
392         /* Close T1 to allow T2 and T3 to finish. */
393         cursor.close();
394         readerTxn.commitNoSync();
395         waitForInsert();
396         waitForReadOper();
397
398         /* T4 performs Oper again in this thread as a double-check. */
399         readerTxn = env.beginTransaction(null, null);
400         cursor = db.openCursor(readerTxn, null);
401         spec.oper.init(this, cursor);
402         spec.oper.doOper(spec.insertKey2);
403         cursor.close();
404         readerTxn.commit();
405
406         closeEnv();
407     }
408
409     /**
410      * Inserts the given key and data in a new transaction and commits it.
411      */

412     private void insert(int keyVal, int dataVal)
413         throws DatabaseException {
414
415         DatabaseEntry key = new DatabaseEntry();
416         DatabaseEntry data = new DatabaseEntry();
417         IntegerBinding.intToEntry(keyVal, key);
418         IntegerBinding.intToEntry(dataVal, data);
419         OperationStatus status;
420         Transaction writerTxn = env.beginTransaction(null, null);
421         try {
422             if (dups) {
423                 status = db.putNoDupData(writerTxn, key, data);
424             } else {
425                 status = db.putNoOverwrite(writerTxn, key, data);
426             }
427         } catch (DeadlockException e) {
428             writerTxn.abort();
429             throw e;
430         }
431         assertEquals(OperationStatus.SUCCESS, status);
432         writerTxn.commitNoSync();
433     }
434
435     /**
436      * Starts writer thread and waits for it to start the insert.
437      */

438     private void startInsert(final int keyVal, final int dataVal)
439         throws DatabaseException, InterruptedException JavaDoc {
440
441         LockStats origStats = env.getLockStats(null);
442
443         writerThread = new JUnitThread("Writer") {
444             public void testBody()
445                 throws DatabaseException {
446                 DatabaseEntry key = new DatabaseEntry();
447                 DatabaseEntry data = new DatabaseEntry();
448                 IntegerBinding.intToEntry(keyVal, key);
449                 IntegerBinding.intToEntry(dataVal, data);
450                 Transaction writerTxn = env.beginTransaction(null, null);
451                 OperationStatus status;
452                 if (dups) {
453                     status = db.putNoDupData(writerTxn, key, data);
454                 } else {
455                     status = db.putNoOverwrite(writerTxn, key, data);
456                 }
457                 assertEquals(OperationStatus.SUCCESS, status);
458                 writerTxn.commitNoSync();
459             }
460         };
461
462         writerThread.start();
463         waitForBlock(origStats);
464     }
465
466     /**
467      * Waits for the writer thread to finish.
468      */

469     private void waitForInsert() {
470
471         try {
472             writerThread.finishTest();
473         } catch (Throwable JavaDoc e) {
474             e.printStackTrace();
475             fail(e.toString());
476         } finally {
477             writerThread = null;
478         }
479     }
480
481     /**
482      * Starts reader thread and waits for it to start the read operation.
483      */

484     private void startReadOper(final int operKeyParam)
485         throws DatabaseException, InterruptedException JavaDoc {
486
487         LockStats origStats = env.getLockStats(null);
488
489         readerThread = new JUnitThread("Reader") {
490             public void testBody()
491                 throws DatabaseException {
492                 Transaction readerTxn = env.beginTransaction(null, null);
493                 Cursor cursor = db.openCursor(readerTxn, null);
494                 spec.oper.init(PhantomRestartTest.this, cursor);
495                 spec.oper.doOper(operKeyParam);
496                 cursor.close();
497                 readerTxn.commitNoSync();
498             }
499         };
500
501         readerThread.start();
502         waitForBlock(origStats);
503     }
504
505     /**
506      * Waits for a new locker to block waiting for a lock.
507      */

508     private void waitForBlock(LockStats origStats)
509         throws DatabaseException, InterruptedException JavaDoc {
510
511         long startTime = System.currentTimeMillis();
512         while (true) {
513
514             /* Give some time to the thread. */
515             Thread.yield();
516             Thread.sleep(10);
517             if (System.currentTimeMillis() - startTime > MAX_INSERT_MILLIS) {
518                 fail("Timeout");
519             }
520
521             /* Wait for the operation to block. */
522             LockStats stats = env.getLockStats(null);
523             if (stats.getNWaiters() > origStats.getNWaiters()) {
524                 break;
525             }
526         }
527     }
528
529     /**
530      * Waits for the reader thread to finish.
531      */

532     private void waitForReadOper() {
533
534         try {
535             readerThread.finishTest();
536         } catch (Throwable JavaDoc e) {
537             e.printStackTrace();
538             fail(e.toString());
539         } finally {
540             readerThread = null;
541         }
542     }
543 }
544
Popular Tags