@Test public void testHashInvalidationListenerWithAppend() throws Exception { SimpleClusterTierClientEntity clientEntity1 = createClientEntity("testHashInvalidationListenerWithAppend", true); SimpleClusterTierClientEntity clientEntity2 = createClientEntity("testHashInvalidationListenerWithAppend", false); final AtomicReference<Long> invalidatedHash = new AtomicReference<>(); StrongServerStoreProxy serverStoreProxy1 = new StrongServerStoreProxy("testHashInvalidationListenerWithAppend", clientEntity1, mock(ServerCallback.class)); StrongServerStoreProxy serverStoreProxy2 = new StrongServerStoreProxy("testHashInvalidationListenerWithAppend", clientEntity2, new ServerCallback() { @Override public void onInvalidateHash(long hash) { invalidatedHash.set(hash); } @Override public void onInvalidateAll() { throw new AssertionError("Should not be called"); } @Override public Chain compact(Chain chain) { throw new AssertionError(); } }); serverStoreProxy1.append(1L, createPayload(1L)); assertThat(invalidatedHash.get(), is(1L)); }
private <T> T performWaitingForHashInvalidation(long key, Callable<T> c, Duration timeout) throws TimeoutException { LongSupplier nanosRemaining = nanosStartingFromNow(timeout); CountDownLatch latch = new CountDownLatch(1); while (true) { if (!entity.isConnected()) { throw new IllegalStateException("Cluster tier manager disconnected"); } CountDownLatch countDownLatch = hashInvalidationsInProgress.putIfAbsent(key, latch); if (countDownLatch == null) { break; } awaitOnLatch(countDownLatch, nanosRemaining); } try { T result = c.call(); LOGGER.debug("CLIENT: Waiting for invalidations on key {}", key); awaitOnLatch(latch, nanosRemaining); LOGGER.debug("CLIENT: key {} invalidated on all clients, unblocking call", key); return result; } catch (Exception ex) { hashInvalidationsInProgress.remove(key); latch.countDown(); if (ex instanceof TimeoutException) { throw (TimeoutException)ex; } throw new RuntimeException(ex); } }
private void allInvalidationDoneResponseListener(EhcacheEntityResponse.AllInvalidationDone response) { LOGGER.debug("CLIENT: on cache {}, server notified that clients invalidated all", getCacheId()); CountDownLatch countDownLatch = invalidateAllLatch.getAndSet(null); if (countDownLatch != null) { LOGGER.debug("CLIENT: on cache {}, count down", getCacheId()); countDownLatch.countDown(); } }
@Test public void testAllInvalidationListener() throws Exception { SimpleClusterTierClientEntity clientEntity1 = createClientEntity("testAllInvalidationListener", true); SimpleClusterTierClientEntity clientEntity2 = createClientEntity("testAllInvalidationListener", false); final AtomicBoolean invalidatedAll = new AtomicBoolean(); StrongServerStoreProxy serverStoreProxy1 = new StrongServerStoreProxy("testAllInvalidationListener", clientEntity1, mock(ServerCallback.class)); StrongServerStoreProxy serverStoreProxy2 = new StrongServerStoreProxy("testAllInvalidationListener", clientEntity2, new ServerCallback() { @Override public void onInvalidateHash(long hash) { throw new AssertionError("Should not be called"); } @Override public void onInvalidateAll() { invalidatedAll.set(true); } @Override public Chain compact(Chain chain) { throw new AssertionError(); } }); serverStoreProxy1.clear(); assertThat(invalidatedAll.get(), is(true)); }
@Test public void testHashInvalidationListenerWithGetAndAppend() throws Exception { SimpleClusterTierClientEntity clientEntity1 = createClientEntity("testHashInvalidationListenerWithGetAndAppend", true); SimpleClusterTierClientEntity clientEntity2 = createClientEntity("testHashInvalidationListenerWithGetAndAppend", false); final AtomicReference<Long> invalidatedHash = new AtomicReference<>(); StrongServerStoreProxy serverStoreProxy1 = new StrongServerStoreProxy("testHashInvalidationListenerWithGetAndAppend", clientEntity1, mock(ServerCallback.class)); StrongServerStoreProxy serverStoreProxy2 = new StrongServerStoreProxy("testHashInvalidationListenerWithGetAndAppend", clientEntity2, new ServerCallback() { @Override public void onInvalidateHash(long hash) { invalidatedHash.set(hash); } @Override public void onInvalidateAll() { throw new AssertionError("Should not be called"); } @Override public Chain compact(Chain chain) { throw new AssertionError(); } }); serverStoreProxy1.getAndAppend(1L, createPayload(1L)); assertThat(invalidatedHash.get(), is(1L)); }
final List<Long> store2InvalidatedHashes = new CopyOnWriteArrayList<>(); StrongServerStoreProxy serverStoreProxy1 = new StrongServerStoreProxy("testServerSideEvictionFiresInvalidations", clientEntity1, new ServerCallback() { @Override public void onInvalidateHash(long hash) { StrongServerStoreProxy serverStoreProxy2 = new StrongServerStoreProxy("testServerSideEvictionFiresInvalidations", clientEntity2, new ServerCallback() { @Override public void onInvalidateHash(long hash) { serverStoreProxy1.append(i, createPayload(i, 512 * 1024)); int entryCount = 0; for (int i = 0; i < ITERATIONS; i++) { Chain elements1 = serverStoreProxy1.get(i); Chain elements2 = serverStoreProxy2.get(i); assertThat(chainsEqual(elements1, elements2), is(true)); if (!elements1.isEmpty()) {
switch (configuredConsistency) { case STRONG: serverStoreProxy = new StrongServerStoreProxy(cacheId, storeClientEntity, invalidation); break; case EVENTUAL:
@Override public void append(final long key, final ByteBuffer payLoad) throws TimeoutException { performWaitingForHashInvalidation(key, () -> { delegate.append(key, payLoad); return null; }, entity.getTimeouts().getWriteOperationTimeout()); }
@Override public void clear() throws TimeoutException { performWaitingForAllInvalidation(() -> { delegate.clear(); return null; }, entity.getTimeouts().getWriteOperationTimeout()); } }
final CountDownLatch latch = new CountDownLatch(2); StrongServerStoreProxy serverStoreProxy1 = new StrongServerStoreProxy("testConcurrentAllInvalidationListener", clientEntity1, mock(ServerCallback.class)); StrongServerStoreProxy serverStoreProxy2 = new StrongServerStoreProxy("testConcurrentAllInvalidationListener", clientEntity2, new ServerCallback() { @Override public void onInvalidateHash(long hash) { try { executor.submit(() -> { serverStoreProxy1.clear(); return null; }); executor.submit(() -> { serverStoreProxy1.clear(); return null; });
switch (configuredConsistency) { case STRONG: serverStoreProxy = new StrongServerStoreProxy(cacheId, storeClientEntity, invalidation); break; case EVENTUAL:
@Override public Chain getAndAppend(final long key, final ByteBuffer payLoad) throws TimeoutException { return performWaitingForHashInvalidation(key, () -> delegate.getAndAppend(key, payLoad), entity.getTimeouts().getWriteOperationTimeout()); }
@Override public void clear() throws TimeoutException { performWaitingForAllInvalidation(() -> { delegate.clear(); return null; }, entity.getTimeouts().getWriteOperationTimeout()); } }
StrongServerStoreProxy serverStoreProxy1 = new StrongServerStoreProxy("testConcurrentHashInvalidationListenerWithAppend", clientEntity1, mock(ServerCallback.class)); StrongServerStoreProxy serverStoreProxy2 = new StrongServerStoreProxy("testConcurrentHashInvalidationListenerWithAppend", clientEntity2, new ServerCallback() { @Override public void onInvalidateHash(long hash) { try { executor.submit(() -> { serverStoreProxy1.append(1L, createPayload(1L)); return null; }); executor.submit(() -> { serverStoreProxy1.append(1L, createPayload(1L)); return null; });
SimpleClusterTierClientEntity clientEntity2 = createClientEntity("testClearInvalidationUnblockedByDisconnection", false); StrongServerStoreProxy serverStoreProxy1 = new StrongServerStoreProxy("testClearInvalidationUnblockedByDisconnection", clientEntity1, mock(ServerCallback.class)); StrongServerStoreProxy serverStoreProxy2 = new StrongServerStoreProxy("testClearInvalidationUnblockedByDisconnection", clientEntity2, new ServerCallback() { @Override public void onInvalidateHash(long hash) { serverStoreProxy1.clear(); fail("expected RuntimeException"); } catch (RuntimeException re) {
private void hashInvalidationDoneResponseListener(EhcacheEntityResponse.HashInvalidationDone response) { long key = response.getKey(); LOGGER.debug("CLIENT: on cache {}, server notified that clients invalidated hash {}", getCacheId(), key); CountDownLatch countDownLatch = hashInvalidationsInProgress.remove(key); if (countDownLatch != null) { countDownLatch.countDown(); } }
@Override public void append(final long key, final ByteBuffer payLoad) throws TimeoutException { performWaitingForHashInvalidation(key, () -> { delegate.append(key, payLoad); return null; }, entity.getTimeouts().getWriteOperationTimeout()); }
private <T> T performWaitingForAllInvalidation(Callable<T> c, Duration timeout) throws TimeoutException { LongSupplier nanosRemaining = nanosStartingFromNow(timeout); CountDownLatch newLatch = new CountDownLatch(1); while (true) { if (!entity.isConnected()) { throw new IllegalStateException("Cluster tier manager disconnected"); } if (invalidateAllLatch.compareAndSet(null, newLatch)) { break; } else { CountDownLatch existingLatch = invalidateAllLatch.get(); if (existingLatch != null) { awaitOnLatch(existingLatch, nanosRemaining); } } } try { T result = c.call(); awaitOnLatch(newLatch, nanosRemaining); LOGGER.debug("CLIENT: all invalidated on all clients, unblocking call"); return result; } catch (Exception ex) { invalidateAllLatch.set(null); newLatch.countDown(); if (ex instanceof TimeoutException) { throw (TimeoutException)ex; } throw new RuntimeException(ex); } }
@Test public void testAppendInvalidationUnblockedByDisconnection() throws Exception { SimpleClusterTierClientEntity clientEntity1 = createClientEntity("testAppendInvalidationUnblockedByDisconnection", true); SimpleClusterTierClientEntity clientEntity2 = createClientEntity("testAppendInvalidationUnblockedByDisconnection", false); StrongServerStoreProxy serverStoreProxy1 = new StrongServerStoreProxy("testAppendInvalidationUnblockedByDisconnection", clientEntity1, mock(ServerCallback.class)); StrongServerStoreProxy serverStoreProxy2 = new StrongServerStoreProxy("testAppendInvalidationUnblockedByDisconnection", clientEntity2, new ServerCallback() { @Override public void onInvalidateHash(long hash) { clientEntity1.fireDisconnectionEvent(); } @Override public void onInvalidateAll() { throw new AssertionError("Should not be called"); } @Override public Chain compact(Chain chain) { throw new AssertionError(); } }); try { serverStoreProxy1.append(1L, createPayload(1L)); fail("expected RuntimeException"); } catch (RuntimeException re) { assertThat(re.getCause(), instanceOf(IllegalStateException.class)); } }
private void allInvalidationDoneResponseListener(EhcacheEntityResponse.AllInvalidationDone response) { LOGGER.debug("CLIENT: on cache {}, server notified that clients invalidated all", getCacheId()); CountDownLatch countDownLatch = invalidateAllLatch.getAndSet(null); if (countDownLatch != null) { LOGGER.debug("CLIENT: on cache {}, count down", getCacheId()); countDownLatch.countDown(); } }