View Javadoc
1   /*
2    * Copyright (C) 2011 The Guava 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  
17  package com.google.common.util.concurrent;
18  
19  import com.google.common.base.Joiner;
20  import com.google.common.util.concurrent.CycleDetectingLockFactory.Policies;
21  import com.google.common.util.concurrent.CycleDetectingLockFactory.Policy;
22  import com.google.common.util.concurrent.CycleDetectingLockFactory.PotentialDeadlockException;
23  
24  import junit.framework.TestCase;
25  
26  import java.util.concurrent.CountDownLatch;
27  import java.util.concurrent.TimeUnit;
28  import java.util.concurrent.locks.Lock;
29  import java.util.concurrent.locks.ReentrantLock;
30  import java.util.concurrent.locks.ReentrantReadWriteLock;
31  import java.util.regex.Matcher;
32  import java.util.regex.Pattern;
33  
34  /**
35   * Unittests for {@link CycleDetectingLockFactory}.
36   *
37   * @author Darick Tong
38   */
39  public class CycleDetectingLockFactoryTest extends TestCase {
40  
41    private ReentrantLock lockA;
42    private ReentrantLock lockB;
43    private ReentrantLock lockC;
44    private ReentrantReadWriteLock.ReadLock readLockA;
45    private ReentrantReadWriteLock.ReadLock readLockB;
46    private ReentrantReadWriteLock.ReadLock readLockC;
47    private ReentrantReadWriteLock.WriteLock writeLockA;
48    private ReentrantReadWriteLock.WriteLock writeLockB;
49    private ReentrantReadWriteLock.WriteLock writeLockC;
50    private ReentrantLock lock1;
51    private ReentrantLock lock2;
52    private ReentrantLock lock3;
53    private ReentrantLock lock01;
54    private ReentrantLock lock02;
55    private ReentrantLock lock03;
56  
57    @Override
58    protected void setUp() throws Exception {
59      super.setUp();
60      CycleDetectingLockFactory factory =
61          CycleDetectingLockFactory.newInstance(Policies.THROW);
62      lockA = factory.newReentrantLock("LockA");
63      lockB = factory.newReentrantLock("LockB");
64      lockC = factory.newReentrantLock("LockC");
65      ReentrantReadWriteLock readWriteLockA =
66          factory.newReentrantReadWriteLock("ReadWriteA");
67      ReentrantReadWriteLock readWriteLockB =
68          factory.newReentrantReadWriteLock("ReadWriteB");
69      ReentrantReadWriteLock readWriteLockC =
70          factory.newReentrantReadWriteLock("ReadWriteC");
71      readLockA = readWriteLockA.readLock();
72      readLockB = readWriteLockB.readLock();
73      readLockC = readWriteLockC.readLock();
74      writeLockA = readWriteLockA.writeLock();
75      writeLockB = readWriteLockB.writeLock();
76      writeLockC = readWriteLockC.writeLock();
77  
78      CycleDetectingLockFactory.WithExplicitOrdering<MyOrder> factory2 =
79          newInstanceWithExplicitOrdering(MyOrder.class, Policies.THROW);
80      lock1 = factory2.newReentrantLock(MyOrder.FIRST);
81      lock2 = factory2.newReentrantLock(MyOrder.SECOND);
82      lock3 = factory2.newReentrantLock(MyOrder.THIRD);
83  
84      CycleDetectingLockFactory.WithExplicitOrdering<OtherOrder> factory3 =
85          newInstanceWithExplicitOrdering(OtherOrder.class, Policies.THROW);
86      lock01 = factory3.newReentrantLock(OtherOrder.FIRST);
87      lock02 = factory3.newReentrantLock(OtherOrder.SECOND);
88      lock03 = factory3.newReentrantLock(OtherOrder.THIRD);
89    }
90  
91    // In the unittest, create each ordered factory with its own set of lock
92    // graph nodes (as opposed to using the static per-Enum map) to avoid
93    // conflicts across different test runs.
94    private <E extends Enum<E>> CycleDetectingLockFactory.WithExplicitOrdering<E>
95        newInstanceWithExplicitOrdering(Class<E> enumClass, Policy policy) {
96      return new CycleDetectingLockFactory.WithExplicitOrdering<E>(
97          policy, CycleDetectingLockFactory.createNodes(enumClass));
98    }
99  
100   public void testDeadlock_twoLocks() {
101     // Establish an acquisition order of lockA -> lockB.
102     lockA.lock();
103     lockB.lock();
104     lockA.unlock();
105     lockB.unlock();
106 
107     // The opposite order should fail (Policies.THROW).
108     PotentialDeadlockException firstException = null;
109     lockB.lock();
110     try {
111       lockA.lock();
112       fail("Expected PotentialDeadlockException");
113     } catch (PotentialDeadlockException expected) {
114       checkMessage(expected, "LockB -> LockA", "LockA -> LockB");
115       firstException = expected;
116     }
117 
118     // Second time should also fail, with a cached causal chain.
119     try {
120       lockA.lock();
121       fail("Expected PotentialDeadlockException");
122     } catch (PotentialDeadlockException expected) {
123       checkMessage(expected, "LockB -> LockA", "LockA -> LockB");
124       // The causal chain should be cached.
125       assertSame(firstException.getCause(), expected.getCause());
126     }
127 
128     // lockA should work after lockB is released.
129     lockB.unlock();
130     lockA.lock();
131   }
132 
133   // Tests transitive deadlock detection.
134   public void testDeadlock_threeLocks() {
135     // Establish an ordering from lockA -> lockB.
136     lockA.lock();
137     lockB.lock();
138     lockB.unlock();
139     lockA.unlock();
140 
141     // Establish an ordering from lockB -> lockC.
142     lockB.lock();
143     lockC.lock();
144     lockB.unlock();
145 
146     // lockC -> lockA should fail.
147     try {
148       lockA.lock();
149       fail("Expected PotentialDeadlockException");
150     } catch (PotentialDeadlockException expected) {
151       checkMessage(
152           expected, "LockC -> LockA", "LockB -> LockC", "LockA -> LockB");
153     }
154   }
155 
156   public void testReentrancy_noDeadlock() {
157     lockA.lock();
158     lockB.lock();
159     lockA.lock();  // Should not assert on lockB -> reentrant(lockA)
160   }
161 
162   public void testExplicitOrdering_noViolations() {
163     lock1.lock();
164     lock3.lock();
165     lock3.unlock();
166     lock2.lock();
167     lock3.lock();
168   }
169 
170   public void testExplicitOrdering_violations() {
171     lock3.lock();
172     try {
173       lock2.lock();
174       fail("Expected PotentialDeadlockException");
175     } catch (PotentialDeadlockException expected) {
176       checkMessage(expected, "MyOrder.THIRD -> MyOrder.SECOND");
177     }
178 
179     try {
180       lock1.lock();
181       fail("Expected PotentialDeadlockException");
182     } catch (PotentialDeadlockException expected) {
183       checkMessage(expected, "MyOrder.THIRD -> MyOrder.FIRST");
184     }
185 
186     lock3.unlock();
187     lock2.lock();
188 
189     try {
190       lock1.lock();
191       fail("Expected PotentialDeadlockException");
192     } catch (PotentialDeadlockException expected) {
193       checkMessage(expected, "MyOrder.SECOND -> MyOrder.FIRST");
194     }
195   }
196 
197   public void testDifferentOrderings_noViolations() {
198     lock3.lock();   // MyOrder, ordinal() == 3
199     lock01.lock();  // OtherOrder, ordinal() == 1
200   }
201 
202   public void testExplicitOrderings_generalCycleDetection() {
203     lock3.lock();   // MyOrder, ordinal() == 3
204     lock01.lock();  // OtherOrder, ordinal() == 1
205 
206     lock3.unlock();
207     try {
208       lock3.lock();
209       fail("Expected PotentialDeadlockException");
210     } catch (PotentialDeadlockException expected) {
211       checkMessage(
212           expected,
213           "OtherOrder.FIRST -> MyOrder.THIRD",
214           "MyOrder.THIRD -> OtherOrder.FIRST");
215     }
216 
217     lockA.lock();
218     lock01.unlock();
219     lockB.lock();
220     lockA.unlock();
221 
222     try {
223       lock01.lock();
224       fail("Expected PotentialDeadlockException");
225     } catch (PotentialDeadlockException expected) {
226       checkMessage(
227           expected,
228           "LockB -> OtherOrder.FIRST",
229           "LockA -> LockB",
230           "OtherOrder.FIRST -> LockA");
231     }
232   }
233 
234   public void testExplicitOrdering_cycleWithUnorderedLock() {
235     Lock myLock = CycleDetectingLockFactory.newInstance(Policies.THROW)
236         .newReentrantLock("MyLock");
237     lock03.lock();
238     myLock.lock();
239     lock03.unlock();
240 
241     try {
242       lock01.lock();
243       fail("Expected PotentialDeadlockException");
244     } catch (PotentialDeadlockException expected) {
245       checkMessage(
246           expected,
247           "MyLock -> OtherOrder.FIRST",
248           "OtherOrder.THIRD -> MyLock",
249           "OtherOrder.FIRST -> OtherOrder.THIRD");
250     }
251   }
252 
253   public void testExplicitOrdering_reentrantAcquisition() {
254     CycleDetectingLockFactory.WithExplicitOrdering<OtherOrder> factory =
255         newInstanceWithExplicitOrdering(OtherOrder.class, Policies.THROW);
256     Lock lockA = factory.newReentrantReadWriteLock(OtherOrder.FIRST).readLock();
257     Lock lockB = factory.newReentrantLock(OtherOrder.SECOND);
258 
259     lockA.lock();
260     lockA.lock();
261     lockB.lock();
262     lockB.lock();
263     lockA.unlock();
264     lockA.unlock();
265     lockB.unlock();
266     lockB.unlock();
267   }
268 
269   public void testExplicitOrdering_acquiringMultipleLocksWithSameRank() {
270     CycleDetectingLockFactory.WithExplicitOrdering<OtherOrder> factory =
271         newInstanceWithExplicitOrdering(OtherOrder.class, Policies.THROW);
272     Lock lockA = factory.newReentrantLock(OtherOrder.FIRST);
273     Lock lockB = factory.newReentrantReadWriteLock(OtherOrder.FIRST).readLock();
274 
275     lockA.lock();
276     try {
277       lockB.lock();
278       fail("Expected IllegalStateException");
279     } catch (IllegalStateException expected) {
280     }
281 
282     lockA.unlock();
283     lockB.lock();
284   }
285 
286   public void testReadLock_deadlock() {
287     readLockA.lock();  // Establish an ordering from readLockA -> lockB.
288     lockB.lock();
289     lockB.unlock();
290     readLockA.unlock();
291 
292     lockB.lock();
293     try {
294       readLockA.lock();
295       fail("Expected PotentialDeadlockException");
296     } catch (PotentialDeadlockException expected) {
297       checkMessage(expected, "LockB -> ReadWriteA", "ReadWriteA -> LockB");
298     }
299   }
300 
301   public void testReadLock_transitive() {
302     readLockA.lock();  // Establish an ordering from readLockA -> lockB.
303     lockB.lock();
304     lockB.unlock();
305     readLockA.unlock();
306 
307     // Establish an ordering from lockB -> readLockC.
308     lockB.lock();
309     readLockC.lock();
310     lockB.unlock();
311     readLockC.unlock();
312 
313     // readLockC -> readLockA
314     readLockC.lock();
315     try {
316       readLockA.lock();
317       fail("Expected PotentialDeadlockException");
318     } catch (PotentialDeadlockException expected) {
319       checkMessage(
320           expected,
321           "ReadWriteC -> ReadWriteA",
322           "LockB -> ReadWriteC",
323           "ReadWriteA -> LockB");
324     }
325   }
326 
327   public void testWriteLock_threeLockDeadLock() {
328     // Establish an ordering from writeLockA -> writeLockB.
329     writeLockA.lock();
330     writeLockB.lock();
331     writeLockB.unlock();
332     writeLockA.unlock();
333 
334     // Establish an ordering from writeLockB -> writeLockC.
335     writeLockB.lock();
336     writeLockC.lock();
337     writeLockB.unlock();
338 
339     // writeLockC -> writeLockA should fail.
340     try {
341       writeLockA.lock();
342       fail("Expected PotentialDeadlockException");
343     } catch (PotentialDeadlockException expected) {
344       checkMessage(
345           expected,
346           "ReadWriteC -> ReadWriteA",
347           "ReadWriteB -> ReadWriteC",
348           "ReadWriteA -> ReadWriteB");
349     }
350   }
351 
352   public void testWriteToReadLockDowngrading() {
353     writeLockA.lock();  // writeLockA downgrades to readLockA
354     readLockA.lock();
355     writeLockA.unlock();
356 
357     lockB.lock();  // readLockA -> lockB
358     readLockA.unlock();
359 
360     // lockB -> writeLockA should fail
361     try {
362       writeLockA.lock();
363       fail("Expected PotentialDeadlockException");
364     } catch (PotentialDeadlockException expected) {
365       checkMessage(
366           expected, "LockB -> ReadWriteA", "ReadWriteA -> LockB");
367     }
368   }
369 
370   public void testReadWriteLockDeadlock() {
371     writeLockA.lock();  // Establish an ordering from writeLockA -> lockB
372     lockB.lock();
373     writeLockA.unlock();
374     lockB.unlock();
375 
376     // lockB -> readLockA should fail.
377     lockB.lock();
378     try {
379       readLockA.lock();
380       fail("Expected PotentialDeadlockException");
381     } catch (PotentialDeadlockException expected) {
382       checkMessage(
383           expected, "LockB -> ReadWriteA", "ReadWriteA -> LockB");
384     }
385   }
386 
387   public void testReadWriteLockDeadlock_transitive() {
388     readLockA.lock();  // Establish an ordering from readLockA -> lockB
389     lockB.lock();
390     readLockA.unlock();
391     lockB.unlock();
392 
393     // Establish an ordering from lockB -> lockC
394     lockB.lock();
395     lockC.lock();
396     lockB.unlock();
397     lockC.unlock();
398 
399     // lockC -> writeLockA should fail.
400     lockC.lock();
401     try {
402       writeLockA.lock();
403       fail("Expected PotentialDeadlockException");
404     } catch (PotentialDeadlockException expected) {
405       checkMessage(
406           expected,
407           "LockC -> ReadWriteA",
408           "LockB -> LockC",
409           "ReadWriteA -> LockB");
410     }
411   }
412 
413   public void testReadWriteLockDeadlock_treatedEquivalently() {
414     readLockA.lock();  // readLockA -> writeLockB
415     writeLockB.lock();
416     readLockA.unlock();
417     writeLockB.unlock();
418 
419     // readLockB -> writeLockA should fail.
420     readLockB.lock();
421     try {
422       writeLockA.lock();
423       fail("Expected PotentialDeadlockException");
424     } catch (PotentialDeadlockException expected) {
425       checkMessage(
426           expected, "ReadWriteB -> ReadWriteA", "ReadWriteA -> ReadWriteB");
427     }
428   }
429 
430   public void testDifferentLockFactories() {
431     CycleDetectingLockFactory otherFactory =
432         CycleDetectingLockFactory.newInstance(Policies.WARN);
433     ReentrantLock lockD = otherFactory.newReentrantLock("LockD");
434 
435     // lockA -> lockD
436     lockA.lock();
437     lockD.lock();
438     lockA.unlock();
439     lockD.unlock();
440 
441     // lockD -> lockA should fail even though lockD is from a different factory.
442     lockD.lock();
443     try {
444       lockA.lock();
445       fail("Expected PotentialDeadlockException");
446     } catch (PotentialDeadlockException expected) {
447       checkMessage(expected, "LockD -> LockA", "LockA -> LockD");
448     }
449   }
450 
451   public void testDifferentLockFactories_policyExecution() {
452     CycleDetectingLockFactory otherFactory =
453         CycleDetectingLockFactory.newInstance(Policies.WARN);
454     ReentrantLock lockD = otherFactory.newReentrantLock("LockD");
455 
456     // lockD -> lockA
457     lockD.lock();
458     lockA.lock();
459     lockA.unlock();
460     lockD.unlock();
461 
462     // lockA -> lockD should warn but otherwise succeed because lockD was
463     // created by a factory with the WARN policy.
464     lockA.lock();
465     lockD.lock();
466   }
467 
468   public void testReentrantLock_tryLock() throws Exception {
469     LockingThread thread = new LockingThread(lockA);
470     thread.start();
471 
472     thread.waitUntilHoldingLock();
473     assertFalse(lockA.tryLock());
474 
475     thread.releaseLockAndFinish();
476     assertTrue(lockA.tryLock());
477   }
478 
479   public void testReentrantWriteLock_tryLock() throws Exception {
480     LockingThread thread = new LockingThread(writeLockA);
481     thread.start();
482 
483     thread.waitUntilHoldingLock();
484     assertFalse(writeLockA.tryLock());
485     assertFalse(readLockA.tryLock());
486 
487     thread.releaseLockAndFinish();
488     assertTrue(writeLockA.tryLock());
489     assertTrue(readLockA.tryLock());
490   }
491 
492   public void testReentrantReadLock_tryLock() throws Exception {
493     LockingThread thread = new LockingThread(readLockA);
494     thread.start();
495 
496     thread.waitUntilHoldingLock();
497     assertFalse(writeLockA.tryLock());
498     assertTrue(readLockA.tryLock());
499     readLockA.unlock();
500 
501     thread.releaseLockAndFinish();
502     assertTrue(writeLockA.tryLock());
503     assertTrue(readLockA.tryLock());
504   }
505 
506   private static class LockingThread extends Thread {
507     final CountDownLatch locked = new CountDownLatch(1);
508     final CountDownLatch finishLatch = new CountDownLatch(1);
509     final Lock lock;
510 
511     LockingThread(Lock lock) {
512       this.lock = lock;
513     }
514 
515     @Override
516     public void run() {
517       lock.lock();
518       try {
519         locked.countDown();
520         finishLatch.await(1, TimeUnit.MINUTES);
521       } catch (InterruptedException e) {
522         fail(e.toString());
523       } finally {
524         lock.unlock();
525       }
526     }
527 
528     void waitUntilHoldingLock() throws InterruptedException {
529       locked.await(1, TimeUnit.MINUTES);
530     }
531 
532     void releaseLockAndFinish() throws InterruptedException {
533       finishLatch.countDown();
534       this.join(10000);
535       assertFalse(this.isAlive());
536     }
537   }
538 
539   public void testReentrantReadWriteLock_implDoesNotExposeShadowedLocks() {
540     assertEquals(
541         "Unexpected number of public methods in ReentrantReadWriteLock. " +
542         "The correctness of CycleDetectingReentrantReadWriteLock depends on " +
543         "the fact that the shadowed ReadLock and WriteLock are never used or " +
544         "exposed by the superclass implementation. If the implementation has " +
545         "changed, the code must be re-inspected to ensure that the " +
546         "assumption is still valid.",
547         24, ReentrantReadWriteLock.class.getMethods().length);
548   }
549 
550   private enum MyOrder {
551     FIRST, SECOND, THIRD;
552   }
553 
554   private enum OtherOrder {
555     FIRST, SECOND, THIRD;
556   }
557 
558   // Given a sequence of lock acquisition descriptions
559   // (e.g. "LockA -> LockB", "LockB -> LockC", ...)
560   // Checks that the exception.getMessage() matches a regex of the form:
561   // "LockA -> LockB \b.*\b LockB -> LockC \b.*\b LockC -> LockA"
562   private void checkMessage(
563       IllegalStateException exception, String... expectedLockCycle) {
564     String regex = Joiner.on("\\b.*\\b").join(expectedLockCycle);
565     assertContainsRegex(regex, exception.getMessage());
566   }
567 
568   // TODO(cpovirk): consider adding support for regex to Truth
569   private static void assertContainsRegex(String expectedRegex, String actual) {
570     Pattern pattern = Pattern.compile(expectedRegex);
571     Matcher matcher = pattern.matcher(actual);
572     if (!matcher.find()) {
573       String actualDesc = (actual == null) ? "null" : ('<' + actual + '>');
574       fail("expected to contain regex:<" + expectedRegex + "> but was:"
575           + actualDesc);
576     }
577   }
578 }