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 static org.junit.Assert.assertFalse; 009import static org.junit.Assert.assertTrue; 010import static org.junit.Assert.fail; 011 012import java.util.UUID; 013import java.util.concurrent.ExecutionException; 014import java.util.concurrent.ExecutorService; 015import java.util.concurrent.Executors; 016import java.util.concurrent.Phaser; 017 018import org.fcrepo.kernel.api.exception.ConcurrentUpdateException; 019import org.fcrepo.kernel.api.identifiers.FedoraId; 020import org.fcrepo.kernel.api.lock.ResourceLockManager; 021 022import org.junit.AfterClass; 023import org.junit.Before; 024import org.junit.BeforeClass; 025import org.junit.Test; 026 027/** 028 * @author pwinckles 029 */ 030public class InMemoryResourceLockManagerTest { 031 032 private ResourceLockManager lockManager; 033 034 private static ExecutorService executor; 035 036 private String txId1; 037 private String txId2; 038 private FedoraId resourceId; 039 040 @BeforeClass 041 public static void beforeClass() { 042 executor = Executors.newCachedThreadPool(); 043 } 044 045 @AfterClass 046 public static void afterClass() { 047 executor.shutdown(); 048 } 049 050 @Before 051 public void setup() { 052 lockManager = new InMemoryResourceLockManager(); 053 txId1 = UUID.randomUUID().toString(); 054 txId2 = UUID.randomUUID().toString(); 055 resourceId = randomResourceId(); 056 } 057 058 @Test 059 public void shouldLockResourceWhenNotAlreadyLockedExclusive() { 060 lockManager.acquireExclusive(txId1, resourceId); 061 } 062 063 @Test 064 public void shouldLockResourceWhenNotAlreadyLockedNonExclusive() { 065 lockManager.acquireNonExclusive(txId1, resourceId); 066 } 067 068 @Test 069 public void sameTxShouldBeAbleToReacquireLockItAlreadyHoldsExclusive() { 070 lockManager.acquireExclusive(txId1, resourceId); 071 lockManager.acquireExclusive(txId1, resourceId); 072 } 073 074 @Test 075 public void sameTxShouldBeAbleToReacquireLockItAlreadyHoldsNonExclusive() { 076 lockManager.acquireNonExclusive(txId1, resourceId); 077 lockManager.acquireNonExclusive(txId1, resourceId); 078 } 079 080 @Test 081 public void shouldFailToAcquireLockWhenHeldByAnotherTxExclusive() { 082 lockManager.acquireExclusive(txId1, resourceId); 083 assertLockException(() -> { 084 lockManager.acquireExclusive(txId2, resourceId); 085 }); 086 assertLockException(() -> { 087 lockManager.acquireNonExclusive(txId2, resourceId); 088 }); 089 } 090 091 @Test 092 public void shouldFailToAcquireLockWhenHeldByAnotherTxSecondExclusive() { 093 lockManager.acquireNonExclusive(txId1, resourceId); 094 assertLockException(() -> { 095 lockManager.acquireExclusive(txId2, resourceId); 096 }); 097 } 098 099 @Test 100 public void shouldSucceedToAcquireNonExclusiveLockWhenHeldByAnotherTxNonExclusive() { 101 lockManager.acquireNonExclusive(txId1, resourceId); 102 lockManager.acquireNonExclusive(txId2, resourceId); 103 } 104 105 @Test 106 public void shouldAcquireLockAfterReleasedByAnotherTx1() { 107 lockManager.acquireExclusive(txId1, resourceId); 108 lockManager.releaseAll(txId1); 109 lockManager.acquireExclusive(txId2, resourceId); 110 } 111 112 @Test 113 public void shouldAcquireLockAfterReleasedByAnotherTx2() { 114 lockManager.acquireExclusive(txId1, resourceId); 115 lockManager.releaseAll(txId1); 116 lockManager.acquireNonExclusive(txId2, resourceId); 117 } 118 119 @Test 120 public void shouldAcquireLockAfterReleasedByAnotherTx3() { 121 lockManager.acquireNonExclusive(txId1, resourceId); 122 lockManager.releaseAll(txId1); 123 lockManager.acquireExclusive(txId2, resourceId); 124 } 125 126 @Test 127 public void shouldAcquireLockAfterReleasedByAnotherTx4() { 128 lockManager.acquireNonExclusive(txId1, resourceId); 129 lockManager.releaseAll(txId1); 130 lockManager.acquireNonExclusive(txId2, resourceId); 131 } 132 133 @Test 134 public void concurrentRequestsFromSameTxShouldBothSucceedWhenLockAvailable() 135 throws ExecutionException, InterruptedException { 136 final var phaser = new Phaser(3); 137 138 final var future1 = executor.submit(() -> { 139 phaser.arriveAndAwaitAdvance(); 140 lockManager.acquireExclusive(txId1, resourceId); 141 return true; 142 }); 143 final var future2 = executor.submit(() -> { 144 phaser.arriveAndAwaitAdvance(); 145 lockManager.acquireExclusive(txId1, resourceId); 146 return true; 147 }); 148 149 phaser.arriveAndAwaitAdvance(); 150 151 assertTrue(future1.get()); 152 assertTrue(future2.get()); 153 } 154 155 @Test 156 public void concurrentExclusiveRequestsFromDifferentTxesOnlyOneShouldSucceed() 157 throws ExecutionException, InterruptedException { 158 final var phaser = new Phaser(3); 159 160 final var future1 = executor.submit(() -> { 161 phaser.arriveAndAwaitAdvance(); 162 try { 163 lockManager.acquireExclusive(txId1, resourceId); 164 return true; 165 } catch (final ConcurrentUpdateException e) { 166 return false; 167 } 168 }); 169 final var future2 = executor.submit(() -> { 170 phaser.arriveAndAwaitAdvance(); 171 try { 172 lockManager.acquireExclusive(txId2, resourceId); 173 return true; 174 } catch (final ConcurrentUpdateException e) { 175 return false; 176 } 177 }); 178 179 phaser.arriveAndAwaitAdvance(); 180 181 if (future1.get()) { 182 assertFalse("Only one tx should have acquired a lock", future2.get()); 183 } else { 184 assertTrue("Only one tx should have acquired a lock", future2.get()); 185 } 186 } 187 188 @Test 189 public void concurrentOneExclusiveRequestsFromDifferentTxesOnlyOneShouldSucceed() 190 throws ExecutionException, InterruptedException { 191 final var phaser = new Phaser(3); 192 193 final var future1 = executor.submit(() -> { 194 phaser.arriveAndAwaitAdvance(); 195 try { 196 lockManager.acquireExclusive(txId1, resourceId); 197 return true; 198 } catch (final ConcurrentUpdateException e) { 199 return false; 200 } 201 }); 202 final var future2 = executor.submit(() -> { 203 phaser.arriveAndAwaitAdvance(); 204 try { 205 lockManager.acquireNonExclusive(txId2, resourceId); 206 return true; 207 } catch (final ConcurrentUpdateException e) { 208 return false; 209 } 210 }); 211 212 phaser.arriveAndAwaitAdvance(); 213 214 if (future1.get()) { 215 assertFalse("Only one tx should have acquired a lock", future2.get()); 216 } else { 217 assertTrue("Only one tx should have acquired a lock", future2.get()); 218 } 219 } 220 221 @Test 222 public void concurrentNonexclusiveRequestsFromDifferentTxesBothShouldSucceed() 223 throws ExecutionException, InterruptedException { 224 final var phaser = new Phaser(3); 225 226 final var future1 = executor.submit(() -> { 227 phaser.arriveAndAwaitAdvance(); 228 try { 229 lockManager.acquireNonExclusive(txId1, resourceId); 230 return true; 231 } catch (final ConcurrentUpdateException e) { 232 return false; 233 } 234 }); 235 final var future2 = executor.submit(() -> { 236 phaser.arriveAndAwaitAdvance(); 237 try { 238 lockManager.acquireNonExclusive(txId2, resourceId); 239 return true; 240 } catch (final ConcurrentUpdateException e) { 241 return false; 242 } 243 }); 244 245 phaser.arriveAndAwaitAdvance(); 246 // Both should succeed. 247 assertTrue(future1.get()); 248 assertTrue(future2.get()); 249 } 250 251 @Test 252 public void releasingAlreadyReleasedLocksShouldDoNothing() { 253 lockManager.acquireExclusive(txId1, resourceId); 254 lockManager.releaseAll(txId1); 255 lockManager.releaseAll(txId1); 256 lockManager.acquireExclusive(txId2, resourceId); 257 } 258 259 private void assertLockException(final Runnable runnable) { 260 try { 261 runnable.run(); 262 fail("acquire should have thrown an exception"); 263 } catch (final ConcurrentUpdateException e) { 264 // expected exception 265 } 266 } 267 268 private FedoraId randomResourceId() { 269 return FedoraId.create(UUID.randomUUID().toString()); 270 } 271 272}