KickJava   Java API By Example, From Geeks To Geeks.

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


1 /*-
2  * See the file LICENSE for redistribution information.
3  *
4  * Copyright (c) 2002,2006 Oracle. All rights reserved.
5  *
6  * $Id: AtomicPutTest.java,v 1.13 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.Cursor;
14 import com.sleepycat.je.Database;
15 import com.sleepycat.je.DatabaseConfig;
16 import com.sleepycat.je.DatabaseEntry;
17 import com.sleepycat.je.DatabaseException;
18 import com.sleepycat.je.DeadlockException;
19 import com.sleepycat.je.LockMode;
20 import com.sleepycat.je.OperationStatus;
21 import com.sleepycat.je.Transaction;
22 import com.sleepycat.je.junit.JUnitMethodThread;
23 import com.sleepycat.je.junit.JUnitThread;
24 import com.sleepycat.je.util.TestUtils;
25
26 /**
27  * Tests put() (overwrite) and putNoOverwrite() to check that they work
28  * atomically under concurrent access. These tests were added after put()
29  * and putNoOverwrite() were changed to work atomically. The history of the
30  * bugs is below.
31  *
32  * Old Algorithm
33  * -------------
34  * put(X, Y):
35  * if duplicates:
36  * return insertDup(X, Y)
37  * else:
38  * search(X)
39  * if SUCCESS:
40  * putCurrent(Y)
41  * return SUCCESS
42  * else:
43  * return insert(X,Y)
44  *
45  * putNoOverwrite(X, Y):
46  * search(X)
47  * if SUCCESS:
48  * return KEYEXIST
49  * else:
50  * if duplicates:
51  * insertDup(X, Y)
52  * else:
53  * insert(X, Y)
54  *
55  * Bug #1: In put with duplicates: Returned KEYEXIST when trying to overwrite
56  * a duplicate duplicate.
57  *
58  * Bug #2: In put without duplicates: Returned KEYEXIST if another thread
59  * inserted in between a search that returned NOTFOUND and the insert().
60  *
61  * Bug #3: In putNoOverwrite with duplicates: Added a duplicate if another
62  * thread inserted in between a search that returned NOTFOUND and the
63  * insert().
64  *
65  * New Algorithm
66  * -------------
67  * put(X, Y):
68  * if duplicates:
69  * insertDup(X, Y)
70  * else:
71  * insert(X, Y)
72  * if KEYEXIST:
73  * putCurrent(Y)
74  * return SUCCESS
75  *
76  * putNoOverwrite(X, Y):
77  * return insert(X, Y)
78  *
79  * Potential Bug #4: In put, if the lock is not acquired: Another thread may
80  * overwrite in between the insert and the putCurrent. But then putCurrent
81  * wouldn't be able to get a write lock, right? I can't think of how a
82  * problem could occur.
83
84  * Potential Bug #5: In putNoOverwrite, if we need to lock an existing record
85  * in order to return KEYEXIST, we may cause more deadlocks than is necessary.
86  *
87  * Low level operations
88  * --------------------
89  * insert(X, Y): insert if key is not present, else return KEYEXIST
90  * insertDup(X, Y): insert if key and data are not present, else return
91  * KEYEXIST
92  *
93  * Both insert methods obtain a lock on the existing record when returning
94  * KEYEXIST, to support overwrite.
95  */

96 public class AtomicPutTest extends TxnTestCase {
97
98     private static final int MAX_KEY = 400; //50000;
99

100     public static Test suite() {
101         return txnTestSuite(AtomicPutTest.class, null,
102                             //null);
103
new String JavaDoc[] {TxnTestCase.TXN_USER});
104     }
105
106     private int nextKey;
107     private Database db;
108
109     /**
110      * Closes databases, then calls the super.tearDown to close the env.
111      */

112     public void tearDown()
113         throws Exception JavaDoc {
114
115         if (db != null) {
116             try {
117                 db.close();
118             } catch (Exception JavaDoc e) {}
119             db = null;
120         }
121         super.tearDown();
122     }
123
124     /**
125      * Tests that put (overwrite), with no duplicates allowed, never causes a
126      * KEYEXIST status return.
127      */

128     public void testOverwriteNoDuplicates()
129     throws Throwable JavaDoc {
130
131         String JavaDoc method = "runOverwriteNoDuplicates";
132         JUnitMethodThread tester1 = new JUnitMethodThread(method + "-t1",
133                                                           method, this);
134         JUnitMethodThread tester2 = new JUnitMethodThread(method + "-t2",
135                                                           method, this);
136     db = openDb("foo", false);
137         tester1.start();
138         tester2.start();
139         finishTests(new JUnitThread[] { tester1, tester2 });
140         db.close();
141         db = null;
142     }
143
144     /**
145      * The old put() implementation first did a search, then inserted if
146      * NOTFOUND was returned by the search. This test tries to create the
147      * situation where one thread does a search on a key that returns NOTFOUND
148      * and another thread immediately afterwards inserts the same key, before
149      * the first thread has a chance to start the insert. Before the fix to
150      * make put() atomic, the first thread would have returned KEYEXIST from
151      * put(), and that should never happen.
152      */

153     public void runOverwriteNoDuplicates()
154         throws DatabaseException {
155
156         DatabaseEntry key = new DatabaseEntry();
157         DatabaseEntry data = new DatabaseEntry();
158         while (nextKey < MAX_KEY) {
159             /*
160              * Attempt to insert the same key as was just inserted by the other
161              * thread. We need to keep incrementing the key, since the error
162              * only occurs for a non-existing key value.
163              */

164             int val = nextKey++ / 2;
165             Transaction txn = txnBegin();
166             key.setData(TestUtils.getTestArray(val));
167             data.setData(TestUtils.getTestArray(val));
168         boolean commit = true;
169         try {
170         OperationStatus status = db.put(txn, key, data);
171         assertEquals("Key=" + val, OperationStatus.SUCCESS, status);
172         } catch (DeadlockException DE) {
173         commit = false;
174         }
175         if (commit) {
176         txnCommit(txn);
177         } else {
178         txnAbort(txn);
179         }
180         }
181     }
182
183     /**
184      * Tests that putNoOverwrite, with duplicates allowed, never inserts a
185      * duplicate.
186      */

187     public void testNoOverwriteWithDuplicates()
188     throws Throwable JavaDoc {
189
190         String JavaDoc method = "runNoOverwriteWithDuplicates";
191         JUnitMethodThread tester1 = new JUnitMethodThread(method + "-t1",
192                                                           method, this);
193         JUnitMethodThread tester2 = new JUnitMethodThread(method + "-t2",
194                                                           method, this);
195     db = openDb("foo", true);
196         tester1.start();
197         tester2.start();
198         finishTests(new JUnitThread[] { tester1, tester2 });
199         db.close();
200         db = null;
201     }
202
203     /**
204      * The old putNoOverwrite() inserted a duplicate after a search returned
205      * NOTFOUND, when duplicates were configured. This test tries to create
206      * the situation where the second thread inserting with a given key inserts
207      * a duplicate, which should never happen since we're using
208      * putNoOverwrite().
209      */

210     public void runNoOverwriteWithDuplicates()
211         throws DatabaseException {
212
213         DatabaseEntry key = new DatabaseEntry();
214         DatabaseEntry data = new DatabaseEntry();
215         while (nextKey < MAX_KEY) {
216             /*
217              * Attempt to insert a duplicate for the same key as was just
218              * inserted by the other thread. Each thread uses a different data
219              * value (modulo 2) so to avoid a duplicate-duplicate, which would
220              * not be inserted.
221              */

222             int val = nextKey++;
223             int keyVal = val / 2;
224             int dataVal = val % 2;
225             key.setData(TestUtils.getTestArray(keyVal));
226             data.setData(TestUtils.getTestArray(dataVal));
227             while (true) {
228                 Transaction txn = txnBegin();
229                 boolean commit = true;
230                 try {
231                     db.putNoOverwrite(txn, key, data);
232                 } catch (DeadlockException DE) {
233                     commit = false;
234                 }
235                 if (commit) {
236                     txnCommit(txn);
237                     break;
238                 } else {
239                     txnAbort(txn);
240                 }
241             }
242             Cursor cursor = db.openCursor(null, null);
243             try {
244                 OperationStatus status = cursor.getSearchKey(key, data,
245                                                              LockMode.DEFAULT);
246                 assertEquals(OperationStatus.SUCCESS, status);
247                 assertEquals(1, cursor.count());
248             } finally {
249                 cursor.close();
250             }
251         }
252     }
253
254     /**
255      * Opens a database.
256      */

257     private Database openDb(String JavaDoc name, boolean dups)
258         throws DatabaseException {
259
260         DatabaseConfig dbConfig = new DatabaseConfig();
261         dbConfig.setTransactional(isTransactional);
262         dbConfig.setAllowCreate(true);
263         dbConfig.setSortedDuplicates(dups);
264
265         Transaction txn = txnBegin();
266         try {
267             return env.openDatabase(txn, name, dbConfig);
268         } finally {
269             txnCommit(txn);
270         }
271     }
272
273     /**
274      * When one thread throws an assertion, the other threads need to be
275      * stopped, otherwise we will see side effects that mask the real problem.
276      */

277     private void finishTests(JUnitThread[] threads)
278     throws Throwable JavaDoc {
279
280         Throwable JavaDoc ex = null;
281         for (int i = 0; i < threads.length; i += 1) {
282             try {
283                 threads[i].finishTest();
284             } catch (Throwable JavaDoc e) {
285                 if (ex == null) {
286                     ex = e;
287                 }
288             }
289         }
290         if (ex != null) {
291             throw ex;
292         }
293     }
294 }
295
Popular Tags