(no commit message)
[utils] / support / general / src / test / java / org / wamblee / concurrency / ReadWriteLockTest.java
1 /*
2  * Copyright 2005-2010 the original author or authors.
3  * 
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  * 
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  * 
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package org.wamblee.concurrency;
17
18 import junit.framework.TestCase;
19
20 /**
21  * Testing the read-write lock class. Note: in case of problems, test cases
22  * could hang.
23  * 
24  * @see ReadWriteLock
25  */
26 public class ReadWriteLockTest extends TestCase {
27     private static final int HALF_SECOND = 100;
28
29     private static final int ONE_SECOND = 200;
30
31     private static final int TWO_SECONDS = 400;
32     private ReadWriteLock lock;
33     private int nReaders;
34     private int nWriters;
35
36     /**
37      * Constructor for ReadWriteLockTest.
38      * 
39      * @param aName
40      */
41     public ReadWriteLockTest(String aName) {
42         super(aName);
43     }
44
45     private synchronized int getReaderCount() {
46         return nReaders;
47     }
48
49     private synchronized int getWriterCount() {
50         return nWriters;
51     }
52
53     synchronized void incrementReaderCount() {
54         nReaders++;
55     }
56
57     synchronized void incrementWriterCount() {
58         nWriters++;
59     }
60
61     synchronized void decrementReaderCount() {
62         nReaders--;
63     }
64
65     synchronized void decrementWriterCount() {
66         nWriters--;
67     }
68
69     /*
70      * @see TestCase#setUp()
71      */
72     protected void setUp() throws Exception {
73         super.setUp();
74         lock = new ReadWriteLock();
75     }
76
77     /*
78      * @see TestCase#tearDown()
79      */
80     protected void tearDown() throws Exception {
81         lock = null;
82         super.tearDown();
83     }
84
85     /**
86      * Acquire and release a read lock.
87      */
88     public void testRead() {
89         lock.acquireRead();
90         lock.releaseRead();
91     }
92
93     /**
94      * Acquire and release a write lock.
95      */
96     public void testWrite() {
97         lock.acquireWrite();
98         lock.releaseWrite();
99     }
100
101     /**
102      * Verify concurrent access by multiple readers is possible.
103      * 
104      * @throws InterruptedException
105      *             May not occur.
106      */
107     public void testMultipleReaders() throws InterruptedException {
108         Runnable runnable = new ReadLocker(lock, this, TWO_SECONDS);
109
110         Thread t1 = new Thread(runnable);
111         t1.start();
112
113         Thread t2 = new Thread(runnable);
114         t2.start();
115         Thread.sleep(ONE_SECOND);
116         assertTrue("Not enough readers!", getReaderCount() == 2);
117         t1.join();
118         t2.join();
119     }
120
121     /**
122      * Verify that only one writer at a time can acquire the write lock.
123      * 
124      * @throws InterruptedException
125      *             May not occur.
126      */
127     public void testSingleWriter() throws InterruptedException {
128         WriteLocker writer = new WriteLocker(lock, this, ONE_SECOND);
129         Thread t1 = new Thread(writer);
130         Thread t2 = new Thread(writer);
131
132         t1.start();
133         t2.start();
134         Thread.sleep(HALF_SECOND);
135         assertTrue("Wrong writer count: " + getWriterCount(),
136             getWriterCount() == 1);
137         t1.join();
138         t2.join();
139     }
140
141     /**
142      * Verify that multiple writers cannot acquire the write lock concurrently.
143      * 
144      * @throws InterruptedException
145      *             May not occur.
146      */
147     public void testMultipleWriters() throws InterruptedException {
148         WriteLocker writer1 = new WriteLocker(lock, this, HALF_SECOND +
149             ONE_SECOND);
150         WriteLocker writer2 = new WriteLocker(lock, this, ONE_SECOND);
151         Thread t1 = new Thread(writer1);
152         Thread t2 = new Thread(writer2);
153
154         t1.start();
155         Thread.sleep(HALF_SECOND);
156         assertTrue(getWriterCount() == 1);
157         t2.start();
158         Thread.sleep(HALF_SECOND);
159         assertTrue(getWriterCount() == 1); // first writer still
160
161         // has the lock.
162         Thread.sleep(ONE_SECOND);
163
164         // at t = 2, the second writer still must have
165         // a lock.
166         assertTrue(getWriterCount() == 1);
167         t1.join();
168         t2.join();
169     }
170
171     /**
172      * Verify that after the first reader acquires a lock, a subsequent writer
173      * can only acquire the lock after the reader has released it.
174      * 
175      * @throws InterruptedException
176      *             May not occur.
177      */
178     public void testReadWrite1() throws InterruptedException {
179         ReadLocker readLocker = new ReadLocker(lock, this, TWO_SECONDS);
180         Thread t1 = new Thread(readLocker);
181         WriteLocker writeLocker = new WriteLocker(lock, this, TWO_SECONDS);
182         Thread t2 = new Thread(writeLocker);
183
184         t1.start(); // acquire read lock
185         Thread.sleep(HALF_SECOND);
186         assertTrue(getReaderCount() == 1);
187         t2.start();
188         Thread.sleep(HALF_SECOND);
189
190         // 1 second underway, reader still holding the
191         // lock so write lock cannot be acquired.
192         assertTrue(getReaderCount() == 1);
193         assertTrue(getWriterCount() == 0);
194         Thread.sleep(ONE_SECOND + HALF_SECOND);
195
196         // 2.5 seconds underway, read lock released and
197         // write lock must be acquired.
198         assertTrue("Wrong no. of readers: " + getReaderCount(),
199             getReaderCount() == 0);
200         assertTrue(getWriterCount() == 1);
201         t1.join();
202         t2.join();
203     }
204
205     /**
206      * Verify that when multiple readers have acquired a read lock, the writer
207      * can only acquire the lock after all readers have released it.
208      * 
209      * @throws InterruptedException
210      *             May not occur.
211      */
212     public void testReadWrite2() throws InterruptedException {
213         ReadLocker readLocker1 = new ReadLocker(lock, this, TWO_SECONDS +
214             HALF_SECOND);
215         ReadLocker readLocker2 = new ReadLocker(lock, this, TWO_SECONDS +
216             HALF_SECOND);
217         Thread t1 = new Thread(readLocker1);
218         Thread t2 = new Thread(readLocker2);
219         WriteLocker writeLocker = new WriteLocker(lock, this, TWO_SECONDS);
220         Thread t3 = new Thread(writeLocker);
221
222         t1.start(); // acquire read lock [0, 2.5]
223         Thread.sleep(ONE_SECOND);
224         // t = 1
225         assertTrue(getReaderCount() == 1);
226         t2.start(); // acquire read lock [1, 3.5]
227         Thread.sleep(HALF_SECOND);
228         // t = 1.5
229         assertTrue(getReaderCount() == 2);
230         t3.start(); // write lock
231         Thread.sleep(HALF_SECOND);
232
233         // 2 seconds,
234         assertTrue(getReaderCount() == 2);
235         assertTrue(getWriterCount() == 0);
236         Thread.sleep(ONE_SECOND);
237
238         // 3 seconds underway, first read lock must
239         // have been released.
240         assertTrue(getReaderCount() == 1);
241         assertTrue(getWriterCount() == 0);
242         Thread.sleep(ONE_SECOND);
243
244         // 4 seconds underway, write lock must have
245         // been acquired.
246         assertTrue(getReaderCount() == 0);
247         assertTrue(getWriterCount() == 1);
248
249         t1.join();
250         t2.join();
251         t3.join();
252     }
253
254     /**
255      * Verify that after a writer acquires a lock, a subsequent reader can only
256      * acquire the lock after the writer has released it.
257      * 
258      * @throws InterruptedException
259      *             May not occur.
260      */
261     public void testReadWrite3() throws InterruptedException {
262         ReadLocker readLocker = new ReadLocker(lock, this, TWO_SECONDS);
263         Thread t1 = new Thread(readLocker);
264         WriteLocker writeLocker = new WriteLocker(lock, this, TWO_SECONDS);
265         Thread t2 = new Thread(writeLocker);
266
267         t2.start(); // acquire write lock
268         Thread.sleep(HALF_SECOND);
269         assertTrue(getWriterCount() == 1);
270         t1.start();
271         Thread.sleep(HALF_SECOND);
272
273         // 1 second underway, writer still holding the
274         // lock so read lock cannot be acquired.
275         assertTrue(getWriterCount() == 1);
276         assertTrue(getReaderCount() == 0);
277         Thread.sleep(ONE_SECOND + HALF_SECOND);
278
279         // 2.5 seconds underway, write lock released and
280         // read lock must be acquired.
281         assertTrue("Wrong no. of writers: " + getReaderCount(),
282             getWriterCount() == 0);
283         assertTrue(getReaderCount() == 1);
284         t1.join();
285         t2.join();
286     }
287
288     /*
289      * The following test cases are for testing whether or not the read write
290      * lock checks the locking correctly. Strictly speaking, these checks
291      * wouldn't be necessary because it involves the contract of the
292      * ReadWriteLock which must be obeyed by users of the ReadWriteLock.
293      * Nevertheless, this is tested anyway to be absolutely sure.
294      */
295
296     /**
297      * Acquire a read lock from one thread, release it from another. Verify that
298      * a RuntimeException is thrown.
299      * 
300      * @throws InterruptedException
301      *             May not occur.
302      */
303     public void testReleaseReadFromWrongThread() throws InterruptedException {
304         Thread t1 = null;
305
306         try {
307             t1 = new Thread(new Runnable() {
308                 public void run() {
309                     ReadWriteLockTest.this.lock.acquireRead();
310                 }
311             });
312             t1.start();
313             Thread.sleep(ONE_SECOND); // wait until thread is started
314             lock.releaseRead(); // release lock from wrong thread.
315         } catch (RuntimeException e) {
316             return; // ok
317         } finally {
318             t1.join();
319         }
320
321         fail();
322     }
323
324     /**
325      * Acquire a write lock from one thread, release it from another. Verify
326      * that a RuntimeException is thrown.
327      * 
328      * @throws InterruptedException
329      *             May not occur.
330      */
331     public void testReleaseWriteFromWrongThread() throws InterruptedException {
332         Thread t1 = null;
333
334         try {
335             t1 = new Thread(new Runnable() {
336                 public void run() {
337                     ReadWriteLockTest.this.lock.acquireWrite();
338                 }
339             });
340             t1.start();
341             Thread.sleep(ONE_SECOND); // wait until thread is started
342             lock.releaseWrite(); // release lock from wrong thread.
343         } catch (RuntimeException e) {
344             return; // ok
345         } finally {
346             t1.join();
347         }
348
349         fail();
350     }
351
352     /**
353      * Try to acquire a read lock multiple times. Verify that a RuntimeException
354      * is thrown.
355      */
356     public void testAcquireReadTwice() {
357         try {
358             lock.acquireRead();
359             lock.acquireRead();
360         } catch (RuntimeException e) {
361             // ok
362             return;
363         }
364
365         fail();
366     }
367
368     /**
369      * Try to acquire a write lock multiple times. Verify that a
370      * RuntimeException is thrown.
371      */
372     public void testAcquireWriteTwice() {
373         try {
374             lock.acquireWrite();
375             lock.acquireWrite();
376         } catch (RuntimeException e) {
377             return; // ok
378         }
379
380         fail();
381     }
382
383     /**
384      * Acquire the lock for reading and directly afterwards acquire it for
385      * writing. Verify that a RuntimeException is thrown.
386      */
387     public void testAcquireReadFollowedByWrite() {
388         try {
389             lock.acquireRead();
390             lock.acquireWrite();
391         } catch (RuntimeException e) {
392             return; // ok
393         }
394
395         fail();
396     }
397
398     /**
399      * Acquire the lock for writing and directly afterwards acquire it for
400      * reading. Verify that a RuntimeException is thrown.
401      */
402     public void testAcquireWriteFollowedByRead() {
403         try {
404             lock.acquireWrite();
405             lock.acquireRead();
406         } catch (RuntimeException e) {
407             return; // ok
408         }
409
410         fail();
411     }
412
413     /**
414      * Acquire a read lock and release it as a write lock. Verify that a
415      * RuntimeException is thrown.
416      */
417     public void testAcquireReadFollowedByReleaseaWrite() {
418         try {
419             lock.acquireRead();
420             lock.releaseWrite();
421         } catch (RuntimeException e) {
422             return; // ok
423         }
424
425         fail();
426     }
427
428     /**
429      * Acquire a write lock and release it as a read lock. Verify that a
430      * RuntimeException is thrown.
431      */
432     public void testAcquireWriteFollowedByReleaseRead() {
433         try {
434             lock.acquireWrite();
435             lock.releaseRead();
436         } catch (RuntimeException e) {
437             return; // ok
438         }
439
440         fail();
441     }
442 }
443
444 /**
445  * ReadLocker acquires a read lock and performs a callback when the lock as been
446  * acquired, sleeps for a designated amount of time, releases the read lock, and
447  * performs a callback after the lock has been released.
448  */
449 class ReadLocker implements Runnable {
450     private ReadWriteLock lock;
451     private ReadWriteLockTest lockTest;
452     private int sleepTime;
453
454     public ReadLocker(ReadWriteLock aLock, ReadWriteLockTest aLockTest,
455         int aSleepTime) {
456         lock = aLock;
457         lockTest = aLockTest;
458         sleepTime = aSleepTime;
459     }
460
461     public void run() {
462         lock.acquireRead();
463         lockTest.incrementReaderCount();
464
465         try {
466             Thread.sleep(sleepTime);
467         } catch (InterruptedException e) {
468             throw new RuntimeException("ReadLocker thread was interrupted." +
469                 Thread.currentThread());
470         }
471
472         lock.releaseRead();
473         lockTest.decrementReaderCount();
474     }
475 }
476
477 /**
478  * WriteLocker acquires a write lock and performs a callback when the lock as
479  * been acquired, sleeps for a designated amount of time, releases the write
480  * lock, and performs a callback after the lock has been released.
481  */
482 class WriteLocker implements Runnable {
483     private ReadWriteLock lock;
484     private ReadWriteLockTest lockTest;
485     private int sleepTime;
486
487     public WriteLocker(ReadWriteLock aLock, ReadWriteLockTest aLockTest,
488         int aSleepTime) {
489         lock = aLock;
490         lockTest = aLockTest;
491         sleepTime = aSleepTime;
492     }
493
494     public void run() {
495         lock.acquireWrite();
496         lockTest.incrementWriterCount();
497
498         try {
499             Thread.sleep(sleepTime);
500         } catch (InterruptedException e) {
501             throw new RuntimeException("WriteLocker thread was interrupted: " +
502                 Thread.currentThread());
503         }
504
505         lock.releaseWrite();
506         lockTest.decrementWriterCount();
507     }
508 }