001/* 002 * Licensed to DuraSpace under one or more contributor license agreements. 003 * See the NOTICE file distributed with this work for additional information 004 * regarding copyright ownership. 005 * 006 * DuraSpace licenses this file to you under the Apache License, 007 * Version 2.0 (the "License"); you may not use this file except in 008 * compliance with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.fcrepo.kernel.impl.lock; 019 020import org.fcrepo.kernel.api.exception.ConcurrentUpdateException; 021import org.fcrepo.kernel.api.identifiers.FedoraId; 022import org.fcrepo.kernel.api.lock.ResourceLockManager; 023import org.junit.AfterClass; 024import org.junit.Before; 025import org.junit.BeforeClass; 026import org.junit.Test; 027 028import java.util.UUID; 029import java.util.concurrent.ExecutionException; 030import java.util.concurrent.ExecutorService; 031import java.util.concurrent.Executors; 032import java.util.concurrent.Phaser; 033 034import static org.junit.Assert.assertFalse; 035import static org.junit.Assert.assertTrue; 036import static org.junit.Assert.fail; 037 038/** 039 * @author pwinckles 040 */ 041public class InMemoryResourceLockManagerTest { 042 043 private ResourceLockManager lockManager; 044 045 private static ExecutorService executor; 046 047 private String txId1; 048 private String txId2; 049 private FedoraId resourceId; 050 051 @BeforeClass 052 public static void beforeClass() { 053 executor = Executors.newCachedThreadPool(); 054 } 055 056 @AfterClass 057 public static void afterClass() { 058 executor.shutdown(); 059 } 060 061 @Before 062 public void setup() { 063 lockManager = new InMemoryResourceLockManager(); 064 txId1 = UUID.randomUUID().toString(); 065 txId2 = UUID.randomUUID().toString(); 066 resourceId = randomResourceId(); 067 } 068 069 @Test 070 public void shouldLockResourceWhenNotAlreadyLocked() { 071 lockManager.acquire(txId1, resourceId); 072 } 073 074 @Test 075 public void sameTxShouldBeAbleToReacquireLockItAlreadyHolds() { 076 lockManager.acquire(txId1, resourceId); 077 lockManager.acquire(txId1, resourceId); 078 } 079 080 @Test 081 public void shouldFailToAcquireLockWhenHeldByAnotherTx() { 082 lockManager.acquire(txId1, resourceId); 083 assertLockException(() -> { 084 lockManager.acquire(txId2, resourceId); 085 }); 086 } 087 088 @Test 089 public void shouldAcquireLockAfterReleasedByAnotherTx() { 090 lockManager.acquire(txId1, resourceId); 091 lockManager.releaseAll(txId1); 092 lockManager.acquire(txId2, resourceId); 093 } 094 095 @Test 096 public void concurrentRequestsFromSameTxShouldBothSucceedWhenLockAvailable() 097 throws ExecutionException, InterruptedException { 098 final var phaser = new Phaser(3); 099 100 final var future1 = executor.submit(() -> { 101 phaser.arriveAndAwaitAdvance(); 102 lockManager.acquire(txId1, resourceId); 103 return true; 104 }); 105 final var future2 = executor.submit(() -> { 106 phaser.arriveAndAwaitAdvance(); 107 lockManager.acquire(txId1, resourceId); 108 return true; 109 }); 110 111 phaser.arriveAndAwaitAdvance(); 112 113 assertTrue(future1.get()); 114 assertTrue(future2.get()); 115 } 116 117 @Test 118 public void concurrentRequestsFromDifferentTxesOnlyOneShouldSucceed() 119 throws ExecutionException, InterruptedException { 120 final var phaser = new Phaser(3); 121 122 final var future1 = executor.submit(() -> { 123 phaser.arriveAndAwaitAdvance(); 124 try { 125 lockManager.acquire(txId1, resourceId); 126 return true; 127 } catch (ConcurrentUpdateException e) { 128 return false; 129 } 130 }); 131 final var future2 = executor.submit(() -> { 132 phaser.arriveAndAwaitAdvance(); 133 try { 134 lockManager.acquire(txId2, resourceId); 135 return true; 136 } catch (ConcurrentUpdateException e) { 137 return false; 138 } 139 }); 140 141 phaser.arriveAndAwaitAdvance(); 142 143 if (future1.get()) { 144 assertFalse("Only one tx should have acquired a lock", future2.get()); 145 } else { 146 assertTrue("Only one tx should have acquired a lock", future2.get()); 147 } 148 } 149 150 @Test 151 public void releasingAlreadyReleasedLocksShouldDoNothing() { 152 lockManager.acquire(txId1, resourceId); 153 lockManager.releaseAll(txId1); 154 lockManager.releaseAll(txId1); 155 lockManager.acquire(txId2, resourceId); 156 } 157 158 private void assertLockException(final Runnable runnable) { 159 try { 160 runnable.run(); 161 fail("acquire should have thrown an exception"); 162 } catch (ConcurrentUpdateException e) { 163 // expected exception 164 } 165 } 166 167 private FedoraId randomResourceId() { 168 return FedoraId.create(UUID.randomUUID().toString()); 169 } 170 171}