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