001: /*BEGIN_COPYRIGHT_BLOCK
002: *
003: * Copyright (c) 2001-2007, JavaPLT group at Rice University (javaplt@rice.edu)
004: * All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions are met:
008: * * Redistributions of source code must retain the above copyright
009: * notice, this list of conditions and the following disclaimer.
010: * * Redistributions in binary form must reproduce the above copyright
011: * notice, this list of conditions and the following disclaimer in the
012: * documentation and/or other materials provided with the distribution.
013: * * Neither the names of DrJava, the JavaPLT group, Rice University, nor the
014: * names of its contributors may be used to endorse or promote products
015: * derived from this software without specific prior written permission.
016: *
017: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
018: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
019: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
020: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
021: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
022: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
023: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
024: * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
025: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
026: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
027: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
028: *
029: * This software is Open Source Initiative approved Open Source Software.
030: * Open Source Initative Approved is a trademark of the Open Source Initiative.
031: *
032: * This file is part of DrJava. Download the current version of this project
033: * from http://www.drjava.org/ or http://sourceforge.net/projects/drjava/
034: *
035: * END_COPYRIGHT_BLOCK*/
036:
037: package edu.rice.cs.util;
038:
039: import edu.rice.cs.drjava.DrJavaTestCase;
040:
041: /** Attempts to test the correctness of the ReaderWriterLock class, which allows multiple reader and writer threads to
042: * safely access a shared resource. (Multiple readers can be active at a time, but only one writer can be active,
043: * during which time no readers can be active.) This can be difficult to test because there is little control over
044: * how the threads are actually scheduled.
045: * @version $Id: ReaderWriterLockTest.java 4255 2007-08-28 19:17:37Z mgricken $
046: */
047: public class ReaderWriterLockTest extends DrJavaTestCase {
048:
049: protected ReaderWriterLock _lock;
050:
051: /** Creates a new lock for the tests. */
052: public void setUp() throws Exception {
053: super .setUp();
054: _lock = new ReaderWriterLock();
055: }
056:
057: // TO DO: Pull the next few lines out into a Semaphore class
058:
059: /** Number of notifications expected before we actually notify. */
060: private int _notifyCount = 0;
061:
062: /** Object to provide semaphore-like synchronization. */
063: private final Object _notifyObject = new Object();
064:
065: /** Notifies the _notifyObject (semaphore) when the _notifyCount reaches 0. (Decrements the count on each call.) */
066: private void _notify() {
067: synchronized (_notifyObject) {
068: _notifyCount--;
069: if (_notifyCount <= 0) {
070: _notifyObject.notify();
071: _notifyCount = 0;
072: }
073: }
074: }
075:
076: /** Tests that multiple readers can run without causing deadlock. We can't really impose any ordering on their output.
077: */
078: public void testMultipleReaders() throws InterruptedException {
079: final StringBuilder buf = new StringBuilder();
080:
081: // Create three threads
082: ReaderThread r1 = new PrinterReaderThread("r1 ", buf);
083: ReaderThread r2 = new PrinterReaderThread("r2 ", buf);
084: ReaderThread r3 = new PrinterReaderThread("r3 ", buf);
085:
086: // Init the count
087: _notifyCount = 3;
088:
089: // Start the readers
090: synchronized (_notifyObject) {
091: r1.start();
092: r2.start();
093: r3.start();
094: _notifyObject.wait();
095: }
096: // String output = buf.toString();
097: // System.out.println(output);
098: }
099:
100: /** Tests that multiple writers run in mutually exclusive intervals without causing deadlock. */
101: public void testMultipleWriters() throws InterruptedException {
102: final StringBuilder buf = new StringBuilder();
103:
104: // Create three threads
105: WriterThread w1 = new PrinterWriterThread("w1 ", buf);
106: WriterThread w2 = new PrinterWriterThread("w2 ", buf);
107: WriterThread w3 = new PrinterWriterThread("w3 ", buf);
108:
109: // Init the count
110: _notifyCount = 3;
111:
112: // Start the readers
113: synchronized (_notifyObject) {
114: w1.start();
115: w2.start();
116: w3.start();
117: _notifyObject.wait();
118: }
119: String output = buf.toString();
120: //System.out.println(output);
121:
122: // Writer output should never be interspersed.
123: assertTrue("w1 writes should happen in order", output
124: .indexOf("w1 w1 w1 ") != -1);
125: assertTrue("w2 writes should happen in order", output
126: .indexOf("w2 w2 w2 ") != -1);
127: assertTrue("w1 writes should happen in order", output
128: .indexOf("w3 w3 w3 ") != -1);
129: }
130:
131: /** Ensure that a single thread can perform multiple reads. */
132: public void testReaderMultipleReads() throws InterruptedException {
133: // Simulate a reader that performs multiple reads in one thread
134: _lock.startRead();
135: _lock.startRead();
136: _lock.endRead();
137: _lock.endRead();
138:
139: // Test that a reading thread can perform an additional read even if
140: // a writing thread is waiting.
141: _lock.startRead();
142: Thread w = new Thread() {
143: public void run() {
144: synchronized (_lock) {
145: _lock.notifyAll();
146: _lock.startWrite();
147: // Waits here and releases _lock...
148: _lock.endWrite();
149: }
150: }
151: };
152: synchronized (_lock) {
153: w.start();
154: _lock.wait();
155: }
156: _lock.startRead();
157: _lock.endRead();
158: _lock.endRead();
159: }
160:
161: /** Ensure that a reading thread cannot perform a write. */
162: public void testCannotWriteInARead() {
163: try {
164: _lock.startRead();
165: _lock.startWrite();
166: fail("Should have caused an IllegalStateException!");
167: } catch (IllegalStateException ise) {
168: // Good, that's what we want
169: }
170: }
171:
172: /** Ensure that a writing thread cannot perform an additional write. */
173: public void testCannotWriteInAWrite() {
174: try {
175: _lock.startWrite();
176: _lock.startWrite();
177: fail("Should have caused an IllegalStateException!");
178: } catch (IllegalStateException ise) {
179: // Good, that's what we want
180: }
181: }
182:
183: /** Ensure that a writing thread cannot perform a read. */
184: public void testCannotReadInAWrite() {
185: try {
186: _lock.startWrite();
187: _lock.startRead();
188: fail("Should have caused an IllegalStateException!");
189: } catch (IllegalStateException ise) {
190: // Good, that's what we want
191: }
192: }
193:
194: /**
195: * We would like to test the following schedule.
196: *
197: * <pre>
198: * W1 |***********|
199: * W2 |..........*****|
200: * R1 |..............********|
201: * R2 |............****|
202: * W3 |...................***|
203: * R3 |.....................****|
204: * R4 |................*******|
205: * R5 |***|
206: * </pre>
207: *
208: * Key: "." means waiting, "*" means running
209: *
210: * This is next to impossible to set up in Java. What we'd really
211: * like is a unit-testing framework that allows us to easily specify
212: * such a schedule in a test. (Conveniently, Corky Cartwright has
213: * applied for a Texas ATP grant to develop just such a framework.)
214: *
215: *
216: * So, instead, we'll just set up these threads, let them run, and
217: * enforce that no one interferes with output from a writer.
218: */
219: public void testMultipleReadersAndWriters()
220: throws InterruptedException {
221: final StringBuilder buf = new StringBuilder();
222:
223: // Create threads
224: WriterThread w1 = new PrinterWriterThread("w1 ", buf);
225: WriterThread w2 = new PrinterWriterThread("w2 ", buf);
226: WriterThread w3 = new PrinterWriterThread("w3 ", buf);
227:
228: ReaderThread r1 = new PrinterReaderThread("r1 ", buf);
229: ReaderThread r2 = new PrinterReaderThread("r2 ", buf);
230: ReaderThread r3 = new PrinterReaderThread("r3 ", buf);
231: ReaderThread r4 = new PrinterReaderThread("r4 ", buf);
232: ReaderThread r5 = new PrinterReaderThread("r5 ", buf);
233:
234: // Init the count
235: _notifyCount = 8;
236:
237: // Start the readers
238: synchronized (_notifyObject) {
239: w1.start();
240: w2.start();
241: r1.start();
242: r2.start();
243: w3.start();
244: r3.start();
245: r4.start();
246: r5.start();
247: _notifyObject.wait();
248: }
249: String output = buf.toString();
250: //System.out.println(output);
251:
252: // Writer output should never be interspersed.
253: assertTrue("w1 writes should happen in order", output
254: .indexOf("w1 w1 w1 ") != -1);
255: assertTrue("w2 writes should happen in order", output
256: .indexOf("w2 w2 w2 ") != -1);
257: assertTrue("w1 writes should happen in order", output
258: .indexOf("w3 w3 w3 ") != -1);
259: }
260:
261: /** A reader thread. */
262: public abstract class ReaderThread extends Thread {
263: public abstract void read() throws Throwable;
264:
265: public void run() {
266: _lock.startRead();
267: try {
268: read();
269: } catch (Throwable t) {
270: t.printStackTrace();
271: }
272: _lock.endRead();
273: }
274: }
275:
276: /** A writer thread. */
277: public abstract class WriterThread extends Thread {
278: public abstract void write() throws Throwable;
279:
280: public void run() {
281: _lock.startWrite();
282: try {
283: write();
284: } catch (Throwable t) {
285: t.printStackTrace();
286: }
287: _lock.endWrite();
288: }
289: }
290:
291: /** A ReaderThread which repeatedly prints to a buffer. */
292: public class PrinterReaderThread extends ReaderThread {
293: PrintCommand _command;
294:
295: public PrinterReaderThread(String msg, final StringBuilder buf) {
296: _command = new PrintCommand(msg, buf);
297: }
298:
299: public void read() {
300: _command.print();
301: }
302: }
303:
304: /** A WriterThread which repeatedly prints to a buffer. */
305: public class PrinterWriterThread extends WriterThread {
306: PrintCommand _command;
307:
308: public PrinterWriterThread(String msg, final StringBuilder buf) {
309: _command = new PrintCommand(msg, buf);
310: }
311:
312: public void write() {
313: _command.print();
314: }
315: }
316:
317: /** Command pattern class to print to a buffer. */
318: public class PrintCommand {
319: /** Number of times to print */
320: int _numIterations = 3;
321: /** Number of milliseconds to wait between iterations */
322: int _waitMillis = 5;
323: /** Buffer to print to */
324: final StringBuilder _buf;
325: /** Message to print */
326: final String _msg;
327:
328: /** Creates a new command to print to a buffer during a read or write. */
329: public PrintCommand(String msg, StringBuilder buf) {
330: _msg = msg;
331: _buf = buf;
332: }
333:
334: /** Prints the message to the buffer. */
335: public void print() {
336: for (int i = 0; i < _numIterations; i++) {
337: _buf.append(_msg);
338: try {
339: Thread.sleep(_waitMillis);
340: } catch (InterruptedException e) {
341: _buf.append(e);
342: }
343: }
344: _notify();
345: }
346: }
347: }
|