package org.infinispan.interceptors.distribution; import static org.mockito.Mockito.RETURNS_DEEP_STUBS; import static org.mockito.Mockito.any; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.AssertJUnit.assertEquals; import static org.testng.AssertJUnit.assertFalse; import static org.testng.AssertJUnit.assertNull; import static org.testng.AssertJUnit.assertTrue; import static org.testng.AssertJUnit.fail; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.infinispan.container.DataContainer; import org.infinispan.container.entries.ImmortalCacheEntry; import org.infinispan.container.entries.InternalCacheEntry; import org.infinispan.distribution.LocalizedCacheTopology; import org.infinispan.interceptors.locking.ClusteringDependentLogic; import org.infinispan.metadata.Metadata; import org.infinispan.statetransfer.StateTransferLock; import org.infinispan.test.AbstractInfinispanTest; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; /** * Test to ensure proper behavior of {@link L1WriteSynchronizer} * * @author wburns * @since 6.0 */ @Test(groups = "unit", testName = "interceptors.distribution.L1WriteSynchronizerTest") public class L1WriteSynchronizerTest extends AbstractInfinispanTest { private L1WriteSynchronizer sync; private DataContainer dc; private StateTransferLock stl; private ClusteringDependentLogic cdl; private final long l1Timeout = 1000; @BeforeMethod public void beforeMethod() { dc = mock(DataContainer.class); stl = mock(StateTransferLock.class); cdl = mock(ClusteringDependentLogic.class); when(cdl.getCacheTopology()).thenReturn(mock(LocalizedCacheTopology.class)); sync = new L1WriteSynchronizer(dc, l1Timeout, stl, cdl); } @Test public void testNullICEProvided() throws ExecutionException, InterruptedException { sync.runL1UpdateIfPossible(null); assertNull(sync.get()); } @Test public void testNullICEProvidedWait() throws ExecutionException, InterruptedException, TimeoutException { sync.runL1UpdateIfPossible(null); assertNull(sync.get(1, TimeUnit.SECONDS)); } @Test public void testGetReturnValueWait() throws InterruptedException, ExecutionException, TimeoutException { Object value = new Object(); InternalCacheEntry ice = new ImmortalCacheEntry(value, value); sync.runL1UpdateIfPossible(ice); assertEquals(ice, sync.get()); } @Test public void testGetReturnValueTimeWait() throws InterruptedException, ExecutionException, TimeoutException { Object value = new Object(); InternalCacheEntry ice = new ImmortalCacheEntry(value, value); sync.runL1UpdateIfPossible(ice); assertEquals(ice, sync.get(1, TimeUnit.SECONDS)); } @Test public void testExceptionWait() throws InterruptedException { Throwable t = mock(Throwable.class); sync.retrievalEncounteredException(t); try { sync.get(); } catch (ExecutionException e) { assertEquals(t, e.getCause()); } } @Test public void testExceptionTimeWait() throws InterruptedException, TimeoutException { Throwable t = mock(Throwable.class); sync.retrievalEncounteredException(t); try { sync.get(1, TimeUnit.SECONDS); fail("Should have thrown an execution exception"); } catch (ExecutionException e) { assertEquals(t, e.getCause()); } } @Test public void testSpawnedThreadBlockingValue() throws InterruptedException, ExecutionException, TimeoutException { Object value = new Object(); Future future = fork(() -> sync.get()); try { future.get(50, TimeUnit.MILLISECONDS); fail("Should have thrown a timeout exception"); } catch (TimeoutException e) { // This should time out exception } InternalCacheEntry ice = new ImmortalCacheEntry(value, value); sync.runL1UpdateIfPossible(ice); assertEquals(ice, future.get(1, TimeUnit.SECONDS)); } @Test public void testSpawnedThreadBlockingValueTimeWait() throws InterruptedException, ExecutionException, TimeoutException { Object value = new Object(); Future future = fork(() -> sync.get(5, TimeUnit.SECONDS)); // This should not return since we haven't signaled the sync yet try { future.get(50, TimeUnit.MILLISECONDS); fail("Should have thrown a timeout exception"); } catch (TimeoutException e) { // This should time out exception } InternalCacheEntry ice = new ImmortalCacheEntry(value, value); sync.runL1UpdateIfPossible(ice); assertEquals(ice, future.get(1, TimeUnit.SECONDS)); } @Test public void testSpawnedThreadBlockingNullValue() throws InterruptedException, ExecutionException, TimeoutException { Future future = fork(() -> sync.get()); try { future.get(50, TimeUnit.MILLISECONDS); fail("Should have thrown a timeout exception"); } catch (TimeoutException e) { // This should time out exception } sync.runL1UpdateIfPossible(null); assertNull(future.get(1, TimeUnit.SECONDS)); } @Test public void testSpawnedThreadBlockingNullValueTimeWait() throws InterruptedException, ExecutionException, TimeoutException { Future future = fork(() -> sync.get(5, TimeUnit.SECONDS)); // This should not return since we haven't signaled the sync yet try { future.get(50, TimeUnit.MILLISECONDS); fail("Should have thrown a timeout exception"); } catch (TimeoutException e) { // This should time out exception } sync.runL1UpdateIfPossible(null); assertNull(future.get(1, TimeUnit.SECONDS)); } @Test public void testSpawnedThreadBlockingException() throws InterruptedException, ExecutionException, TimeoutException { Throwable t = new Exception(); Future future = fork(() -> sync.get()); // This should not return since we haven't signaled the sync yet try { future.get(50, TimeUnit.MILLISECONDS); fail("Should have thrown a timeout exception"); } catch (TimeoutException e) { // This should time out exception } sync.retrievalEncounteredException(t); try { sync.get(1, TimeUnit.SECONDS); fail("Should have thrown an execution exception"); } catch (ExecutionException e) { assertEquals(t, e.getCause()); } } @Test public void testSpawnedThreadBlockingExceptionTimeWait() throws InterruptedException, ExecutionException, TimeoutException { Throwable t = new Exception(); Future future = fork(() -> sync.get(5, TimeUnit.SECONDS)); try { future.get(50, TimeUnit.MILLISECONDS); fail("Should have thrown a timeout exception"); } catch (TimeoutException e) { // This should time out exception } sync.retrievalEncounteredException(t); try { sync.get(1, TimeUnit.SECONDS); fail("Should have thrown an execution exception"); } catch (ExecutionException e) { assertEquals(t, e.getCause()); } } @Test public void testWriteCancelled() { assertTrue(sync.trySkipL1Update()); Object keyValue = new Object(); InternalCacheEntry ice = new ImmortalCacheEntry(keyValue, keyValue); sync.runL1UpdateIfPossible(ice); // The dc shouldn't have been updated verify(dc, never()).put(any(), any(), any(Metadata.class)); } @Test public void testWriteCannotCancel() throws InterruptedException, TimeoutException, BrokenBarrierException, ExecutionException { final CyclicBarrier barrier = new CyclicBarrier(2); // We use the topology lock as a sync point to know when the write is being attempted - note this is after // the synchronizer has been marked as a write occurring doAnswer(i -> { barrier.await(); return null; }).when(stl).acquireSharedTopologyLock(); Future<Void> future = fork(() -> { Object keyValue = new Object(); InternalCacheEntry ice = new ImmortalCacheEntry(keyValue, keyValue); sync.runL1UpdateIfPossible(ice); }, null); // wait for the thread to try updating barrier.await(1, TimeUnit.SECONDS); assertFalse(sync.trySkipL1Update()); future.get(1, TimeUnit.SECONDS); // The dc should have been updated verify(dc).put(any(), any(), any(Metadata.class)); } @Test public void testDCUpdatedHigherICELifespan() { verifyDCUpdate(Long.MAX_VALUE, false); } @Test public void testDCUpdatedLowerICELifespan() { verifyDCUpdate(50, true); } private void verifyDCUpdate(long iceLifespan, boolean shouldBeIceLifespan) { Object value = new Object(); Object key = new Object(); InternalCacheEntry ice = when(mock(InternalCacheEntry.class, RETURNS_DEEP_STUBS).getValue()).thenReturn(value).getMock(); when(ice.getKey()).thenReturn(key); when(ice.getLifespan()).thenReturn(iceLifespan); sync.runL1UpdateIfPossible(ice); verify(dc).put(eq(key), eq(value), any(Metadata.class)); Metadata.Builder verifier = verify(ice.getMetadata().builder()); verifier.lifespan(shouldBeIceLifespan ? iceLifespan : l1Timeout); verifier.maxIdle(-1); } }