Home | History | Annotate | Download | only in concurrent
      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 }
    579