001/* 002 * The contents of this file are subject to the license and copyright 003 * detailed in the LICENSE and NOTICE files at the root of the source 004 * tree. 005 */ 006package org.fcrepo.kernel.impl.lock; 007 008import org.fcrepo.kernel.api.exception.ConcurrentUpdateException; 009import org.fcrepo.kernel.api.identifiers.FedoraId; 010import org.fcrepo.kernel.api.lock.ResourceLockManager; 011import org.junit.AfterClass; 012import org.junit.Before; 013import org.junit.BeforeClass; 014import org.junit.Test; 015 016import java.util.UUID; 017import java.util.concurrent.ExecutionException; 018import java.util.concurrent.ExecutorService; 019import java.util.concurrent.Executors; 020import java.util.concurrent.Phaser; 021 022import static org.junit.Assert.assertFalse; 023import static org.junit.Assert.assertTrue; 024import static org.junit.Assert.fail; 025 026/** 027 * @author pwinckles 028 */ 029public class InMemoryResourceLockManagerTest { 030 031 private ResourceLockManager lockManager; 032 033 private static ExecutorService executor; 034 035 private String txId1; 036 private String txId2; 037 private FedoraId resourceId; 038 039 @BeforeClass 040 public static void beforeClass() { 041 executor = Executors.newCachedThreadPool(); 042 } 043 044 @AfterClass 045 public static void afterClass() { 046 executor.shutdown(); 047 } 048 049 @Before 050 public void setup() { 051 lockManager = new InMemoryResourceLockManager(); 052 txId1 = UUID.randomUUID().toString(); 053 txId2 = UUID.randomUUID().toString(); 054 resourceId = randomResourceId(); 055 } 056 057 @Test 058 public void shouldLockResourceWhenNotAlreadyLocked() { 059 lockManager.acquire(txId1, resourceId); 060 } 061 062 @Test 063 public void sameTxShouldBeAbleToReacquireLockItAlreadyHolds() { 064 lockManager.acquire(txId1, resourceId); 065 lockManager.acquire(txId1, resourceId); 066 } 067 068 @Test 069 public void shouldFailToAcquireLockWhenHeldByAnotherTx() { 070 lockManager.acquire(txId1, resourceId); 071 assertLockException(() -> { 072 lockManager.acquire(txId2, resourceId); 073 }); 074 } 075 076 @Test 077 public void shouldAcquireLockAfterReleasedByAnotherTx() { 078 lockManager.acquire(txId1, resourceId); 079 lockManager.releaseAll(txId1); 080 lockManager.acquire(txId2, resourceId); 081 } 082 083 @Test 084 public void concurrentRequestsFromSameTxShouldBothSucceedWhenLockAvailable() 085 throws ExecutionException, InterruptedException { 086 final var phaser = new Phaser(3); 087 088 final var future1 = executor.submit(() -> { 089 phaser.arriveAndAwaitAdvance(); 090 lockManager.acquire(txId1, resourceId); 091 return true; 092 }); 093 final var future2 = executor.submit(() -> { 094 phaser.arriveAndAwaitAdvance(); 095 lockManager.acquire(txId1, resourceId); 096 return true; 097 }); 098 099 phaser.arriveAndAwaitAdvance(); 100 101 assertTrue(future1.get()); 102 assertTrue(future2.get()); 103 } 104 105 @Test 106 public void concurrentRequestsFromDifferentTxesOnlyOneShouldSucceed() 107 throws ExecutionException, InterruptedException { 108 final var phaser = new Phaser(3); 109 110 final var future1 = executor.submit(() -> { 111 phaser.arriveAndAwaitAdvance(); 112 try { 113 lockManager.acquire(txId1, resourceId); 114 return true; 115 } catch (ConcurrentUpdateException e) { 116 return false; 117 } 118 }); 119 final var future2 = executor.submit(() -> { 120 phaser.arriveAndAwaitAdvance(); 121 try { 122 lockManager.acquire(txId2, resourceId); 123 return true; 124 } catch (ConcurrentUpdateException e) { 125 return false; 126 } 127 }); 128 129 phaser.arriveAndAwaitAdvance(); 130 131 if (future1.get()) { 132 assertFalse("Only one tx should have acquired a lock", future2.get()); 133 } else { 134 assertTrue("Only one tx should have acquired a lock", future2.get()); 135 } 136 } 137 138 @Test 139 public void releasingAlreadyReleasedLocksShouldDoNothing() { 140 lockManager.acquire(txId1, resourceId); 141 lockManager.releaseAll(txId1); 142 lockManager.releaseAll(txId1); 143 lockManager.acquire(txId2, resourceId); 144 } 145 146 private void assertLockException(final Runnable runnable) { 147 try { 148 runnable.run(); 149 fail("acquire should have thrown an exception"); 150 } catch (ConcurrentUpdateException e) { 151 // expected exception 152 } 153 } 154 155 private FedoraId randomResourceId() { 156 return FedoraId.create(UUID.randomUUID().toString()); 157 } 158 159}