From b9a212870981d5ad0ab23481dc6e4aefc0098caa Mon Sep 17 00:00:00 2001 From: Radim Vansa Date: Wed, 26 Aug 2015 16:21:03 +0200 Subject: [PATCH] HHH-10083 Support replicated and distributed caches --- .../hibernate-infinispan.gradle | 7 + .../access/FutureUpdateSynchronization.java | 52 ++ .../InvalidationCacheAccessDelegate.java | 9 - ....java => InvalidationSynchronization.java} | 4 +- .../access/NonTxPutFromLoadInterceptor.java | 2 +- .../access/PutFromLoadValidator.java | 2 +- .../access/TombstoneAccessDelegate.java | 175 ++++++ .../access/TombstoneCallInterceptor.java | 197 +++++++ .../access/TombstoneSynchronization.java | 49 ++ .../collection/CollectionRegionImpl.java | 5 +- .../infinispan/entity/EntityRegionImpl.java | 4 +- .../infinispan/entity/ReadOnlyAccess.java | 6 +- .../infinispan/entity/ReadWriteAccess.java | 4 +- .../cache/infinispan/impl/BaseRegion.java | 75 ++- .../impl/BaseTransactionalDataRegion.java | 143 ++++- .../naturalid/NaturalIdRegionImpl.java | 8 +- .../query/QueryResultsRegionImpl.java | 62 +-- .../ClusteredTimestampsRegionImpl.java | 15 +- .../cache/infinispan/util/Caches.java | 505 ++++++++++++------ .../cache/infinispan/util/Externalizers.java | 13 +- .../cache/infinispan/util/FutureUpdate.java | 109 ++++ .../util/InvocationAfterCompletion.java | 78 +++ .../cache/infinispan/util/Tombstone.java | 162 ++++++ .../infinispan/util/TombstoneUpdate.java | 78 +++ .../infinispan/AbstractNonFunctionalTest.java | 30 +- .../AbstractRegionAccessStrategyTest.java | 26 + .../InfinispanRegionFactoryTestCase.java | 2 +- ...actCollectionRegionAccessStrategyTest.java | 24 +- .../CollectionRegionAccessExtraAPITest.java | 5 +- .../CollectionRegionReadOnlyAccessTest.java | 21 +- .../CollectionRegionReadWriteAccessTest.java | 13 +- ...llectionRegionTransactionalAccessTest.java | 25 +- ...bstractEntityRegionAccessStrategyTest.java | 4 +- .../entity/EntityRegionExtraAPITest.java | 5 +- .../EntityRegionReadOnlyAccessTest.java | 10 +- .../EntityRegionTransactionalAccessTest.java | 33 +- .../functional/AbstractFunctionalTest.java | 53 +- .../functional/BulkOperationsTest.java | 10 +- .../functional/ConcurrentWriteTest.java | 15 +- .../infinispan/functional/EqualityTest.java | 2 +- .../functional/InvalidationTest.java | 175 ++++++ .../functional/JndiRegionFactoryTest.java | 11 +- .../functional/MultiTenancyTest.java | 2 +- .../infinispan/functional/NoTenancyTest.java | 3 +- .../infinispan/functional/ReadOnlyTest.java | 143 +---- .../infinispan/functional/ReadWriteTest.java | 12 +- .../infinispan/functional/TombstoneTest.java | 375 +++++++++++++ .../cluster/ClusterAwareRegionFactory.java | 12 +- .../functional/cluster/DualNodeTest.java | 2 +- .../EntityCollectionInvalidationTest.java | 16 +- .../cluster/NaturalIdInvalidationTest.java | 2 +- .../cluster/SessionRefreshTest.java | 2 +- .../infinispan/query/QueryRegionImplTest.java | 2 +- .../stress/CorrectnessTestCase.java | 162 +++--- .../timestamp/TimestampsRegionImplTest.java | 6 +- .../util/BatchModeTransactionCoordinator.java | 20 +- .../cache/infinispan/util/CacheTestUtil.java | 29 +- .../util/TestInfinispanRegionFactory.java | 59 +- .../infinispan/util/TestTimeService.java | 26 + 59 files changed, 2393 insertions(+), 708 deletions(-) create mode 100644 hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/access/FutureUpdateSynchronization.java rename hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/access/{Synchronization.java => InvalidationSynchronization.java} (81%) create mode 100644 hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/access/TombstoneAccessDelegate.java create mode 100644 hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/access/TombstoneCallInterceptor.java create mode 100644 hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/access/TombstoneSynchronization.java create mode 100644 hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/util/FutureUpdate.java create mode 100644 hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/util/InvocationAfterCompletion.java create mode 100644 hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/util/Tombstone.java create mode 100644 hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/util/TombstoneUpdate.java create mode 100644 hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/InvalidationTest.java create mode 100644 hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/TombstoneTest.java create mode 100644 hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/util/TestTimeService.java diff --git a/hibernate-infinispan/hibernate-infinispan.gradle b/hibernate-infinispan/hibernate-infinispan.gradle index 5b9efaf962..4e7d0d90d1 100644 --- a/hibernate-infinispan/hibernate-infinispan.gradle +++ b/hibernate-infinispan/hibernate-infinispan.gradle @@ -59,6 +59,13 @@ test { systemProperties['hibernate.cache.infinispan.jgroups_cfg'] = '2lc-test-tcp.xml' // systemProperties['log4j.configuration'] = 'file:/log4j/log4j-infinispan.xml' enabled = true + // Without this I have trouble running specific test using --tests switch + doFirst { + filter.includePatterns.each { + include "${it.replaceAll('\\.', "\\${File.separator}")}.class" + } + filter.setIncludePatterns('*') + } } task packageTests(type: Jar) { diff --git a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/access/FutureUpdateSynchronization.java b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/access/FutureUpdateSynchronization.java new file mode 100644 index 0000000000..2e1db9b5cf --- /dev/null +++ b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/access/FutureUpdateSynchronization.java @@ -0,0 +1,52 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.cache.infinispan.access; + +import org.hibernate.cache.infinispan.util.FutureUpdate; +import org.hibernate.cache.infinispan.util.InvocationAfterCompletion; +import org.hibernate.resource.transaction.TransactionCoordinator; +import org.infinispan.AdvancedCache; +import org.infinispan.util.logging.Log; +import org.infinispan.util.logging.LogFactory; + +import java.util.UUID; + +/** + * @author Radim Vansa <rvansa@redhat.com> + */ +public class FutureUpdateSynchronization extends InvocationAfterCompletion { + private static final Log log = LogFactory.getLog( FutureUpdateSynchronization.class ); + + private final UUID uuid = UUID.randomUUID(); + private final Object key; + private final Object value; + + public FutureUpdateSynchronization(TransactionCoordinator tc, AdvancedCache cache, boolean requiresTransaction, Object key, Object value) { + super(tc, cache, requiresTransaction); + this.key = key; + this.value = value; + } + + public UUID getUuid() { + return uuid; + } + + @Override + protected void invoke(boolean success, AdvancedCache cache) { + // Exceptions in #afterCompletion() are silently ignored, since the transaction + // is already committed in DB. However we must not return until we update the cache. + for (;;) { + try { + cache.put(key, new FutureUpdate(uuid, success ? value : null)); + return; + } + catch (Exception e) { + log.error("Failure updating cache in afterCompletion, will retry", e); + } + } + } +} diff --git a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/access/InvalidationCacheAccessDelegate.java b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/access/InvalidationCacheAccessDelegate.java index 0589e70320..93480e2b5e 100755 --- a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/access/InvalidationCacheAccessDelegate.java +++ b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/access/InvalidationCacheAccessDelegate.java @@ -28,15 +28,6 @@ public abstract class InvalidationCacheAccessDelegate implements AccessDelegate protected final PutFromLoadValidator putValidator; protected final AdvancedCache writeCache; - public static InvalidationCacheAccessDelegate create(BaseRegion region, PutFromLoadValidator validator) { - if (region.getCache().getCacheConfiguration().transaction().transactionMode().isTransactional()) { - return new TxInvalidationCacheAccessDelegate(region, validator); - } - else { - return new NonTxInvalidationCacheAccessDelegate(region, validator); - } - } - /** * Create a new transactional access delegate instance. * diff --git a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/access/Synchronization.java b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/access/InvalidationSynchronization.java similarity index 81% rename from hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/access/Synchronization.java rename to hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/access/InvalidationSynchronization.java index 12aa398bd1..0ca6e93969 100644 --- a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/access/Synchronization.java +++ b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/access/InvalidationSynchronization.java @@ -13,12 +13,12 @@ import java.util.UUID; * * @author Radim Vansa <rvansa@redhat.com> */ -public class Synchronization implements javax.transaction.Synchronization { +public class InvalidationSynchronization implements javax.transaction.Synchronization { public final UUID uuid = UUID.randomUUID(); private final NonTxPutFromLoadInterceptor nonTxPutFromLoadInterceptor; private final Object[] keys; - public Synchronization(NonTxPutFromLoadInterceptor nonTxPutFromLoadInterceptor, Object[] keys) { + public InvalidationSynchronization(NonTxPutFromLoadInterceptor nonTxPutFromLoadInterceptor, Object[] keys) { this.nonTxPutFromLoadInterceptor = nonTxPutFromLoadInterceptor; this.keys = keys; } diff --git a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/access/NonTxPutFromLoadInterceptor.java b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/access/NonTxPutFromLoadInterceptor.java index 07bf4fe79b..620abe37d9 100644 --- a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/access/NonTxPutFromLoadInterceptor.java +++ b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/access/NonTxPutFromLoadInterceptor.java @@ -20,7 +20,7 @@ import org.infinispan.remoting.rpc.RpcManager; * Non-transactional counterpart of {@link TxPutFromLoadInterceptor}. * Invokes {@link PutFromLoadValidator#beginInvalidatingKey(Object, Object)} for each invalidation from * remote node ({@link BeginInvalidationCommand} and sends {@link EndInvalidationCommand} after the transaction - * is complete, with help of {@link Synchronization}; + * is complete, with help of {@link InvalidationSynchronization}; * * @author Radim Vansa <rvansa@redhat.com> */ diff --git a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/access/PutFromLoadValidator.java b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/access/PutFromLoadValidator.java index a7e50d0a69..f58cbbcaf0 100644 --- a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/access/PutFromLoadValidator.java +++ b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/access/PutFromLoadValidator.java @@ -617,7 +617,7 @@ public class PutFromLoadValidator { if (trace) { log.tracef("Registering lock owner %s for %s: %s", lockOwnerToString(session), cache.getName(), Arrays.toString(keys)); } - Synchronization sync = new Synchronization(nonTxPutFromLoadInterceptor, keys); + InvalidationSynchronization sync = new InvalidationSynchronization(nonTxPutFromLoadInterceptor, keys); transactionCoordinator.getLocalSynchronizations().registerSynchronization(sync); return sync.uuid; } diff --git a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/access/TombstoneAccessDelegate.java b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/access/TombstoneAccessDelegate.java new file mode 100644 index 0000000000..24ddc3bc80 --- /dev/null +++ b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/access/TombstoneAccessDelegate.java @@ -0,0 +1,175 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.cache.infinispan.access; + +import org.hibernate.cache.CacheException; +import org.hibernate.cache.infinispan.impl.BaseTransactionalDataRegion; +import org.hibernate.cache.infinispan.util.Caches; +import org.hibernate.cache.infinispan.util.FutureUpdate; +import org.hibernate.cache.infinispan.util.TombstoneUpdate; +import org.hibernate.cache.infinispan.util.Tombstone; +import org.hibernate.cache.spi.access.SoftLock; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.resource.transaction.TransactionCoordinator; +import org.infinispan.AdvancedCache; +import org.infinispan.configuration.cache.Configuration; +import org.infinispan.context.Flag; +import org.infinispan.util.logging.Log; +import org.infinispan.util.logging.LogFactory; + +import java.util.concurrent.TimeUnit; + +/** + * @author Radim Vansa <rvansa@redhat.com> + */ +public class TombstoneAccessDelegate implements AccessDelegate { + private static final Log log = LogFactory.getLog( TombstoneAccessDelegate.class ); + + protected final BaseTransactionalDataRegion region; + protected final AdvancedCache cache; + protected final AdvancedCache writeCache; + protected final AdvancedCache asyncWriteCache; + protected final AdvancedCache putFromLoadCache; + protected final boolean requiresTransaction; + + public TombstoneAccessDelegate(BaseTransactionalDataRegion region) { + this.region = region; + this.cache = region.getCache(); + this.writeCache = Caches.ignoreReturnValuesCache(cache); + this.asyncWriteCache = Caches.asyncWriteCache(cache, Flag.IGNORE_RETURN_VALUES); + this.putFromLoadCache = writeCache.withFlags( Flag.ZERO_LOCK_ACQUISITION_TIMEOUT, Flag.FAIL_SILENTLY ); + Configuration configuration = cache.getCacheConfiguration(); + if (configuration.clustering().cacheMode().isInvalidation()) { + throw new IllegalArgumentException("For tombstone-based caching, invalidation cache is not allowed."); + } + if (configuration.transaction().transactionMode().isTransactional()) { + throw new IllegalArgumentException("Currently transactional caches are not supported."); + } + requiresTransaction = configuration.transaction().transactionMode().isTransactional() + && !configuration.transaction().autoCommit(); + } + + @Override + public Object get(SessionImplementor session, Object key, long txTimestamp) throws CacheException { + if (txTimestamp < region.getLastRegionInvalidation() ) { + return null; + } + Object value = cache.get(key); + if (value instanceof Tombstone) { + return null; + } + else if (value instanceof FutureUpdate) { + return ((FutureUpdate) value).getValue(); + } + else { + return value; + } + } + + @Override + public boolean putFromLoad(SessionImplementor session, Object key, Object value, long txTimestamp, Object version) { + return putFromLoad(session, key, value, txTimestamp, version, false); + } + + @Override + public boolean putFromLoad(SessionImplementor session, Object key, Object value, long txTimestamp, Object version, boolean minimalPutOverride) throws CacheException { + long lastRegionInvalidation = region.getLastRegionInvalidation(); + if (txTimestamp < lastRegionInvalidation) { + log.tracef("putFromLoad not executed since tx started at %d, before last region invalidation finished = %d", txTimestamp, lastRegionInvalidation); + return false; + } + if (minimalPutOverride) { + Object prev = cache.get(key); + if (prev instanceof Tombstone) { + Tombstone tombstone = (Tombstone) prev; + long lastTimestamp = tombstone.getLastTimestamp(); + if (txTimestamp <= lastTimestamp) { + log.tracef("putFromLoad not executed since tx started at %d, before last invalidation finished = %d", txTimestamp, lastTimestamp); + return false; + } + } + else if (prev != null) { + log.tracef("putFromLoad not executed since cache contains %s", prev); + return false; + } + } + // we can't use putForExternalRead since the PFER flag means that entry is not wrapped into context + // when it is present in the container. TombstoneCallInterceptor will deal with this. + putFromLoadCache.put(key, new TombstoneUpdate(session.getTimestamp(), value)); + return true; + } + + @Override + public boolean insert(SessionImplementor session, Object key, Object value, Object version) throws CacheException { + write(session, key, value); + return true; + } + + @Override + public boolean update(SessionImplementor session, Object key, Object value, Object currentVersion, Object previousVersion) throws CacheException { + write(session, key, value); + return true; + } + + protected void write(SessionImplementor session, Object key, Object value) { + TransactionCoordinator tc = session.getTransactionCoordinator(); + FutureUpdateSynchronization sync = new FutureUpdateSynchronization(tc, writeCache, requiresTransaction, key, value); + // FutureUpdate is handled in TombstoneCallInterceptor + writeCache.put(key, new FutureUpdate(sync.getUuid(), null), region.getTombstoneExpiration(), TimeUnit.MILLISECONDS); + tc.getLocalSynchronizations().registerSynchronization(sync); + } + + @Override + public void remove(SessionImplementor session, Object key) throws CacheException { + TransactionCoordinator transactionCoordinator = session.getTransactionCoordinator(); + TombstoneSynchronization sync = new TombstoneSynchronization(transactionCoordinator, asyncWriteCache, requiresTransaction, region, key); + Tombstone tombstone = new Tombstone(sync.getUuid(), session.getTimestamp() + region.getTombstoneExpiration(), false); + writeCache.put(key, tombstone, region.getTombstoneExpiration(), TimeUnit.MILLISECONDS); + transactionCoordinator.getLocalSynchronizations().registerSynchronization(sync); + } + + @Override + public void removeAll() throws CacheException { + region.beginInvalidation(); + try { + Caches.broadcastEvictAll(cache); + } + finally { + region.endInvalidation(); + } + } + + @Override + public void evict(Object key) throws CacheException { + writeCache.put(key, TombstoneUpdate.EVICT); + } + + @Override + public void evictAll() throws CacheException { + region.beginInvalidation(); + try { + Caches.broadcastEvictAll(cache); + } + finally { + region.endInvalidation(); + } + } + + @Override + public void unlockItem(SessionImplementor session, Object key) throws CacheException { + } + + @Override + public boolean afterInsert(SessionImplementor session, Object key, Object value, Object version) { + return false; + } + + @Override + public boolean afterUpdate(SessionImplementor session, Object key, Object value, Object currentVersion, Object previousVersion, SoftLock lock) { + return false; + } +} diff --git a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/access/TombstoneCallInterceptor.java b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/access/TombstoneCallInterceptor.java new file mode 100644 index 0000000000..aa9a1e73e9 --- /dev/null +++ b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/access/TombstoneCallInterceptor.java @@ -0,0 +1,197 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.cache.infinispan.access; + +import org.hibernate.cache.infinispan.util.FutureUpdate; +import org.hibernate.cache.infinispan.util.TombstoneUpdate; +import org.hibernate.cache.infinispan.util.Tombstone; +import org.infinispan.AdvancedCache; +import org.infinispan.commands.read.SizeCommand; +import org.infinispan.commands.write.PutKeyValueCommand; +import org.infinispan.commons.util.CloseableIterable; +import org.infinispan.container.entries.CacheEntry; +import org.infinispan.container.entries.MVCCEntry; +import org.infinispan.context.Flag; +import org.infinispan.context.InvocationContext; +import org.infinispan.factories.annotations.Inject; +import org.infinispan.interceptors.CallInterceptor; +import org.infinispan.metadata.EmbeddedMetadata; +import org.infinispan.metadata.Metadata; +import org.infinispan.util.logging.Log; +import org.infinispan.util.logging.LogFactory; + +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +/** + * Note that this does not implement all commands, only those appropriate for {@link TombstoneAccessDelegate} + * and {@link org.hibernate.cache.infinispan.impl.BaseTransactionalDataRegion} + * + * The behaviour here also breaks notifications, which are not used for 2LC caches. + * + * @author Radim Vansa <rvansa@redhat.com> + */ +public class TombstoneCallInterceptor extends CallInterceptor { + private static final Log log = LogFactory.getLog( TombstoneCallInterceptor.class ); + private static final UUID ZERO = new UUID(0, 0); + + private AdvancedCache cache; + private final Metadata expiringMetadata; + + public TombstoneCallInterceptor(long tombstoneExpiration) { + expiringMetadata = new EmbeddedMetadata.Builder().lifespan(tombstoneExpiration, TimeUnit.MILLISECONDS).build(); + } + + @Inject + public void injectDependencies(AdvancedCache cache) { + this.cache = cache; + } + + @Override + public Object visitPutKeyValueCommand(InvocationContext ctx, PutKeyValueCommand command) throws Throwable { + MVCCEntry e = (MVCCEntry) ctx.lookupEntry(command.getKey()); + if (e == null) { + return null; + } + Object value = command.getValue(); + if (value instanceof TombstoneUpdate) { + return handleTombstoneUpdate(e, (TombstoneUpdate) value); + } + else if (value instanceof Tombstone) { + return handleTombstone(e, (Tombstone) value); + } + else if (value instanceof FutureUpdate) { + return handleFutureUpdate(e, (FutureUpdate) value, command); + } + else { + return super.visitPutKeyValueCommand(ctx, command); + } + } + + private Object handleFutureUpdate(MVCCEntry e, FutureUpdate futureUpdate, PutKeyValueCommand command) { + Object storedValue = e.getValue(); + if (storedValue instanceof FutureUpdate) { + FutureUpdate storedFutureUpdate = (FutureUpdate) storedValue; + if (futureUpdate.getUuid().equals(storedFutureUpdate.getUuid())) { + if (futureUpdate.getValue() != null) { + // transaction succeeded + setValue(e, futureUpdate.getValue()); + } + else { + // transaction failed + setValue(e, storedFutureUpdate.getValue()); + } + } + else { + // two conflicting updates + setValue(e, new FutureUpdate(ZERO, null)); + e.setMetadata(expiringMetadata); + // Infinispan always commits the entry with data with the metadata provided to the command, + // However, in non-conflicting case we want to keep the value not expired + command.setMetadata(expiringMetadata); + } + } + else if (storedValue instanceof Tombstone){ + return null; + } + else { + if (futureUpdate.getValue() != null) { + // The future update has disappeared (probably due to region invalidation) and + // the currently stored value was putFromLoaded (or is null). + // We cannot keep the possibly outdated value here but we cannot know that + // this command's value is the most up-to-date. Therefore, we'll remove + // the value and let future putFromLoad update it. + removeValue(e); + } + else { + // this is the pre-update + // change in logic: we don't keep the old value around anymore (for read-write strategy) + setValue(e, new FutureUpdate(futureUpdate.getUuid(), null)); + } + } + return null; + } + + private Object handleTombstone(MVCCEntry e, Tombstone tombstone) { + Object storedValue = e.getValue(); + if (storedValue instanceof Tombstone) { + e.setChanged(true); + e.setValue(tombstone.merge((Tombstone) storedValue)); + } + else { + setValue(e, tombstone); + } + return null; + } + + protected Object handleTombstoneUpdate(MVCCEntry e, TombstoneUpdate tombstoneUpdate) { + Object storedValue = e.getValue(); + Object value = tombstoneUpdate.getValue(); + + if (storedValue instanceof Tombstone) { + Tombstone tombstone = (Tombstone) storedValue; + if (tombstone.getLastTimestamp() < tombstoneUpdate.getTimestamp()) { + e.setChanged(true); + e.setValue(value); + } + } + else if (storedValue == null) { + // putFromLoad (putIfAbsent) + setValue(e, value); + } + else if (value == null) { + // evict + removeValue(e); + } + return null; + } + + private Object setValue(MVCCEntry e, Object value) { + if (e.isRemoved()) { + e.setRemoved(false); + e.setCreated(true); + e.setValid(true); + } + else { + e.setChanged(true); + } + return e.setValue(value); + } + + private void removeValue(MVCCEntry e) { + e.setRemoved(true); + e.setChanged(true); + e.setCreated(false); + e.setValid(false); + e.setValue(null); + } + + @Override + public Object visitSizeCommand(InvocationContext ctx, SizeCommand command) throws Throwable { + Set flags = command.getFlags(); + int size = 0; + Map contextEntries = ctx.getLookedUpEntries(); + AdvancedCache decoratedCache = cache.getAdvancedCache().withFlags(flags != null ? flags.toArray(new Flag[flags.size()]) : null); + // In non-transactional caches we don't care about context + CloseableIterable> iterable = decoratedCache + .filterEntries(Tombstone.EXCLUDE_TOMBSTONES) + .converter(FutureUpdate.VALUE_EXTRACTOR); + try { + for (CacheEntry entry : iterable) { + if (entry.getValue() != null && size++ == Integer.MAX_VALUE) { + return Integer.MAX_VALUE; + } + } + } + finally { + iterable.close(); + } + return size; + } +} diff --git a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/access/TombstoneSynchronization.java b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/access/TombstoneSynchronization.java new file mode 100644 index 0000000000..350bbc6259 --- /dev/null +++ b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/access/TombstoneSynchronization.java @@ -0,0 +1,49 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.cache.infinispan.access; + +import org.hibernate.cache.infinispan.impl.BaseTransactionalDataRegion; +import org.hibernate.cache.infinispan.util.InvocationAfterCompletion; +import org.hibernate.cache.infinispan.util.Tombstone; +import org.hibernate.resource.transaction.TransactionCoordinator; +import org.infinispan.AdvancedCache; + +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +/** + * @author Radim Vansa <rvansa@redhat.com> + */ +public class TombstoneSynchronization extends InvocationAfterCompletion { + private final UUID uuid = UUID.randomUUID(); + private final BaseTransactionalDataRegion region; + private final K key; + + public TombstoneSynchronization(TransactionCoordinator tc, AdvancedCache cache, boolean requiresTransaction, BaseTransactionalDataRegion region, K key) { + super(tc, cache, requiresTransaction); + this.key = key; + this.region = region; + } + + public UUID getUuid() { + return uuid; + } + + public K getKey() { + return key; + } + + @Override + public void beforeCompletion() { + } + + @Override + public void invoke(boolean success, AdvancedCache cache) { + Tombstone tombstone = new Tombstone(uuid, region.nextTimestamp(), true); + cache.put(key, tombstone, region.getTombstoneExpiration(), TimeUnit.MILLISECONDS); + } +} diff --git a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/collection/CollectionRegionImpl.java b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/collection/CollectionRegionImpl.java index fc79383bc9..7788fcb5dc 100644 --- a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/collection/CollectionRegionImpl.java +++ b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/collection/CollectionRegionImpl.java @@ -47,13 +47,12 @@ public class CollectionRegionImpl extends BaseTransactionalDataRegion implements @Override public CollectionRegionAccessStrategy buildAccessStrategy(AccessType accessType) throws CacheException { checkAccessType( accessType ); - getValidator(); - AccessDelegate delegate = InvalidationCacheAccessDelegate.create(this, getValidator()); + AccessDelegate accessDelegate = createAccessDelegate(); switch ( accessType ) { case READ_ONLY: case READ_WRITE: case TRANSACTIONAL: - return new CollectionAccess( this, delegate ); + return new CollectionAccess( this, accessDelegate ); default: throw new CacheException( "Unsupported access type [" + accessType.getExternalName() + "]" ); } diff --git a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/entity/EntityRegionImpl.java b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/entity/EntityRegionImpl.java index 759933830a..7fab32196e 100644 --- a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/entity/EntityRegionImpl.java +++ b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/entity/EntityRegionImpl.java @@ -7,7 +7,7 @@ package org.hibernate.cache.infinispan.entity; import org.hibernate.cache.CacheException; -import org.hibernate.cache.infinispan.access.InvalidationCacheAccessDelegate; +import org.hibernate.cache.infinispan.access.AccessDelegate; import org.hibernate.cache.infinispan.impl.BaseTransactionalDataRegion; import org.hibernate.cache.spi.CacheDataDescription; import org.hibernate.cache.spi.CacheKeysFactory; @@ -50,7 +50,7 @@ public class EntityRegionImpl extends BaseTransactionalDataRegion implements Ent if ( !getCacheDataDescription().isMutable() ) { accessType = AccessType.READ_ONLY; } - InvalidationCacheAccessDelegate accessDelegate = InvalidationCacheAccessDelegate.create(this, getValidator()); + AccessDelegate accessDelegate = createAccessDelegate(); switch ( accessType ) { case READ_ONLY: return new ReadOnlyAccess( this, accessDelegate); diff --git a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/entity/ReadOnlyAccess.java b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/entity/ReadOnlyAccess.java index af92f1b97b..6bde288f3f 100644 --- a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/entity/ReadOnlyAccess.java +++ b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/entity/ReadOnlyAccess.java @@ -7,7 +7,7 @@ package org.hibernate.cache.infinispan.entity; import org.hibernate.cache.CacheException; -import org.hibernate.cache.infinispan.access.InvalidationCacheAccessDelegate; +import org.hibernate.cache.infinispan.access.AccessDelegate; import org.hibernate.cache.spi.EntityRegion; import org.hibernate.cache.spi.access.EntityRegionAccessStrategy; import org.hibernate.cache.spi.access.SoftLock; @@ -25,9 +25,9 @@ import org.hibernate.persister.entity.EntityPersister; class ReadOnlyAccess implements EntityRegionAccessStrategy { protected final EntityRegionImpl region; - protected final InvalidationCacheAccessDelegate delegate; + protected final AccessDelegate delegate; - ReadOnlyAccess(EntityRegionImpl region, InvalidationCacheAccessDelegate delegate) { + ReadOnlyAccess(EntityRegionImpl region, AccessDelegate delegate) { this.region = region; this.delegate = delegate; } diff --git a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/entity/ReadWriteAccess.java b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/entity/ReadWriteAccess.java index f8dd4b439f..912037503c 100644 --- a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/entity/ReadWriteAccess.java +++ b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/entity/ReadWriteAccess.java @@ -7,7 +7,7 @@ package org.hibernate.cache.infinispan.entity; import org.hibernate.cache.CacheException; -import org.hibernate.cache.infinispan.access.InvalidationCacheAccessDelegate; +import org.hibernate.cache.infinispan.access.AccessDelegate; import org.hibernate.cache.spi.access.SoftLock; import org.hibernate.engine.spi.SessionImplementor; @@ -20,7 +20,7 @@ import org.hibernate.engine.spi.SessionImplementor; */ class ReadWriteAccess extends ReadOnlyAccess { - ReadWriteAccess(EntityRegionImpl region, InvalidationCacheAccessDelegate delegate) { + ReadWriteAccess(EntityRegionImpl region, AccessDelegate delegate) { super(region, delegate); } diff --git a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/impl/BaseRegion.java b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/impl/BaseRegion.java index cf6953c14b..a176c3900d 100644 --- a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/impl/BaseRegion.java +++ b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/impl/BaseRegion.java @@ -38,25 +38,18 @@ public abstract class BaseRegion implements Region { private static final Log log = LogFactory.getLog( BaseRegion.class ); - private enum InvalidateState { - INVALID, CLEARING, VALID - } - - private final String name; - private final AdvancedCache localAndSkipLoadCache; - private final TransactionManager tm; - - private final Object invalidationMutex = new Object(); - private final AtomicReference invalidateState = - new AtomicReference( InvalidateState.VALID ); - + protected final String name; + protected final AdvancedCache cache; + protected final AdvancedCache localAndSkipLoadCache; + protected final TransactionManager tm; private final RegionFactory factory; - protected final AdvancedCache cache; + protected volatile long lastRegionInvalidation = Long.MIN_VALUE; + protected int invalidations = 0; private PutFromLoadValidator validator; - /** + /** * Base region constructor. * * @param cache instance for the region @@ -146,32 +139,7 @@ public abstract class BaseRegion implements Region { * @return true if the region is valid, false otherwise */ public boolean checkValid() { - boolean valid = isValid(); - if ( !valid ) { - synchronized (invalidationMutex) { - if ( invalidateState.compareAndSet( InvalidateState.INVALID, InvalidateState.CLEARING ) ) { - try { - runInvalidation( getCurrentTransaction() != null ); - log.tracef( "Transition state from CLEARING to VALID" ); - invalidateState.compareAndSet( - InvalidateState.CLEARING, InvalidateState.VALID - ); - } - catch ( Exception e ) { - if ( log.isTraceEnabled() ) { - log.trace( "Could not invalidate region: ", e ); - } - } - } - } - valid = isValid(); - } - - return valid; - } - - protected boolean isValid() { - return invalidateState.get() == InvalidateState.VALID; + return lastRegionInvalidation != Long.MAX_VALUE; } /** @@ -213,10 +181,31 @@ public abstract class BaseRegion implements Region { * Invalidates the region. */ public void invalidateRegion() { + // this is called only from EvictAllCommand, we don't have any ongoing transaction + beginInvalidation(); + endInvalidation(); + } + + public void beginInvalidation() { if (log.isTraceEnabled()) { - log.trace( "Invalidate region: " + name ); + log.trace( "Begin invalidating region: " + name ); + } + synchronized (this) { + lastRegionInvalidation = Long.MAX_VALUE; + ++invalidations; + } + runInvalidation(getCurrentTransaction() != null); + } + + public void endInvalidation() { + synchronized (this) { + if (--invalidations == 0) { + lastRegionInvalidation = nextTimestamp(); + } + } + if (log.isTraceEnabled()) { + log.trace( "End invalidating region: " + name ); } - invalidateState.set(InvalidateState.INVALID); } public TransactionManager getTransactionManager() { @@ -233,7 +222,7 @@ public abstract class BaseRegion implements Region { return cache; } - private Transaction getCurrentTransaction() { + protected Transaction getCurrentTransaction() { try { // Transaction manager could be null return tm != null ? tm.getTransaction() : null; diff --git a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/impl/BaseTransactionalDataRegion.java b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/impl/BaseTransactionalDataRegion.java index 8011334d50..46df90e1fa 100644 --- a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/impl/BaseTransactionalDataRegion.java +++ b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/impl/BaseTransactionalDataRegion.java @@ -6,15 +6,35 @@ */ package org.hibernate.cache.infinispan.impl; +import org.hibernate.cache.infinispan.InfinispanRegionFactory; +import org.hibernate.cache.infinispan.access.AccessDelegate; +import org.hibernate.cache.infinispan.access.NonTxInvalidationCacheAccessDelegate; +import org.hibernate.cache.infinispan.access.TombstoneAccessDelegate; +import org.hibernate.cache.infinispan.access.TombstoneCallInterceptor; +import org.hibernate.cache.infinispan.access.TxInvalidationCacheAccessDelegate; +import org.hibernate.cache.infinispan.util.Caches; +import org.hibernate.cache.infinispan.util.FutureUpdate; +import org.hibernate.cache.infinispan.util.Tombstone; import org.hibernate.cache.spi.CacheDataDescription; import org.hibernate.cache.spi.CacheKeysFactory; import org.hibernate.cache.spi.RegionFactory; import org.hibernate.cache.spi.TransactionalDataRegion; import org.infinispan.AdvancedCache; +import org.infinispan.commons.util.CloseableIterator; +import org.infinispan.configuration.cache.CacheMode; +import org.infinispan.configuration.cache.Configuration; +import org.infinispan.container.entries.CacheEntry; +import org.infinispan.interceptors.CallInterceptor; +import org.infinispan.interceptors.base.CommandInterceptor; +import org.infinispan.util.logging.Log; +import org.infinispan.util.logging.LogFactory; import javax.transaction.TransactionManager; +import java.util.List; +import java.util.Map; + /** * Support for Inifinispan {@link org.hibernate.cache.spi.TransactionalDataRegion} implementors. * @@ -24,26 +44,51 @@ import javax.transaction.TransactionManager; */ public abstract class BaseTransactionalDataRegion extends BaseRegion implements TransactionalDataRegion { - + private static final Log log = LogFactory.getLog( BaseTransactionalDataRegion.class ); private final CacheDataDescription metadata; private final CacheKeysFactory cacheKeysFactory; - /** - * Base transactional region constructor - * - * @param cache instance to store transactional data - * @param name of the transactional region + protected final boolean useTombstones; + protected final long tombstoneExpiration; + protected final boolean requiresTransaction; + + /** + * Base transactional region constructor + * + * @param cache instance to store transactional data + * @param name of the transactional region * @param transactionManager - * @param metadata for the transactional region - * @param factory for the transactional region - * @param cacheKeysFactory factory for cache keys - */ + * @param metadata for the transactional region + * @param factory for the transactional region + * @param cacheKeysFactory factory for cache keys + */ public BaseTransactionalDataRegion( AdvancedCache cache, String name, TransactionManager transactionManager, CacheDataDescription metadata, RegionFactory factory, CacheKeysFactory cacheKeysFactory) { super( cache, name, transactionManager, factory); this.metadata = metadata; this.cacheKeysFactory = cacheKeysFactory; + + Configuration configuration = cache.getCacheConfiguration(); + CacheMode cacheMode = configuration.clustering().cacheMode(); + + useTombstones = cacheMode.isDistributed() || cacheMode.isReplicated(); + // TODO: make these timeouts configurable + tombstoneExpiration = InfinispanRegionFactory.PENDING_PUTS_CACHE_CONFIGURATION.expiration().maxIdle(); + requiresTransaction = configuration.transaction().transactionMode().isTransactional() + && !configuration.transaction().autoCommit(); + + if (useTombstones) { + if (configuration.eviction().maxEntries() >= 0) { + log.warn("Setting eviction on cache using tombstones can introduce inconsistencies!"); + } + + cache.removeInterceptor(CallInterceptor.class); + TombstoneCallInterceptor tombstoneCallInterceptor = new TombstoneCallInterceptor(tombstoneExpiration); + cache.getComponentRegistry().registerComponent(tombstoneCallInterceptor, TombstoneCallInterceptor.class); + List interceptorChain = cache.getInterceptorChain(); + cache.addInterceptor(tombstoneCallInterceptor, interceptorChain.size()); + } } @Override @@ -54,4 +99,82 @@ public abstract class BaseTransactionalDataRegion public CacheKeysFactory getCacheKeysFactory() { return cacheKeysFactory; } + + protected AccessDelegate createAccessDelegate() { + CacheMode cacheMode = cache.getCacheConfiguration().clustering().cacheMode(); + if (cacheMode.isDistributed() || cacheMode.isReplicated()) { + return new TombstoneAccessDelegate(this); + } + else { + if (cache.getCacheConfiguration().transaction().transactionMode().isTransactional()) { + return new TxInvalidationCacheAccessDelegate(this, getValidator()); + } + else { + return new NonTxInvalidationCacheAccessDelegate(this, getValidator()); + } + } + } + + public long getTombstoneExpiration() { + return tombstoneExpiration; + } + + public long getLastRegionInvalidation() { + return lastRegionInvalidation; + } + + @Override + protected void runInvalidation(boolean inTransaction) { + if (!useTombstones) { + super.runInvalidation(inTransaction); + return; + } + // If the transaction is required, we simply need it -> will create our own + boolean startedTx = false; + if ( !inTransaction && requiresTransaction) { + try { + tm.begin(); + startedTx = true; + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + // We can never use cache.clear() since tombstones must be kept. + try { + AdvancedCache localCache = Caches.localCache(cache); + CloseableIterator it = Caches.entrySet(localCache, Tombstone.EXCLUDE_TOMBSTONES).iterator(); + try { + while (it.hasNext()) { + // Cannot use it.next(); it.remove() due to ISPN-5653 + CacheEntry entry = it.next(); + localCache.remove(entry.getKey(), entry.getValue()); + } + } + finally { + it.close(); + } + } + finally { + if (startedTx) { + try { + tm.commit(); + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + } + } + + @Override + public Map toMap() { + if (useTombstones) { + AdvancedCache localCache = Caches.localCache(cache); + return Caches.entrySet(localCache, Tombstone.EXCLUDE_TOMBSTONES, FutureUpdate.VALUE_EXTRACTOR).toMap(); + } + else { + return super.toMap(); + } + } } diff --git a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/naturalid/NaturalIdRegionImpl.java b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/naturalid/NaturalIdRegionImpl.java index 4a6c522c6e..cfefcc7c56 100644 --- a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/naturalid/NaturalIdRegionImpl.java +++ b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/naturalid/NaturalIdRegionImpl.java @@ -8,8 +8,6 @@ package org.hibernate.cache.infinispan.naturalid; import org.hibernate.cache.CacheException; import org.hibernate.cache.infinispan.access.AccessDelegate; -import org.hibernate.cache.infinispan.access.PutFromLoadValidator; -import org.hibernate.cache.infinispan.access.InvalidationCacheAccessDelegate; import org.hibernate.cache.infinispan.impl.BaseTransactionalDataRegion; import org.hibernate.cache.spi.CacheDataDescription; import org.hibernate.cache.spi.CacheKeysFactory; @@ -52,13 +50,13 @@ public class NaturalIdRegionImpl extends BaseTransactionalDataRegion if (!getCacheDataDescription().isMutable()) { accessType = AccessType.READ_ONLY; } - AccessDelegate delegate = InvalidationCacheAccessDelegate.create( this, getValidator()); + AccessDelegate accessDelegate = createAccessDelegate(); switch ( accessType ) { case READ_ONLY: - return new ReadOnlyAccess( this, delegate ); + return new ReadOnlyAccess( this, accessDelegate ); case READ_WRITE: case TRANSACTIONAL: - return new ReadWriteAccess( this, delegate ); + return new ReadWriteAccess( this, accessDelegate ); default: throw new CacheException( "Unsupported access type [" + accessType.getExternalName() + "]" ); } diff --git a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/query/QueryResultsRegionImpl.java b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/query/QueryResultsRegionImpl.java index 9274e46bc7..18d18b66fe 100644 --- a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/query/QueryResultsRegionImpl.java +++ b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/query/QueryResultsRegionImpl.java @@ -8,18 +8,14 @@ package org.hibernate.cache.infinispan.query; import javax.transaction.Transaction; import javax.transaction.TransactionManager; -import javax.transaction.Status; -import javax.transaction.Synchronization; -import org.hibernate.HibernateException; import org.hibernate.cache.CacheException; import org.hibernate.cache.infinispan.impl.BaseTransactionalDataRegion; import org.hibernate.cache.infinispan.util.Caches; +import org.hibernate.cache.infinispan.util.InvocationAfterCompletion; import org.hibernate.cache.spi.QueryResultsRegion; import org.hibernate.cache.spi.RegionFactory; import org.hibernate.engine.spi.SessionImplementor; -import org.hibernate.jdbc.WorkExecutor; -import org.hibernate.jdbc.WorkExecutorVisitable; import org.hibernate.resource.transaction.TransactionCoordinator; import org.infinispan.AdvancedCache; import org.infinispan.configuration.cache.TransactionConfiguration; @@ -28,8 +24,6 @@ import org.infinispan.transaction.TransactionMode; import org.infinispan.util.logging.Log; import org.infinispan.util.logging.LogFactory; -import java.sql.Connection; -import java.sql.SQLException; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -106,14 +100,6 @@ public class QueryResultsRegionImpl extends BaseTransactionalDataRegion implemen @Override public Object get(SessionImplementor session, Object key) throws CacheException { - // If the region is not valid, skip cache store to avoid going remote to retrieve the query. - // The aim of this is to maintain same logic/semantics as when state transfer was configured. - // TODO: Once https://issues.jboss.org/browse/ISPN-835 has been resolved, revert to state transfer and remove workaround - boolean skipCacheStore = false; - if ( !isValid() ) { - skipCacheStore = true; - } - if ( !checkValid() ) { return null; } @@ -129,12 +115,7 @@ public class QueryResultsRegionImpl extends BaseTransactionalDataRegion implemen result = map.get(key); } if (result == null) { - if ( skipCacheStore ) { - result = getCache.withFlags( Flag.SKIP_CACHE_STORE ).get( key ); - } - else { - result = getCache.get( key ); - } + result = getCache.get( key ); } return result; } @@ -178,51 +159,28 @@ public class QueryResultsRegionImpl extends BaseTransactionalDataRegion implemen } } - private class PostTransactionQueryUpdate implements Synchronization { - private final TransactionCoordinator tc; + private class PostTransactionQueryUpdate extends InvocationAfterCompletion { private final SessionImplementor session; private final Object key; private final Object value; public PostTransactionQueryUpdate(TransactionCoordinator tc, SessionImplementor session, Object key, Object value) { - this.tc = tc; + super(tc, putCache, putCacheRequiresTransaction); this.session = session; this.key = key; this.value = value; } @Override - public void beforeCompletion() { + public void afterCompletion(int status) { + transactionContext.remove(session); + super.afterCompletion(status); } @Override - public void afterCompletion(int status) { - transactionContext.remove(session); - switch (status) { - case Status.STATUS_COMMITTING: - case Status.STATUS_COMMITTED: - try { - // TODO: isolation without obtaining Connection - tc.createIsolationDelegate().delegateWork(new WorkExecutorVisitable() { - @Override - public Void accept(WorkExecutor executor, Connection connection) throws SQLException { - putCache.put(key, value); - return null; - } - } - , putCacheRequiresTransaction); - } - catch (HibernateException e) { - // silently fail any exceptions - if (log.isTraceEnabled()) { - log.trace("Exception during query cache update", e); - } - } - break; - default: - // it would be nicer to react only on ROLLING_BACK and ROLLED_BACK statuses - // but TransactionCoordinator gives us UNKNOWN on rollback - break; + protected void invoke(boolean success, AdvancedCache cache) { + if (success) { + cache.put(key, value); } } } diff --git a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/timestamp/ClusteredTimestampsRegionImpl.java b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/timestamp/ClusteredTimestampsRegionImpl.java index a684d2d97c..6567e697b1 100644 --- a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/timestamp/ClusteredTimestampsRegionImpl.java +++ b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/timestamp/ClusteredTimestampsRegionImpl.java @@ -67,21 +67,8 @@ public class ClusteredTimestampsRegionImpl extends TimestampsRegionImpl { public Object get(SessionImplementor session, Object key) throws CacheException { Object value = localCache.get( key ); - // If the region is not valid, skip cache store to avoid going remote to retrieve the query. - // The aim of this is to maintain same logic/semantics as when state transfer was configured. - // TODO: Once https://issues.jboss.org/browse/ISPN-835 has been resolved, revert to state transfer and remove workaround - boolean skipCacheStore = false; - if ( !isValid() ) { - skipCacheStore = true; - } - if ( value == null && checkValid() ) { - if ( skipCacheStore ) { - value = cache.withFlags( Flag.SKIP_CACHE_STORE ).get( key ); - } - else { - value = cache.get( key ); - } + value = cache.get( key ); if ( value != null ) { localCache.put( key, value ); diff --git a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/util/Caches.java b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/util/Caches.java index 89e8ff615f..55b5c3e85c 100644 --- a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/util/Caches.java +++ b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/util/Caches.java @@ -6,9 +6,9 @@ */ package org.hibernate.cache.infinispan.util; -import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; -import java.util.Iterator; +import java.util.Map; import java.util.Set; import java.util.concurrent.Callable; import javax.transaction.Status; @@ -20,6 +20,8 @@ import org.infinispan.commons.util.CloseableIterator; import org.infinispan.container.entries.CacheEntry; import org.infinispan.context.Flag; import org.infinispan.filter.AcceptAllKeyValueFilter; +import org.infinispan.filter.Converter; +import org.infinispan.filter.KeyValueFilter; import org.infinispan.filter.NullValueConverter; import org.infinispan.remoting.rpc.RpcManager; import org.infinispan.remoting.rpc.RpcOptions; @@ -36,17 +38,17 @@ public class Caches { // Suppresses default constructor, ensuring non-instantiability. } - /** - * Call an operation within a transaction. This method guarantees that the - * right pattern is used to make sure that the transaction is always either - * committed or rollback. - * - * @param cache instance whose transaction manager to use - * @param c callable instance to run within a transaction - * @param type of callable return - * @return returns whatever the callable returns - * @throws Exception if any operation within the transaction fails - */ + /** + * Call an operation within a transaction. This method guarantees that the + * right pattern is used to make sure that the transaction is always either + * committed or rollback. + * + * @param cache instance whose transaction manager to use + * @param c callable instance to run within a transaction + * @param type of callable return + * @return returns whatever the callable returns + * @throws Exception if any operation within the transaction fails + */ public static T withinTx( AdvancedCache cache, Callable c) throws Exception { @@ -54,17 +56,17 @@ public class Caches { return withinTx( cache.getTransactionManager(), c ); } - /** - * Call an operation within a transaction. This method guarantees that the - * right pattern is used to make sure that the transaction is always either - * committed or rollbacked. - * - * @param tm transaction manager - * @param c callable instance to run within a transaction - * @param type of callable return - * @return returns whatever the callable returns - * @throws Exception if any operation within the transaction fails - */ + /** + * Call an operation within a transaction. This method guarantees that the + * right pattern is used to make sure that the transaction is always either + * committed or rollbacked. + * + * @param tm transaction manager + * @param c callable instance to run within a transaction + * @param type of callable return + * @return returns whatever the callable returns + * @throws Exception if any operation within the transaction fails + */ public static T withinTx( TransactionManager tm, Callable c) throws Exception { @@ -106,36 +108,36 @@ public class Caches { }); } - /** - * Transform a given cache into a local cache - * - * @param cache to be transformed - * @return a cache that operates only in local-mode - */ + /** + * Transform a given cache into a local cache + * + * @param cache to be transformed + * @return a cache that operates only in local-mode + */ public static AdvancedCache localCache(AdvancedCache cache) { return cache.withFlags( Flag.CACHE_MODE_LOCAL ); } - /** - * Transform a given cache into a cache that ignores return values for - * operations returning previous values, i.e. {@link AdvancedCache#put(Object, Object)} - * - * @param cache to be transformed - * @return a cache that ignores return values - */ + /** + * Transform a given cache into a cache that ignores return values for + * operations returning previous values, i.e. {@link AdvancedCache#put(Object, Object)} + * + * @param cache to be transformed + * @return a cache that ignores return values + */ public static AdvancedCache ignoreReturnValuesCache(AdvancedCache cache) { return cache.withFlags( Flag.SKIP_CACHE_LOAD, Flag.SKIP_REMOTE_LOOKUP, Flag.IGNORE_RETURN_VALUES ); } - /** - * Transform a given cache into a cache that ignores return values for - * operations returning previous values, i.e. {@link AdvancedCache#put(Object, Object)}, - * adding an extra flag. - * - * @param cache to be transformed - * @param extraFlag to add to the returned cache - * @return a cache that ignores return values - */ + /** + * Transform a given cache into a cache that ignores return values for + * operations returning previous values, i.e. {@link AdvancedCache#put(Object, Object)}, + * adding an extra flag. + * + * @param cache to be transformed + * @param extraFlag to add to the returned cache + * @return a cache that ignores return values + */ public static AdvancedCache ignoreReturnValuesCache( AdvancedCache cache, Flag extraFlag) { return cache.withFlags( @@ -143,14 +145,14 @@ public class Caches { ); } - /** - * Transform a given cache into a cache that writes cache entries without - * waiting for them to complete, adding an extra flag. - * - * @param cache to be transformed - * @param extraFlag to add to the returned cache - * @return a cache that writes asynchronously - */ + /** + * Transform a given cache into a cache that writes cache entries without + * waiting for them to complete, adding an extra flag. + * + * @param cache to be transformed + * @param extraFlag to add to the returned cache + * @return a cache that writes asynchronously + */ public static AdvancedCache asyncWriteCache( AdvancedCache cache, Flag extraFlag) { @@ -162,12 +164,12 @@ public class Caches { ); } - /** - * Transform a given cache into a cache that fails silently if cache writes fail. - * - * @param cache to be transformed - * @return a cache that fails silently if cache writes fail - */ + /** + * Transform a given cache into a cache that fails silently if cache writes fail. + * + * @param cache to be transformed + * @return a cache that fails silently if cache writes fail + */ public static AdvancedCache failSilentWriteCache(AdvancedCache cache) { return cache.withFlags( Flag.FAIL_SILENTLY, @@ -177,14 +179,14 @@ public class Caches { ); } - /** - * Transform a given cache into a cache that fails silently if - * cache writes fail, adding an extra flag. - * - * @param cache to be transformed - * @param extraFlag to be added to returned cache - * @return a cache that fails silently if cache writes fail - */ + /** + * Transform a given cache into a cache that fails silently if + * cache writes fail, adding an extra flag. + * + * @param cache to be transformed + * @param extraFlag to be added to returned cache + * @return a cache that fails silently if cache writes fail + */ public static AdvancedCache failSilentWriteCache( AdvancedCache cache, Flag extraFlag) { @@ -197,13 +199,13 @@ public class Caches { ); } - /** - * Transform a given cache into a cache that fails silently if - * cache reads fail. - * - * @param cache to be transformed - * @return a cache that fails silently if cache reads fail - */ + /** + * Transform a given cache into a cache that fails silently if + * cache reads fail. + * + * @param cache to be transformed + * @return a cache that fails silently if cache reads fail + */ public static AdvancedCache failSilentReadCache(AdvancedCache cache) { return cache.withFlags( Flag.FAIL_SILENTLY, @@ -211,11 +213,11 @@ public class Caches { ); } - /** - * Broadcast an evict-all command with the given cache instance. - * - * @param cache instance used to broadcast command - */ + /** + * Broadcast an evict-all command with the given cache instance. + * + * @param cache instance used to broadcast command + */ public static void broadcastEvictAll(AdvancedCache cache) { final RpcManager rpcManager = cache.getRpcManager(); if ( rpcManager != null ) { @@ -230,41 +232,41 @@ public class Caches { } } - /** - * Indicates whether the given cache is configured with - * {@link org.infinispan.configuration.cache.CacheMode#INVALIDATION_ASYNC} or - * {@link org.infinispan.configuration.cache.CacheMode#INVALIDATION_SYNC}. - * - * @param cache to check for invalidation configuration - * @return true if the cache is configured with invalidation, false otherwise - */ + /** + * Indicates whether the given cache is configured with + * {@link org.infinispan.configuration.cache.CacheMode#INVALIDATION_ASYNC} or + * {@link org.infinispan.configuration.cache.CacheMode#INVALIDATION_SYNC}. + * + * @param cache to check for invalidation configuration + * @return true if the cache is configured with invalidation, false otherwise + */ public static boolean isInvalidationCache(AdvancedCache cache) { return cache.getCacheConfiguration() .clustering().cacheMode().isInvalidation(); } - /** - * Indicates whether the given cache is configured with - * {@link org.infinispan.configuration.cache.CacheMode#REPL_SYNC}, - * {@link org.infinispan.configuration.cache.CacheMode#INVALIDATION_SYNC}, or - * {@link org.infinispan.configuration.cache.CacheMode#DIST_SYNC}. - * - * @param cache to check for synchronous configuration - * @return true if the cache is configured with synchronous mode, false otherwise - */ + /** + * Indicates whether the given cache is configured with + * {@link org.infinispan.configuration.cache.CacheMode#REPL_SYNC}, + * {@link org.infinispan.configuration.cache.CacheMode#INVALIDATION_SYNC}, or + * {@link org.infinispan.configuration.cache.CacheMode#DIST_SYNC}. + * + * @param cache to check for synchronous configuration + * @return true if the cache is configured with synchronous mode, false otherwise + */ public static boolean isSynchronousCache(AdvancedCache cache) { return cache.getCacheConfiguration() .clustering().cacheMode().isSynchronous(); } - /** - * Indicates whether the given cache is configured to cluster its contents. - * A cache is considered to clustered if it's configured with any cache mode - * except {@link org.infinispan.configuration.cache.CacheMode#LOCAL} - * - * @param cache to check whether it clusters its contents - * @return true if the cache is configured with clustering, false otherwise - */ + /** + * Indicates whether the given cache is configured to cluster its contents. + * A cache is considered to clustered if it's configured with any cache mode + * except {@link org.infinispan.configuration.cache.CacheMode#LOCAL} + * + * @param cache to check whether it clusters its contents + * @return true if the cache is configured with clustering, false otherwise + */ public static boolean isClustered(AdvancedCache cache) { return cache.getCacheConfiguration() .clustering().cacheMode().isClustered(); @@ -291,89 +293,236 @@ public class Caches { /** * This interface is provided for convenient fluent use of CloseableIterable */ - public interface CollectableCloseableIterable extends CloseableIterable { - Set toSet(); + public interface CollectableCloseableIterable extends CloseableIterable { + Set toSet(); } - public static CollectableCloseableIterable keys(AdvancedCache cache) { + public interface MapCollectableCloseableIterable extends CloseableIterable> { + Map toMap(); + } + + public static CollectableCloseableIterable keys(AdvancedCache cache) { + return keys(cache, (KeyValueFilter) AcceptAllKeyValueFilter.getInstance()); + } + + public static CollectableCloseableIterable keys(AdvancedCache cache, KeyValueFilter filter) { if (cache.getCacheConfiguration().transaction().transactionMode().isTransactional()) { // Dummy read to enlist the LocalTransaction as workaround for ISPN-5676 cache.containsKey(false); } // HHH-10023: we can't use keySet() - final CloseableIterable> entryIterable = cache - .filterEntries( AcceptAllKeyValueFilter.getInstance() ) + final CloseableIterable> entryIterable = cache + .filterEntries( filter ) .converter( NullValueConverter.getInstance() ); - return new CollectableCloseableIterable() { + return new CollectableCloseableIterableImpl(entryIterable, Selector.KEY); + } + + public static CollectableCloseableIterable values(AdvancedCache cache) { + return values(cache, (KeyValueFilter) AcceptAllKeyValueFilter.getInstance()); + } + + public static CollectableCloseableIterable values(AdvancedCache cache, KeyValueFilter filter) { + if (cache.getCacheConfiguration().transaction().transactionMode().isTransactional()) { + // Dummy read to enlist the LocalTransaction as workaround for ISPN-5676 + cache.containsKey(false); + } + // HHH-10023: we can't use values() + final CloseableIterable> entryIterable = cache.filterEntries(filter); + return new CollectableCloseableIterableImpl(entryIterable, Selector.VALUE); + } + + public static CollectableCloseableIterable values(AdvancedCache cache, KeyValueFilter filter, Converter converter) { + if (cache.getCacheConfiguration().transaction().transactionMode().isTransactional()) { + // Dummy read to enlist the LocalTransaction as workaround for ISPN-5676 + cache.containsKey(false); + } + // HHH-10023: we can't use values() + final CloseableIterable> entryIterable = cache.filterEntries(filter).converter(converter); + return new CollectableCloseableIterableImpl(entryIterable, Selector.VALUE); + } + + + public static MapCollectableCloseableIterable entrySet(AdvancedCache cache) { + return entrySet(cache, (KeyValueFilter) AcceptAllKeyValueFilter.getInstance()); + } + + public static MapCollectableCloseableIterable entrySet(AdvancedCache cache, KeyValueFilter filter) { + if (cache.getCacheConfiguration().transaction().transactionMode().isTransactional()) { + // Dummy read to enlist the LocalTransaction as workaround for ISPN-5676 + cache.containsKey(false); + } + // HHH-10023: we can't use values() + final CloseableIterable> entryIterable = cache.filterEntries(filter); + return new MapCollectableCloseableIterableImpl(entryIterable); + } + + public static MapCollectableCloseableIterable entrySet(AdvancedCache cache, KeyValueFilter filter, Converter converter) { + if (cache.getCacheConfiguration().transaction().transactionMode().isTransactional()) { + // Dummy read to enlist the LocalTransaction as workaround for ISPN-5676 + cache.containsKey(false); + } + // HHH-10023: we can't use values() + final CloseableIterable> entryIterable = cache.filterEntries(filter).converter(converter); + return new MapCollectableCloseableIterableImpl(entryIterable); + } + + /* Function, T> */ + private interface Selector { + Selector KEY = new Selector() { @Override - public void close() { - entryIterable.close(); - } - - @Override - public CloseableIterator iterator() { - final CloseableIterator> entryIterator = entryIterable.iterator(); - return new CloseableIterator() { - @Override - public void close() { - entryIterator.close(); - } - - @Override - public boolean hasNext() { - return entryIterator.hasNext(); - } - - @Override - public Object next() { - return entryIterator.next().getKey(); - } - - @Override - public void remove() { - throw new UnsupportedOperationException( "remove() not supported" ); - } - }; - } - - @Override - public String toString() { - CloseableIterator> it = entryIterable.iterator(); - try { - if (!it.hasNext()) { - return "[]"; - } - - StringBuilder sb = new StringBuilder(); - sb.append('['); - for (; ; ) { - CacheEntry entry = it.next(); - sb.append(entry.getKey()); - if (!it.hasNext()) { - return sb.append(']').toString(); - } - sb.append(',').append(' '); - } - } - finally { - it.close(); - } - } - - @Override - public Set toSet() { - HashSet set = new HashSet(); - CloseableIterator it = iterator(); - try { - while (it.hasNext()) { - set.add(it.next()); - } - } - finally { - it.close(); - } - return set; + public Object apply(CacheEntry entry) { + return entry.getKey(); } }; + + Selector VALUE = new Selector() { + @Override + public Object apply(CacheEntry entry) { + return entry.getValue(); + } + }; + + T apply(CacheEntry entry); + } + + private static class CollectableCloseableIterableImpl implements CollectableCloseableIterable { + private final CloseableIterable> entryIterable; + private final Selector selector; + + public CollectableCloseableIterableImpl(CloseableIterable> entryIterable, Selector selector) { + this.entryIterable = entryIterable; + this.selector = selector; + } + + @Override + public void close() { + entryIterable.close(); + } + + @Override + public CloseableIterator iterator() { + final CloseableIterator> entryIterator = entryIterable.iterator(); + return new CloseableIterator() { + @Override + public void close() { + entryIterator.close(); + } + + @Override + public boolean hasNext() { + return entryIterator.hasNext(); + } + + @Override + public T next() { + return selector.apply(entryIterator.next()); + } + + @Override + public void remove() { + throw new UnsupportedOperationException( "remove() not supported" ); + } + }; + } + + @Override + public String toString() { + CloseableIterator> it = entryIterable.iterator(); + try { + if (!it.hasNext()) { + return "[]"; + } + + StringBuilder sb = new StringBuilder(); + sb.append('['); + for (; ; ) { + CacheEntry entry = it.next(); + sb.append(selector.apply(entry)); + if (!it.hasNext()) { + return sb.append(']').toString(); + } + sb.append(',').append(' '); + } + } + finally { + it.close(); + } + } + + @Override + public Set toSet() { + HashSet set = new HashSet(); + CloseableIterator it = iterator(); + try { + while (it.hasNext()) { + set.add(it.next()); + } + } + finally { + it.close(); + } + return set; + } + } + + private static class MapCollectableCloseableIterableImpl implements MapCollectableCloseableIterable { + private final CloseableIterable> entryIterable; + + public MapCollectableCloseableIterableImpl(CloseableIterable> entryIterable) { + this.entryIterable = entryIterable; + } + + @Override + public Map toMap() { + Map map = new HashMap(); + CloseableIterator> it = entryIterable.iterator(); + try { + while (it.hasNext()) { + CacheEntry entry = it.next(); + V value = entry.getValue(); + if (value != null) { + map.put(entry.getKey(), value); + } + } + return map; + } + finally { + it.close(); + } + } + + @Override + public String toString() { + CloseableIterator> it = entryIterable.iterator(); + try { + if (!it.hasNext()) { + return "{}"; + } + + StringBuilder sb = new StringBuilder(); + sb.append('{'); + for (; ; ) { + CacheEntry entry = it.next(); + sb.append(entry.getKey()).append('=').append(entry.getValue()); + if (!it.hasNext()) { + return sb.append('}').toString(); + } + sb.append(',').append(' '); + } + } + finally { + it.close(); + } + } + + @Override + public void close() { + entryIterable.close(); + } + + @Override + public CloseableIterator> iterator() { + return entryIterable.iterator(); + } } } diff --git a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/util/Externalizers.java b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/util/Externalizers.java index f16aabd0f9..c25d6cb317 100644 --- a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/util/Externalizers.java +++ b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/util/Externalizers.java @@ -7,7 +7,6 @@ package org.hibernate.cache.infinispan.util; import org.infinispan.commons.marshall.AdvancedExternalizer; -import org.infinispan.commons.util.Util; import java.io.IOException; import java.io.ObjectInput; @@ -22,9 +21,19 @@ import java.util.UUID; public class Externalizers { public final static int UUID = 1200; + public final static int TOMBSTONE = 1201; + public final static int EXCLUDE_TOMBSTONES_FILTER = 1202; + public final static int TOMBSTONE_UPDATE = 1203; + public final static int FUTURE_UPDATE = 1204; + public final static int VALUE_EXTRACTOR = 1205; public final static AdvancedExternalizer[] ALL_EXTERNALIZERS = new AdvancedExternalizer[] { - new UUIDExternalizer() + new UUIDExternalizer(), + new Tombstone.Externalizer(), + new Tombstone.ExcludeTombstonesFilterExternalizer(), + new TombstoneUpdate.Externalizer(), + new FutureUpdate.Externalizer(), + new FutureUpdate.ValueExtractorExternalizer() }; public static class UUIDExternalizer implements AdvancedExternalizer { diff --git a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/util/FutureUpdate.java b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/util/FutureUpdate.java new file mode 100644 index 0000000000..8a5eb9d3af --- /dev/null +++ b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/util/FutureUpdate.java @@ -0,0 +1,109 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.cache.infinispan.util; + +import org.infinispan.commons.marshall.AdvancedExternalizer; +import org.infinispan.filter.Converter; +import org.infinispan.metadata.Metadata; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.util.Collections; +import java.util.Set; +import java.util.UUID; + +/** + * This value can be overwritten only by an entity with the same uuid + * + * @author Radim Vansa <rvansa@redhat.com> + */ +public class FutureUpdate { + public static final ValueExtractor VALUE_EXTRACTOR = new ValueExtractor(); + + private final UUID uuid; + private final Object value; + + public FutureUpdate(UUID uuid, Object value) { + this.uuid = uuid; + this.value = value; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("FutureUpdate{"); + sb.append("uuid=").append(uuid); + sb.append(", value=").append(value); + sb.append('}'); + return sb.toString(); + } + + public UUID getUuid() { + return uuid; + } + + public Object getValue() { + return value; + } + + public static class Externalizer implements AdvancedExternalizer { + + @Override + public void writeObject(ObjectOutput output, FutureUpdate object) throws IOException { + output.writeLong(object.uuid.getMostSignificantBits()); + output.writeLong(object.uuid.getLeastSignificantBits()); + output.writeObject(object.value); + } + + @Override + public FutureUpdate readObject(ObjectInput input) throws IOException, ClassNotFoundException { + long msb = input.readLong(); + long lsb = input.readLong(); + return new FutureUpdate(new UUID(msb, lsb), input.readObject()); + } + + @Override + public Set> getTypeClasses() { + return Collections.>singleton(FutureUpdate.class); + } + + @Override + public Integer getId() { + return Externalizers.FUTURE_UPDATE; + } + } + + public static class ValueExtractor implements Converter { + private ValueExtractor() {} + + @Override + public Object convert(Object key, Object value, Metadata metadata) { + return value instanceof FutureUpdate ? ((FutureUpdate) value).getValue() : value; + } + } + + public static class ValueExtractorExternalizer implements AdvancedExternalizer { + @Override + public Set> getTypeClasses() { + return Collections.>singleton(ValueExtractor.class); + } + + @Override + public Integer getId() { + return Externalizers.VALUE_EXTRACTOR; + } + + @Override + public void writeObject(ObjectOutput output, ValueExtractor object) throws IOException { + } + + @Override + public ValueExtractor readObject(ObjectInput input) throws IOException, ClassNotFoundException { + return VALUE_EXTRACTOR; + } + } +} diff --git a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/util/InvocationAfterCompletion.java b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/util/InvocationAfterCompletion.java new file mode 100644 index 0000000000..f6eeccb803 --- /dev/null +++ b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/util/InvocationAfterCompletion.java @@ -0,0 +1,78 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.cache.infinispan.util; + +import org.hibernate.HibernateException; +import org.hibernate.jdbc.WorkExecutor; +import org.hibernate.jdbc.WorkExecutorVisitable; +import org.hibernate.resource.transaction.TransactionCoordinator; +import org.infinispan.AdvancedCache; +import org.infinispan.util.logging.Log; +import org.infinispan.util.logging.LogFactory; + +import javax.transaction.Status; +import javax.transaction.Synchronization; + +import java.sql.Connection; +import java.sql.SQLException; + +/** + * @author Radim Vansa <rvansa@redhat.com> + */ +public abstract class InvocationAfterCompletion implements Synchronization { + protected static final Log log = LogFactory.getLog( InvocationAfterCompletion.class ); + + protected final TransactionCoordinator tc; + protected final AdvancedCache cache; + protected final boolean requiresTransaction; + + public InvocationAfterCompletion(TransactionCoordinator tc, AdvancedCache cache, boolean requiresTransaction) { + this.tc = tc; + this.cache = cache; + this.requiresTransaction = requiresTransaction; + } + + @Override + public void beforeCompletion() { + } + + @Override + public void afterCompletion(int status) { + switch (status) { + case Status.STATUS_COMMITTING: + case Status.STATUS_COMMITTED: + invokeIsolated(true); + break; + default: + // it would be nicer to react only on ROLLING_BACK and ROLLED_BACK statuses + // but TransactionCoordinator gives us UNKNOWN on rollback + invokeIsolated(false); + break; + } + } + + protected void invokeIsolated(final boolean success) { + try { + // TODO: isolation without obtaining Connection -> needs HHH-9993 + tc.createIsolationDelegate().delegateWork(new WorkExecutorVisitable() { + @Override + public Void accept(WorkExecutor executor, Connection connection) throws SQLException { + invoke(success, cache); + return null; + } + }, requiresTransaction); + } + catch (HibernateException e) { + // silently fail any exceptions + if (log.isTraceEnabled()) { + log.trace("Exception during query cache update", e); + } + } + } + + protected abstract void invoke(boolean success, AdvancedCache cache); +} diff --git a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/util/Tombstone.java b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/util/Tombstone.java new file mode 100644 index 0000000000..3034daca53 --- /dev/null +++ b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/util/Tombstone.java @@ -0,0 +1,162 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.cache.infinispan.util; + +import org.infinispan.commons.marshall.AdvancedExternalizer; +import org.infinispan.filter.KeyValueFilter; +import org.infinispan.metadata.Metadata; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.util.Arrays; +import java.util.Collections; +import java.util.Set; +import java.util.UUID; + +/** + * @author Radim Vansa <rvansa@redhat.com> + */ +public class Tombstone { + public static final ExcludeTombstonesFilter EXCLUDE_TOMBSTONES = new ExcludeTombstonesFilter(); + + // when release == true and UUID is not found, don't insert anything, because this is a release delta + private final boolean release; + // the format of data is repeated (timestamp, UUID.LSB, UUID.MSB) + private final long[] data; + + public Tombstone(UUID uuid, long timestamp, boolean release) { + this.data = new long[] { timestamp, uuid.getLeastSignificantBits(), uuid.getMostSignificantBits() }; + this.release = release; + } + + private Tombstone(long[] data, boolean release) { + this.data = data; + this.release = release; + } + + public long getLastTimestamp() { + long max = data[0]; + for (int i = 3; i < data.length; i += 3) { + max = Math.max(max, data[i]); + } + return max; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("Tombstone{"); + sb.append("release=").append(release); + sb.append(", data={"); + for (int i = 0; i < data.length; i += 3) { + if (i != 0) { + sb.append(", "); + } + sb.append(new UUID(data[i + 2], data[i + 1])).append('=').append(data[i]); + } + sb.append("} }"); + return sb.toString(); + } + + public Tombstone merge(Tombstone old) { + assert old != null; + assert data.length == 3; + if (release) { + int toRemove = 0; + for (int i = 0; i < old.data.length; i += 3) { + if (old.data[i] < data[0] || (data[1] == old.data[i + 1] && data[2] == old.data[i + 2])) { + toRemove += 3; + } + } + if (old.data.length == toRemove) { + // we want to remove all, but we need to keep at least ourselves + return this; + } + else { + long[] newData = new long[old.data.length - toRemove]; + int j = 0; + for (int i = 0; i < old.data.length; i += 3) { + if (old.data[i] >= data[0] && (data[1] != old.data[i + 1] || data[2] != old.data[i + 2])) { + newData[j] = old.data[i]; + newData[j + 1] = old.data[i + 1]; + newData[j + 2] = old.data[i + 2]; + j += 3; + } + } + return new Tombstone(newData, false); + } + } + else { + long[] newData = Arrays.copyOf(old.data, old.data.length + 3); + System.arraycopy(data, 0, newData, old.data.length, 3); + return new Tombstone(newData, false); + } + } + + public static class Externalizer implements AdvancedExternalizer { + @Override + public Set> getTypeClasses() { + return Collections.>singleton(Tombstone.class); + } + + @Override + public Integer getId() { + return Externalizers.TOMBSTONE; + } + + @Override + public void writeObject(ObjectOutput output, Tombstone tombstone) throws IOException { + output.writeBoolean(tombstone.release); + output.writeInt(tombstone.data.length); + for (int i = 0; i < tombstone.data.length; ++i) { + output.writeLong(tombstone.data[i]); + } + } + + @Override + public Tombstone readObject(ObjectInput input) throws IOException, ClassNotFoundException { + boolean release = input.readBoolean(); + int length = input.readInt(); + long[] data = new long[length]; + for (int i = 0; i < data.length; ++i) { + data[i] = input.readLong(); + } + return new Tombstone(data, release); +// return INSTANCE; + } + } + + public static class ExcludeTombstonesFilter implements KeyValueFilter { + private ExcludeTombstonesFilter() {} + + @Override + public boolean accept(Object key, Object value, Metadata metadata) { + return !(value instanceof Tombstone); + } + } + + public static class ExcludeTombstonesFilterExternalizer implements AdvancedExternalizer { + @Override + public Set> getTypeClasses() { + return Collections.>singleton(ExcludeTombstonesFilter.class); + } + + @Override + public Integer getId() { + return Externalizers.EXCLUDE_TOMBSTONES_FILTER; + } + + @Override + public void writeObject(ObjectOutput output, ExcludeTombstonesFilter object) throws IOException { + } + + @Override + public ExcludeTombstonesFilter readObject(ObjectInput input) throws IOException, ClassNotFoundException { + return EXCLUDE_TOMBSTONES; + } + } +} diff --git a/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/util/TombstoneUpdate.java b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/util/TombstoneUpdate.java new file mode 100644 index 0000000000..754ac45efa --- /dev/null +++ b/hibernate-infinispan/src/main/java/org/hibernate/cache/infinispan/util/TombstoneUpdate.java @@ -0,0 +1,78 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.cache.infinispan.util; + +import org.infinispan.commons.marshall.AdvancedExternalizer; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.util.Collections; +import java.util.Set; + +/** + * @author Radim Vansa <rvansa@redhat.com> + */ +public class TombstoneUpdate { + public static TombstoneUpdate EVICT = new TombstoneUpdate(Long.MIN_VALUE, null); + private long timestamp; + private T value; + + public TombstoneUpdate(long timestamp, T value) { + this.timestamp = timestamp; + this.value = value; + } + + public long getTimestamp() { + return timestamp; + } + + public T getValue() { + return value; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("TombstoneUpdate{"); + sb.append("timestamp=").append(timestamp); + sb.append(", value=").append(value); + sb.append('}'); + return sb.toString(); + } + + public static class Externalizer implements AdvancedExternalizer { + @Override + public Set> getTypeClasses() { + return Collections.>singleton(TombstoneUpdate.class); + } + + @Override + public Integer getId() { + return Externalizers.TOMBSTONE_UPDATE; + } + + @Override + public void writeObject(ObjectOutput output, TombstoneUpdate object) throws IOException { + output.writeObject(object.getValue()); + if (object.getValue() != null) { + output.writeLong(object.getTimestamp()); + } + } + + @Override + public TombstoneUpdate readObject(ObjectInput input) throws IOException, ClassNotFoundException { + Object value = input.readObject(); + if (value != null) { + long timestamp = input.readLong(); + return new TombstoneUpdate(timestamp, value); + } + else { + return EVICT; + } + } + } +} diff --git a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/AbstractNonFunctionalTest.java b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/AbstractNonFunctionalTest.java index 8f5d6fc98b..6ea59854cf 100644 --- a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/AbstractNonFunctionalTest.java +++ b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/AbstractNonFunctionalTest.java @@ -6,6 +6,7 @@ */ package org.hibernate.test.cache.infinispan; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.Callable; @@ -23,6 +24,7 @@ import org.hibernate.test.cache.infinispan.util.InfinispanTestingSetup; import org.hibernate.test.cache.infinispan.util.TestInfinispanRegionFactory; import org.hibernate.testing.junit4.CustomParameterized; import org.infinispan.Cache; +import org.infinispan.configuration.cache.CacheMode; import org.jboss.logging.Logger; import org.junit.After; import org.junit.Before; @@ -48,19 +50,35 @@ public abstract class AbstractNonFunctionalTest extends org.hibernate.testing.ju @Rule public InfinispanTestingSetup infinispanTestIdentifier = new InfinispanTestingSetup(); + @CustomParameterized.Order(0) @Parameterized.Parameters(name = "{0}") - public static List getJtaParameters() { + public List getJtaParameters() { return Arrays.asList( new Object[] { "JTA", BatchModeJtaPlatform.class }, new Object[] { "non-JTA", null }); } - @Parameterized.Parameter(value = 0) + @CustomParameterized.Order(1) + @Parameterized.Parameters(name = "{2}") + public List getCacheModeParameters() { + ArrayList modes = new ArrayList<>(); + modes.add(new Object[] { CacheMode.INVALIDATION_SYNC }); + if (!useTransactionalCache()) { + modes.add(new Object[]{CacheMode.REPL_SYNC}); + modes.add(new Object[]{CacheMode.DIST_SYNC}); + } + return modes; + } + + @Parameterized.Parameter(0) public String mode; - @Parameterized.Parameter(value = 1) + @Parameterized.Parameter(1) public Class jtaPlatform; + @Parameterized.Parameter(2) + public CacheMode cacheMode; + public static final String REGION_PREFIX = "test"; private static final String PREFER_IPV4STACK = "java.net.preferIPv4Stack"; @@ -160,10 +178,16 @@ public abstract class AbstractNonFunctionalTest extends org.hibernate.testing.ju protected StandardServiceRegistryBuilder createStandardServiceRegistryBuilder() { final StandardServiceRegistryBuilder ssrb = CacheTestUtil.buildBaselineStandardServiceRegistryBuilder( REGION_PREFIX, getRegionFactoryClass(), true, false, jtaPlatform); + ssrb.applySetting(TestInfinispanRegionFactory.TRANSACTIONAL, useTransactionalCache()); + ssrb.applySetting(TestInfinispanRegionFactory.CACHE_MODE, cacheMode); return ssrb; } protected Class getRegionFactoryClass() { return TestInfinispanRegionFactory.class; } + + protected boolean useTransactionalCache() { + return false; + } } \ No newline at end of file diff --git a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/AbstractRegionAccessStrategyTest.java b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/AbstractRegionAccessStrategyTest.java index b47910e268..993e516550 100644 --- a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/AbstractRegionAccessStrategyTest.java +++ b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/AbstractRegionAccessStrategyTest.java @@ -11,13 +11,19 @@ import org.hibernate.cache.spi.CacheDataDescription; import org.hibernate.cache.spi.access.AccessType; import org.hibernate.cache.spi.access.RegionAccessStrategy; import org.hibernate.cache.spi.access.SoftLock; +import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess; +import org.hibernate.engine.jdbc.spi.JdbcServices; +import org.hibernate.engine.jdbc.spi.SqlExceptionHelper; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.transaction.internal.TransactionImpl; import org.hibernate.internal.util.compare.ComparableComparator; +import org.hibernate.resource.jdbc.spi.JdbcSessionContext; +import org.hibernate.resource.jdbc.spi.JdbcSessionOwner; import org.hibernate.resource.transaction.TransactionCoordinator; import org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorBuilderImpl; import org.hibernate.resource.transaction.backend.jdbc.spi.JdbcResourceTransactionAccess; import org.hibernate.resource.transaction.spi.TransactionCoordinatorOwner; +import org.hibernate.service.ServiceRegistry; import org.hibernate.test.cache.infinispan.util.BatchModeJtaPlatform; import org.hibernate.test.cache.infinispan.util.BatchModeTransactionCoordinator; import org.hibernate.test.cache.infinispan.util.InfinispanTestingSetup; @@ -35,6 +41,8 @@ import javax.transaction.RollbackException; import javax.transaction.SystemException; import javax.transaction.TransactionManager; +import java.sql.Connection; +import java.sql.SQLException; import java.util.Arrays; import java.util.concurrent.CountDownLatch; @@ -122,8 +130,26 @@ public abstract class AbstractRegionAccessStrategyTest pferCallable = new Callable() { public Void call() throws Exception { @@ -142,7 +146,9 @@ public abstract class AbstractCollectionRegionAccessStrategyTest extends Lock lock = super.acquirePutFromLoadLock(session, key, txTimestamp); try { removeLatch.countDown(); - pferLatch.await( 2, TimeUnit.SECONDS ); + // the remove should be blocked because the putFromLoad has been acquired + // and the remove can continue only after we've inserted the entry + assertFalse(pferLatch.await( 2, TimeUnit.SECONDS ) ); } catch (InterruptedException e) { log.debug( "Interrupted" ); diff --git a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/collection/CollectionRegionAccessExtraAPITest.java b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/collection/CollectionRegionAccessExtraAPITest.java index e79672617e..1bd6eabf51 100644 --- a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/collection/CollectionRegionAccessExtraAPITest.java +++ b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/collection/CollectionRegionAccessExtraAPITest.java @@ -6,7 +6,6 @@ */ package org.hibernate.test.cache.infinispan.collection; -import org.hibernate.cache.spi.RegionFactory; import org.hibernate.cache.spi.access.AccessType; import org.hibernate.cache.spi.access.CollectionRegionAccessStrategy; import org.hibernate.test.cache.infinispan.AbstractExtraAPITest; @@ -32,8 +31,8 @@ public abstract class CollectionRegionAccessExtraAPITest extends AbstractExtraAP } @Override - protected Class getRegionFactoryClass() { - return TestInfinispanRegionFactory.Transactional.class; + protected boolean useTransactionalCache() { + return true; } } diff --git a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/collection/CollectionRegionReadOnlyAccessTest.java b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/collection/CollectionRegionReadOnlyAccessTest.java index 25718b6851..a96dc23952 100644 --- a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/collection/CollectionRegionReadOnlyAccessTest.java +++ b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/collection/CollectionRegionReadOnlyAccessTest.java @@ -16,24 +16,15 @@ import static org.junit.Assert.assertTrue; * * @author Brian Stansberry */ -public abstract class CollectionRegionReadOnlyAccessTest extends AbstractCollectionRegionAccessStrategyTest { +public class CollectionRegionReadOnlyAccessTest extends AbstractCollectionRegionAccessStrategyTest { @Override protected AccessType getAccessType() { return AccessType.READ_ONLY; } - /** - * Tests READ_ONLY access when invalidation is used. - * - * @author Galder Zamarreño - * @since 3.5 - */ - public static class Invalidation extends CollectionRegionReadOnlyAccessTest { - @Override - public void testCacheConfiguration() { - assertFalse(isTransactional()); - assertTrue( "Using Invalidation", isUsingInvalidation() ); - assertTrue( isSynchronous() ); - } - } + @Override + public void testCacheConfiguration() { + assertFalse(isTransactional()); + assertTrue( isSynchronous() ); + } } diff --git a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/collection/CollectionRegionReadWriteAccessTest.java b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/collection/CollectionRegionReadWriteAccessTest.java index 0a8004504f..5efef250ae 100644 --- a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/collection/CollectionRegionReadWriteAccessTest.java +++ b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/collection/CollectionRegionReadWriteAccessTest.java @@ -10,18 +10,15 @@ import static org.junit.Assert.assertTrue; * * @author Radim Vansa <rvansa@redhat.com> */ -public abstract class CollectionRegionReadWriteAccessTest extends AbstractCollectionRegionAccessStrategyTest { +public class CollectionRegionReadWriteAccessTest extends AbstractCollectionRegionAccessStrategyTest { @Override protected AccessType getAccessType() { return AccessType.READ_WRITE; } - public static class Invalidation extends CollectionRegionReadWriteAccessTest { - @Override - public void testCacheConfiguration() { - assertFalse(isTransactional()); - assertTrue(isUsingInvalidation()); - assertTrue(isSynchronous()); - } + @Override + public void testCacheConfiguration() { + assertFalse(isTransactional()); + assertTrue(isSynchronous()); } } diff --git a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/collection/CollectionRegionTransactionalAccessTest.java b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/collection/CollectionRegionTransactionalAccessTest.java index 02783499f6..5f046983b6 100644 --- a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/collection/CollectionRegionTransactionalAccessTest.java +++ b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/collection/CollectionRegionTransactionalAccessTest.java @@ -5,7 +5,6 @@ * See the lgpl.txt file in the root directory or . */ package org.hibernate.test.cache.infinispan.collection; -import org.hibernate.cache.spi.RegionFactory; import org.hibernate.cache.spi.access.AccessType; import org.hibernate.test.cache.infinispan.util.TestInfinispanRegionFactory; @@ -16,30 +15,20 @@ import static org.junit.Assert.assertTrue; * * @author Brian Stansberry */ -public abstract class CollectionRegionTransactionalAccessTest extends AbstractCollectionRegionAccessStrategyTest { +public class CollectionRegionTransactionalAccessTest extends AbstractCollectionRegionAccessStrategyTest { @Override protected AccessType getAccessType() { return AccessType.TRANSACTIONAL; } @Override - protected Class getRegionFactoryClass() { - return TestInfinispanRegionFactory.Transactional.class; + protected boolean useTransactionalCache() { + return true; } - /** - * InvalidatedTransactionalTestCase. - * - * @author Galder Zamarreño - * @since 3.5 - */ - public static class Invalidation extends CollectionRegionTransactionalAccessTest { - @Override - public void testCacheConfiguration() { - assertTrue("Transactions", isTransactional()); - assertTrue("Using Invalidation", isUsingInvalidation()); - assertTrue("Synchronous mode", isSynchronous()); - } + @Override + public void testCacheConfiguration() { + assertTrue("Transactions", isTransactional()); + assertTrue("Synchronous mode", isSynchronous()); } - } diff --git a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/entity/AbstractEntityRegionAccessStrategyTest.java b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/entity/AbstractEntityRegionAccessStrategyTest.java index 0f675c1ef3..8a721af30e 100644 --- a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/entity/AbstractEntityRegionAccessStrategyTest.java +++ b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/entity/AbstractEntityRegionAccessStrategyTest.java @@ -52,7 +52,8 @@ public abstract class AbstractEntityRegionAccessStrategyTest extends } @Test - public abstract void testCacheConfiguration(); + public void testCacheConfiguration() { + } @Test public void testGetRegion() { @@ -396,5 +397,4 @@ public abstract class AbstractEntityRegionAccessStrategyTest extends long txTimestamp = System.currentTimeMillis(); assertEquals("Correct node1 value", VALUE2, localAccessStrategy.get(null, KEY, txTimestamp)); } - } diff --git a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/entity/EntityRegionExtraAPITest.java b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/entity/EntityRegionExtraAPITest.java index d94542e9f6..9f106868c0 100644 --- a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/entity/EntityRegionExtraAPITest.java +++ b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/entity/EntityRegionExtraAPITest.java @@ -6,7 +6,6 @@ */ package org.hibernate.test.cache.infinispan.entity; -import org.hibernate.cache.spi.RegionFactory; import org.hibernate.cache.spi.access.AccessType; import org.hibernate.cache.spi.access.EntityRegionAccessStrategy; import org.hibernate.test.cache.infinispan.AbstractExtraAPITest; @@ -60,8 +59,8 @@ public class EntityRegionExtraAPITest extends AbstractExtraAPITest getRegionFactoryClass() { - return TestInfinispanRegionFactory.Transactional.class; + protected boolean useTransactionalCache() { + return true; } } diff --git a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/entity/EntityRegionReadOnlyAccessTest.java b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/entity/EntityRegionReadOnlyAccessTest.java index 005e7797cd..8e201fa6fb 100644 --- a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/entity/EntityRegionReadOnlyAccessTest.java +++ b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/entity/EntityRegionReadOnlyAccessTest.java @@ -24,7 +24,7 @@ import static org.junit.Assert.assertTrue; * @author Galder Zamarreño * @since 3.5 */ -public abstract class EntityRegionReadOnlyAccessTest extends AbstractEntityRegionAccessStrategyTest { +public class EntityRegionReadOnlyAccessTest extends AbstractEntityRegionAccessStrategyTest { @Override protected AccessType getAccessType() { @@ -66,12 +66,4 @@ public abstract class EntityRegionReadOnlyAccessTest extends AbstractEntityRegio @Override public void testContestedPutFromLoad() throws Exception { } - - public static class Invalidation extends EntityRegionReadOnlyAccessTest { - @Test - @Override - public void testCacheConfiguration() { - assertTrue("Using Invalidation", isUsingInvalidation()); - } - } } diff --git a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/entity/EntityRegionTransactionalAccessTest.java b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/entity/EntityRegionTransactionalAccessTest.java index 4bb3e502a2..950a320a69 100644 --- a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/entity/EntityRegionTransactionalAccessTest.java +++ b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/entity/EntityRegionTransactionalAccessTest.java @@ -6,21 +6,10 @@ */ package org.hibernate.test.cache.infinispan.entity; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import junit.framework.AssertionFailedError; -import org.hibernate.cache.spi.RegionFactory; import org.hibernate.cache.spi.access.AccessType; -import org.hibernate.cache.spi.access.SoftLock; -import org.hibernate.test.cache.infinispan.util.TestInfinispanRegionFactory; -import org.hibernate.test.cache.infinispan.util.TestingKeyFactory; -import org.infinispan.transaction.tm.BatchModeTransactionManager; import org.jboss.logging.Logger; import org.junit.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; /** @@ -29,7 +18,7 @@ import static org.junit.Assert.assertTrue; * @author Galder Zamarreño * @since 3.5 */ -public abstract class EntityRegionTransactionalAccessTest extends AbstractEntityRegionAccessStrategyTest { +public class EntityRegionTransactionalAccessTest extends AbstractEntityRegionAccessStrategyTest { private static final Logger log = Logger.getLogger( EntityRegionTransactionalAccessTest.class ); @Override @@ -38,20 +27,14 @@ public abstract class EntityRegionTransactionalAccessTest extends AbstractEntity } @Override - protected Class getRegionFactoryClass() { - return TestInfinispanRegionFactory.Transactional.class; + protected boolean useTransactionalCache() { + return true; } - /** - * @author Galder Zamarreño - */ - public static class Invalidation extends EntityRegionTransactionalAccessTest { - @Test - @Override - public void testCacheConfiguration() { - assertTrue(isTransactional()); - assertTrue("Using Invalidation", isUsingInvalidation()); - assertTrue("Synchronous mode", isSynchronous()); - } + @Test + @Override + public void testCacheConfiguration() { + assertTrue(isTransactional()); + assertTrue("Synchronous mode", isSynchronous()); } } diff --git a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/AbstractFunctionalTest.java b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/AbstractFunctionalTest.java index 3e11e35331..4348361203 100644 --- a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/AbstractFunctionalTest.java +++ b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/AbstractFunctionalTest.java @@ -6,7 +6,7 @@ */ package org.hibernate.test.cache.infinispan.functional; -import java.util.Arrays; +import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -30,8 +30,7 @@ import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; import org.hibernate.test.cache.infinispan.tm.JtaPlatformImpl; import org.hibernate.testing.junit4.CustomParameterized; -import org.infinispan.util.logging.Log; -import org.infinispan.util.logging.LogFactory; +import org.infinispan.configuration.cache.CacheMode; import org.junit.ClassRule; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -42,12 +41,16 @@ import org.junit.runners.Parameterized; */ @RunWith(CustomParameterized.class) public abstract class AbstractFunctionalTest extends BaseNonConfigCoreFunctionalTestCase { - private static final Log log = LogFactory.getLog( AbstractFunctionalTest.class ); - - protected static final Object[] TRANSACTIONAL = new Object[]{"transactional", JtaPlatformImpl.class, JtaTransactionCoordinatorBuilderImpl.class, XaConnectionProvider.class, AccessType.TRANSACTIONAL, TestInfinispanRegionFactory.Transactional.class}; - protected static final Object[] READ_WRITE = new Object[]{"read-write", null, JdbcResourceLocalTransactionCoordinatorBuilderImpl.class, null, AccessType.READ_WRITE, TestInfinispanRegionFactory.class}; - protected static final Object[] READ_ONLY = new Object[]{"read-only", null, JdbcResourceLocalTransactionCoordinatorBuilderImpl.class, null, AccessType.READ_ONLY, TestInfinispanRegionFactory.class}; + protected static final Object[] TRANSACTIONAL = new Object[]{"transactional", JtaPlatformImpl.class, JtaTransactionCoordinatorBuilderImpl.class, XaConnectionProvider.class, AccessType.TRANSACTIONAL, true, CacheMode.INVALIDATION_SYNC }; + protected static final Object[] READ_WRITE_INVALIDATION = new Object[]{"read-write", null, JdbcResourceLocalTransactionCoordinatorBuilderImpl.class, null, AccessType.READ_WRITE, false, CacheMode.INVALIDATION_SYNC }; + protected static final Object[] READ_ONLY_INVALIDATION = new Object[]{"read-only", null, JdbcResourceLocalTransactionCoordinatorBuilderImpl.class, null, AccessType.READ_ONLY, false, CacheMode.INVALIDATION_SYNC }; + protected static final Object[] READ_WRITE_REPLICATED = new Object[]{"read-write", null, JdbcResourceLocalTransactionCoordinatorBuilderImpl.class, null, AccessType.READ_WRITE, false, CacheMode.REPL_SYNC }; + protected static final Object[] READ_ONLY_REPLICATED = new Object[]{"read-only", null, JdbcResourceLocalTransactionCoordinatorBuilderImpl.class, null, AccessType.READ_ONLY, false, CacheMode.REPL_SYNC }; + protected static final Object[] READ_WRITE_DISTRIBUTED = new Object[]{"read-write", null, JdbcResourceLocalTransactionCoordinatorBuilderImpl.class, null, AccessType.READ_WRITE, false, CacheMode.DIST_SYNC }; + protected static final Object[] READ_ONLY_DISTRIBUTED = new Object[]{"read-only", null, JdbcResourceLocalTransactionCoordinatorBuilderImpl.class, null, AccessType.READ_ONLY, false, CacheMode.DIST_SYNC }; + // We need to use @ClassRule here since in @BeforeClassOnce startUp we're preparing the session factory, + // constructing CacheManager along - and there we check that the test has the name already set @ClassRule public static final InfinispanTestingSetup infinispanTestIdentifier = new InfinispanTestingSetup(); @@ -67,13 +70,35 @@ public abstract class AbstractFunctionalTest extends BaseNonConfigCoreFunctional public AccessType accessType; @Parameterized.Parameter(value = 5) - public Class regionFactoryClass; + public boolean useTransactionalCache; + + @Parameterized.Parameter(value = 6) + public CacheMode cacheMode; protected boolean useJta; - @Parameterized.Parameters(name = "{0}") + @CustomParameterized.Order(0) + @Parameterized.Parameters(name = "{0}, {6}") public abstract List getParameters(); + public List getParameters(boolean tx, boolean rw, boolean ro) { + ArrayList parameters = new ArrayList<>(); + if (tx) { + parameters.add(TRANSACTIONAL); + } + if (rw) { + parameters.add(READ_WRITE_INVALIDATION); + parameters.add(READ_WRITE_REPLICATED); + parameters.add(READ_WRITE_DISTRIBUTED); + } + if (ro) { + parameters.add(READ_ONLY_INVALIDATION); + parameters.add(READ_ONLY_REPLICATED); + parameters.add(READ_ONLY_DISTRIBUTED); + } + return parameters; + } + @BeforeClassOnce public void setUseJta() { useJta = jtaPlatformClass != null; @@ -93,6 +118,10 @@ public abstract class AbstractFunctionalTest extends BaseNonConfigCoreFunctional return accessType.getExternalName(); } + protected Class getRegionFactoryClass() { + return TestInfinispanRegionFactory.class; + } + protected boolean getUseQueryCache() { return true; } @@ -105,7 +134,9 @@ public abstract class AbstractFunctionalTest extends BaseNonConfigCoreFunctional settings.put( Environment.USE_SECOND_LEVEL_CACHE, "true" ); settings.put( Environment.GENERATE_STATISTICS, "true" ); settings.put( Environment.USE_QUERY_CACHE, String.valueOf( getUseQueryCache() ) ); - settings.put( Environment.CACHE_REGION_FACTORY, regionFactoryClass.getName() ); + settings.put( Environment.CACHE_REGION_FACTORY, getRegionFactoryClass().getName() ); + settings.put( TestInfinispanRegionFactory.TRANSACTIONAL, useTransactionalCache ); + settings.put( TestInfinispanRegionFactory.CACHE_MODE, cacheMode); if ( jtaPlatformClass != null ) { settings.put( AvailableSettings.JTA_PLATFORM, jtaPlatformClass.getName() ); diff --git a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/BulkOperationsTest.java b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/BulkOperationsTest.java index f22217d57d..19a34a7310 100644 --- a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/BulkOperationsTest.java +++ b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/BulkOperationsTest.java @@ -12,21 +12,13 @@ import java.util.List; import java.util.Set; import org.hibernate.FlushMode; -import org.hibernate.Session; -import org.hibernate.cache.spi.access.AccessType; -import org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorBuilderImpl; -import org.hibernate.resource.transaction.backend.jta.internal.JtaTransactionCoordinatorBuilderImpl; import org.hibernate.stat.SecondLevelCacheStatistics; -import org.hibernate.test.cache.infinispan.tm.JtaPlatformImpl; -import org.hibernate.test.cache.infinispan.tm.XaConnectionProvider; import org.hibernate.test.cache.infinispan.util.InfinispanTestingSetup; import org.hibernate.test.cache.infinispan.functional.entities.Contact; import org.hibernate.test.cache.infinispan.functional.entities.Customer; -import org.hibernate.test.cache.infinispan.util.TestInfinispanRegionFactory; import org.junit.ClassRule; import org.junit.Test; -import org.junit.runners.Parameterized; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -41,7 +33,7 @@ import static org.junit.Assert.assertNull; public class BulkOperationsTest extends SingleNodeTest { @Override public List getParameters() { - return Arrays.asList(TRANSACTIONAL, READ_WRITE); + return getParameters(true, true, false); } @ClassRule diff --git a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/ConcurrentWriteTest.java b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/ConcurrentWriteTest.java index 20ced9fe23..bf40bc45e3 100644 --- a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/ConcurrentWriteTest.java +++ b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/ConcurrentWriteTest.java @@ -63,7 +63,7 @@ public class ConcurrentWriteTest extends SingleNodeTest { @Override public List getParameters() { - return Arrays.asList(TRANSACTIONAL, READ_WRITE); + return getParameters(true, true, false); } @Override @@ -91,19 +91,25 @@ public class ConcurrentWriteTest extends SingleNodeTest { public void testSingleUser() throws Exception { // setup sessionFactory().getStatistics().clear(); + // wait a while to make sure that timestamp comparison works after invalidateRegion + Thread.sleep(1); + Customer customer = createCustomer( 0 ); final Integer customerId = customer.getId(); getCustomerIDs().add( customerId ); + // wait a while to make sure that timestamp comparison works after collection remove (during insert) + Thread.sleep(1); + assertNull( "contact exists despite not being added", getFirstContact( customerId ) ); // check that cache was hit SecondLevelCacheStatistics customerSlcs = sessionFactory() .getStatistics() .getSecondLevelCacheStatistics( Customer.class.getName() ); - assertEquals( customerSlcs.getPutCount(), 1 ); - assertEquals( customerSlcs.getElementCountInMemory(), 1 ); - assertEquals( customerSlcs.getEntries().size(), 1 ); + assertEquals( 1, customerSlcs.getPutCount() ); + assertEquals( 1, customerSlcs.getElementCountInMemory() ); + assertEquals( 1, customerSlcs.getEntries().size() ); log.infof( "Add contact to customer {0}", customerId ); SecondLevelCacheStatistics contactsCollectionSlcs = sessionFactory() @@ -155,6 +161,7 @@ public class ConcurrentWriteTest extends SingleNodeTest { for ( Future future : futures ) { future.get(); } + executor.shutdown(); log.info( "All future gets checked" ); } catch (Throwable t) { diff --git a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/EqualityTest.java b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/EqualityTest.java index 2e3e6c4fc6..9dbb400079 100644 --- a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/EqualityTest.java +++ b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/EqualityTest.java @@ -21,7 +21,7 @@ import static org.junit.Assert.assertTrue; public class EqualityTest extends SingleNodeTest { @Override public List getParameters() { - return Arrays.asList(TRANSACTIONAL, READ_WRITE, READ_ONLY); + return getParameters(true, true, true); } @Override diff --git a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/InvalidationTest.java b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/InvalidationTest.java new file mode 100644 index 0000000000..6864c0fbcd --- /dev/null +++ b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/InvalidationTest.java @@ -0,0 +1,175 @@ +package org.hibernate.test.cache.infinispan.functional; + +import org.hibernate.PessimisticLockException; +import org.hibernate.cache.infinispan.InfinispanRegionFactory; +import org.hibernate.cache.infinispan.entity.EntityRegionImpl; +import org.hibernate.cache.spi.Region; +import org.hibernate.test.cache.infinispan.functional.entities.Item; +import org.hibernate.testing.TestForIssue; +import org.infinispan.AdvancedCache; +import org.infinispan.commands.read.GetKeyValueCommand; +import org.infinispan.context.InvocationContext; +import org.infinispan.interceptors.base.BaseCustomInterceptor; +import org.infinispan.util.logging.Log; +import org.infinispan.util.logging.LogFactory; +import org.junit.Test; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.Phaser; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +/** + * Tests specific to invalidation mode caches + * + * @author Radim Vansa <rvansa@redhat.com> + */ +public class InvalidationTest extends SingleNodeTest { + static final Log log = LogFactory.getLog(ReadOnlyTest.class); + + @Override + public List getParameters() { + return Arrays.asList(TRANSACTIONAL, READ_WRITE_INVALIDATION); + } + + @Test + @TestForIssue(jiraKey = "HHH-9868") + public void testConcurrentRemoveAndPutFromLoad() throws Exception { + Region region = sessionFactory().getSecondLevelCacheRegion(Item.class.getName()); + AdvancedCache entityCache = ((EntityRegionImpl) region).getCache(); + + final Item item = new Item( "chris", "Chris's Item" ); + withTxSession(s -> { + s.persist(item); + }); + + Phaser deletePhaser = new Phaser(2); + Phaser getPhaser = new Phaser(2); + HookInterceptor hook = new HookInterceptor(); + + AdvancedCache pendingPutsCache = entityCache.getCacheManager().getCache( + entityCache.getName() + "-" + InfinispanRegionFactory.PENDING_PUTS_CACHE_NAME).getAdvancedCache(); + pendingPutsCache.addInterceptor(hook, 0); + AtomicBoolean getThreadBlockedInDB = new AtomicBoolean(false); + + Thread deleteThread = new Thread(() -> { + try { + withTxSession(s -> { + Item loadedItem = s.get(Item.class, item.getId()); + assertNotNull(loadedItem); + arriveAndAwait(deletePhaser, 2000); + arriveAndAwait(deletePhaser, 2000); + log.trace("Item loaded"); + s.delete(loadedItem); + s.flush(); + log.trace("Item deleted"); + // start get-thread here + arriveAndAwait(deletePhaser, 2000); + // we need longer timeout since in non-MVCC DBs the get thread + // can be blocked + arriveAndAwait(deletePhaser, 4000); + }); + } catch (Exception e) { + throw new RuntimeException(e); + } + }, "delete-thread"); + Thread getThread = new Thread(() -> { + try { + withTxSession(s -> { + // DB load should happen before the record is deleted, + // putFromLoad should happen after deleteThread ends + Item loadedItem = s.get(Item.class, item.getId()); + if (getThreadBlockedInDB.get()) { + assertNull(loadedItem); + } else { + assertNotNull(loadedItem); + } + }); + } catch (PessimisticLockException e) { + // If we end up here, database locks guard us against situation tested + // in this case and HHH-9868 cannot happen. + // (delete-thread has ITEMS table write-locked and we try to acquire read-lock) + try { + arriveAndAwait(getPhaser, 2000); + arriveAndAwait(getPhaser, 2000); + } catch (Exception e1) { + throw new RuntimeException(e1); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + }, "get-thread"); + + deleteThread.start(); + // deleteThread loads the entity + arriveAndAwait(deletePhaser, 2000); + withTx(() -> { + sessionFactory().getCache().evictEntity(Item.class, item.getId()); + assertFalse(sessionFactory().getCache().containsEntity(Item.class, item.getId())); + return null; + }); + arriveAndAwait(deletePhaser, 2000); + // delete thread invalidates PFER + arriveAndAwait(deletePhaser, 2000); + // get thread gets the entity from DB + hook.block(getPhaser, getThread); + getThread.start(); + try { + arriveAndAwait(getPhaser, 2000); + } catch (TimeoutException e) { + getThreadBlockedInDB.set(true); + } + arriveAndAwait(deletePhaser, 2000); + // delete thread finishes the remove from DB and cache + deleteThread.join(); + hook.unblock(); + arriveAndAwait(getPhaser, 2000); + // get thread puts the entry into cache + getThread.join(); + + withTxSession(s -> { + Item loadedItem = s.get(Item.class, item.getId()); + assertNull(loadedItem); + }); + } + + protected static void arriveAndAwait(Phaser phaser, int timeout) throws TimeoutException, InterruptedException { + phaser.awaitAdvanceInterruptibly(phaser.arrive(), timeout, TimeUnit.MILLISECONDS); + } + + private static class HookInterceptor extends BaseCustomInterceptor { + Phaser phaser; + Thread thread; + + public synchronized void block(Phaser phaser, Thread thread) { + this.phaser = phaser; + this.thread = thread; + } + + public synchronized void unblock() { + phaser = null; + thread = null; + } + + @Override + public Object visitGetKeyValueCommand(InvocationContext ctx, GetKeyValueCommand command) throws Throwable { + Phaser phaser; + Thread thread; + synchronized (this) { + phaser = this.phaser; + thread = this.thread; + } + if (phaser != null && Thread.currentThread() == thread) { + arriveAndAwait(phaser, 2000); + arriveAndAwait(phaser, 2000); + } + return super.visitGetKeyValueCommand(ctx, command); + } + } +} diff --git a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/JndiRegionFactoryTest.java b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/JndiRegionFactoryTest.java index 99d0285df0..487b0d0f05 100644 --- a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/JndiRegionFactoryTest.java +++ b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/JndiRegionFactoryTest.java @@ -20,11 +20,10 @@ import javax.naming.StringRefAddr; import org.hibernate.boot.registry.StandardServiceRegistry; import org.hibernate.cache.infinispan.InfinispanRegionFactory; import org.hibernate.cache.infinispan.JndiInfinispanRegionFactory; -import org.hibernate.cache.spi.access.AccessType; +import org.hibernate.cache.spi.RegionFactory; import org.hibernate.cfg.Environment; import org.hibernate.engine.config.spi.ConfigurationService; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorBuilderImpl; import org.hibernate.stat.Statistics; import org.hibernate.test.cache.infinispan.functional.entities.Item; @@ -41,7 +40,6 @@ import org.jboss.util.naming.NonSerializableFactory; import org.jnp.server.Main; import org.jnp.server.SingletonNamingServer; -import org.junit.runners.Parameterized; import static org.junit.Assert.assertEquals; @@ -59,7 +57,12 @@ public class JndiRegionFactoryTest extends SingleNodeTest { @Override public List getParameters() { - return Collections.singletonList(new Object[]{"read-write", null, JdbcResourceLocalTransactionCoordinatorBuilderImpl.class, null, AccessType.READ_WRITE, JndiInfinispanRegionFactory.class}); + return Collections.singletonList(READ_WRITE_INVALIDATION); + } + + @Override + protected Class getRegionFactoryClass() { + return JndiInfinispanRegionFactory.class; } @Override diff --git a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/MultiTenancyTest.java b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/MultiTenancyTest.java index f4756c0462..1082654305 100644 --- a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/MultiTenancyTest.java +++ b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/MultiTenancyTest.java @@ -36,7 +36,7 @@ public class MultiTenancyTest extends SingleNodeTest { @Override public List getParameters() { - return Collections.singletonList(READ_ONLY); + return Collections.singletonList(READ_ONLY_INVALIDATION); } @Override diff --git a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/NoTenancyTest.java b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/NoTenancyTest.java index 5d53d7fc92..5e4f03169e 100644 --- a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/NoTenancyTest.java +++ b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/NoTenancyTest.java @@ -20,7 +20,7 @@ import static org.junit.Assert.assertNotNull; public class NoTenancyTest extends SingleNodeTest { @Override public List getParameters() { - return Collections.singletonList(READ_ONLY); + return Collections.singletonList(READ_ONLY_INVALIDATION); } @Test @@ -42,5 +42,4 @@ public class NoTenancyTest extends SingleNodeTest { assertEquals(1, localCache.size()); assertEquals(sessionFactory().getClassMetadata(Item.class).getIdentifierType().getReturnedClass(), keys.iterator().next().getClass()); } - } diff --git a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/ReadOnlyTest.java b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/ReadOnlyTest.java index 0bc67b8e9a..3068730e37 100644 --- a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/ReadOnlyTest.java +++ b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/ReadOnlyTest.java @@ -6,34 +6,16 @@ */ package org.hibernate.test.cache.infinispan.functional; -import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.concurrent.Phaser; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -import org.hibernate.PessimisticLockException; -import org.hibernate.cache.infinispan.InfinispanRegionFactory; -import org.hibernate.cache.infinispan.entity.EntityRegionImpl; -import org.hibernate.cache.spi.Region; import org.hibernate.stat.SecondLevelCacheStatistics; import org.hibernate.stat.Statistics; import org.hibernate.test.cache.infinispan.functional.entities.Item; -import org.hibernate.testing.TestForIssue; -import org.infinispan.AdvancedCache; -import org.infinispan.commands.read.GetKeyValueCommand; -import org.infinispan.context.InvocationContext; -import org.infinispan.interceptors.base.BaseCustomInterceptor; import org.infinispan.util.logging.Log; import org.infinispan.util.logging.LogFactory; import org.junit.Test; -import org.junit.runners.Parameterized; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; /** * Parent tests for both transactional and @@ -47,7 +29,7 @@ public class ReadOnlyTest extends SingleNodeTest { @Override public List getParameters() { - return Collections.singletonList(READ_ONLY); + return getParameters(false, false, true); } @Test @@ -106,127 +88,4 @@ public class ReadOnlyTest extends SingleNodeTest { s.delete(found); }); } - - @Test - @TestForIssue(jiraKey = "HHH-9868") - public void testConcurrentRemoveAndPutFromLoad() throws Exception { - final Item item = new Item( "chris", "Chris's Item" ); - withTxSession(s -> { - s.persist(item); - }); - Region region = sessionFactory().getSecondLevelCacheRegion(Item.class.getName()); - - Phaser deletePhaser = new Phaser(2); - Phaser getPhaser = new Phaser(2); - HookInterceptor hook = new HookInterceptor(); - - AdvancedCache entityCache = ((EntityRegionImpl) region).getCache(); - AdvancedCache pendingPutsCache = entityCache.getCacheManager().getCache( - entityCache.getName() + "-" + InfinispanRegionFactory.PENDING_PUTS_CACHE_NAME).getAdvancedCache(); - pendingPutsCache.addInterceptor(hook, 0); - - Thread deleteThread = new Thread(() -> { - try { - withTxSession(s -> { - Item loadedItem = s.get(Item.class, item.getId()); - assertNotNull(loadedItem); - arriveAndAwait(deletePhaser); - arriveAndAwait(deletePhaser); - log.trace("Item loaded"); - s.delete(loadedItem); - s.flush(); - log.trace("Item deleted"); - // start get-thread here - arriveAndAwait(deletePhaser); - arriveAndAwait(deletePhaser); - }); - } catch (Exception e) { - throw new RuntimeException(e); - } - }, "delete-thread"); - Thread getThread = new Thread(() -> { - try { - withTxSession(s -> { - // DB load should happen before the record is deleted, - // putFromLoad should happen after deleteThread ends - Item loadedItem = s.get(Item.class, item.getId()); - assertNotNull(loadedItem); - }); - } catch (PessimisticLockException e) { - // If we end up here, database locks guard us against situation tested - // in this case and HHH-9868 cannot happen. - // (delete-thread has ITEMS table write-locked and we try to acquire read-lock) - try { - arriveAndAwait(getPhaser); - arriveAndAwait(getPhaser); - } catch (Exception e1) { - throw new RuntimeException(e1); - } - } catch (Exception e) { - throw new RuntimeException(e); - } - }, "get-thread"); - - deleteThread.start(); - // deleteThread loads the entity - arriveAndAwait(deletePhaser); - withTx(() -> { - sessionFactory().getCache().evictEntity(Item.class, item.getId()); - assertFalse(sessionFactory().getCache().containsEntity(Item.class, item.getId())); - return null; - }); - arriveAndAwait(deletePhaser); - // delete thread invalidates PFER - arriveAndAwait(deletePhaser); - // get thread gets the entity from DB - hook.block(getPhaser, getThread); - getThread.start(); - arriveAndAwait(getPhaser); - arriveAndAwait(deletePhaser); - // delete thread finishes the remove from DB and cache - deleteThread.join(); - hook.unblock(); - arriveAndAwait(getPhaser); - // get thread puts the entry into cache - getThread.join(); - - withTxSession(s -> { - Item loadedItem = s.get(Item.class, item.getId()); - assertNull(loadedItem); - }); - } - - protected static void arriveAndAwait(Phaser phaser) throws TimeoutException, InterruptedException { - phaser.awaitAdvanceInterruptibly(phaser.arrive(), 1000, TimeUnit.SECONDS); - } - - private static class HookInterceptor extends BaseCustomInterceptor { - Phaser phaser; - Thread thread; - - public synchronized void block(Phaser phaser, Thread thread) { - this.phaser = phaser; - this.thread = thread; - } - - public synchronized void unblock() { - phaser = null; - thread = null; - } - - @Override - public Object visitGetKeyValueCommand(InvocationContext ctx, GetKeyValueCommand command) throws Throwable { - Phaser phaser; - Thread thread; - synchronized (this) { - phaser = this.phaser; - thread = this.thread; - } - if (phaser != null && Thread.currentThread() == thread) { - arriveAndAwait(phaser); - arriveAndAwait(phaser); - } - return super.visitGetKeyValueCommand(ctx, command); - } - } } diff --git a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/ReadWriteTest.java b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/ReadWriteTest.java index bdf24158d3..d190d8be6b 100644 --- a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/ReadWriteTest.java +++ b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/ReadWriteTest.java @@ -32,11 +32,9 @@ import org.hibernate.testing.TestForIssue; import org.infinispan.commons.util.ByRef; import org.junit.After; import org.junit.Test; -import org.junit.runners.Parameterized; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNotNull; -import static org.hibernate.test.cache.infinispan.util.TxUtil.markRollbackOnly; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -51,7 +49,7 @@ import static org.junit.Assert.fail; public class ReadWriteTest extends ReadOnlyTest { @Override public List getParameters() { - return Arrays.asList(TRANSACTIONAL, READ_WRITE); + return getParameters(true, true, false); } @Override @@ -85,14 +83,18 @@ public class ReadWriteTest extends ReadOnlyTest { s.persist( item ); s.persist( another ); }); + // The collection has been removed, but we can't add it again immediately using putFromLoad + Thread.sleep(1); withTxSession(s -> { Item loaded = s.load( Item.class, item.getId() ); assertEquals( 1, loaded.getItems().size() ); }); + SecondLevelCacheStatistics cStats = stats.getSecondLevelCacheStatistics( Item.class.getName() + ".items" ); + assertEquals( 1, cStats.getElementCountInMemory() ); + withTxSession(s -> { - SecondLevelCacheStatistics cStats = stats.getSecondLevelCacheStatistics( Item.class.getName() + ".items" ); Item loadedWithCachedCollection = (Item) s.load( Item.class, item.getId() ); stats.logSummary(); assertEquals( item.getName(), loadedWithCachedCollection.getName() ); @@ -385,6 +387,8 @@ public class ReadWriteTest extends ReadOnlyTest { SecondLevelCacheStatistics slcs = stats.getSecondLevelCacheStatistics( Item.class.getName() ); sessionFactory().getCache().evictEntityRegion( Item.class.getName() ); + Thread.sleep(1); + assertEquals(0, slcs.getPutCount()); assertEquals( 0, slcs.getElementCountInMemory() ); assertEquals( 0, slcs.getEntries().size() ); diff --git a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/TombstoneTest.java b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/TombstoneTest.java new file mode 100644 index 0000000000..b0f09b1e6c --- /dev/null +++ b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/TombstoneTest.java @@ -0,0 +1,375 @@ +package org.hibernate.test.cache.infinispan.functional; + +import org.hibernate.PessimisticLockException; +import org.hibernate.StaleStateException; +import org.hibernate.cache.infinispan.InfinispanRegionFactory; +import org.hibernate.cache.infinispan.entity.EntityRegionImpl; +import org.hibernate.cache.infinispan.util.Caches; +import org.hibernate.cache.infinispan.util.FutureUpdate; +import org.hibernate.cache.infinispan.util.Tombstone; +import org.hibernate.cache.spi.Region; +import org.hibernate.cache.spi.entry.StandardCacheEntryImpl; +import org.hibernate.test.cache.infinispan.functional.entities.Item; +import org.hibernate.test.cache.infinispan.util.TestInfinispanRegionFactory; +import org.hibernate.test.cache.infinispan.util.TestTimeService; +import org.infinispan.AdvancedCache; +import org.infinispan.util.logging.Log; +import org.infinispan.util.logging.LogFactory; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Test; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.Semaphore; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.Assert.*; + + +/** + * Tests specific to tombstone-based caches + * + * @author Radim Vansa <rvansa@redhat.com> + */ +public class TombstoneTest extends SingleNodeTest { + private static Log log = LogFactory.getLog(TombstoneTest.class); + + private static final ExecutorService EXECUTOR = Executors.newCachedThreadPool(new ThreadFactory() { + AtomicInteger counter = new AtomicInteger(); + + @Override + public Thread newThread(Runnable r) { + return new Thread(r, TombstoneTest.class.getSimpleName() + "-executor-" + counter.incrementAndGet()); + } + }); + private static final long TOMBSTONE_TIMEOUT = InfinispanRegionFactory.PENDING_PUTS_CACHE_CONFIGURATION.expiration().maxIdle(); + private static final int WAIT_TIMEOUT = 2000; + private static final TestTimeService TIME_SERVICE = new TestTimeService(); + private Region region; + private AdvancedCache entityCache; + private long itemId; + + @Override + public List getParameters() { + return Arrays.asList(READ_WRITE_REPLICATED, READ_WRITE_DISTRIBUTED); + } + + @Override + protected void startUp() { + super.startUp(); + region = sessionFactory().getSecondLevelCacheRegion(Item.class.getName()); + entityCache = ((EntityRegionImpl) region).getCache(); + } + + @Before + public void insertAndClearCache() throws Exception { + Item item = new Item("my item", "item that belongs to me"); + withTxSession(s -> s.persist(item)); + entityCache.clear(); + assertEquals("Cache is not empty", Collections.EMPTY_SET, Caches.keys(entityCache).toSet()); + itemId = item.getId(); + } + + @After + public void cleanup() throws Exception { + withTxSession(s -> { + s.createQuery("delete from Item").executeUpdate(); + }); + } + + @AfterClass + public static void shutdown() { + EXECUTOR.shutdown(); + } + + @Override + protected void addSettings(Map settings) { + super.addSettings(settings); + settings.put(TestInfinispanRegionFactory.TIME_SERVICE, TIME_SERVICE); + } + + @Test + public void testTombstoneExpiration() throws Exception { + CyclicBarrier loadBarrier = new CyclicBarrier(2); + CountDownLatch flushLatch = new CountDownLatch(2); + CountDownLatch commitLatch = new CountDownLatch(1); + + Future first = removeFlushWait(itemId, loadBarrier, null, flushLatch, commitLatch); + Future second = removeFlushWait(itemId, loadBarrier, null, flushLatch, commitLatch); + awaitOrThrow(flushLatch); + + Map contents = Caches.entrySet(entityCache).toMap(); + assertEquals(1, contents.size()); + assertEquals(Tombstone.class, contents.get(itemId).getClass()); + commitLatch.countDown(); + first.get(WAIT_TIMEOUT, TimeUnit.SECONDS); + second.get(WAIT_TIMEOUT, TimeUnit.SECONDS); + + // after commit, the tombstone should still be in memory for some time (though, updatable) + contents = Caches.entrySet(entityCache).toMap(); + assertEquals(1, contents.size()); + assertEquals(Tombstone.class, contents.get(itemId).getClass()); + + TIME_SERVICE.advance(TOMBSTONE_TIMEOUT + 1); + assertNull(entityCache.get(itemId)); // force expiration + contents = Caches.entrySet(entityCache).toMap(); + assertEquals(Collections.EMPTY_MAP, contents); + } + + @Test + public void testFutureUpdateExpiration() throws Exception { + CyclicBarrier loadBarrier = new CyclicBarrier(2); + CountDownLatch flushLatch = new CountDownLatch(2); + CountDownLatch commitLatch = new CountDownLatch(1); + + Future first = updateFlushWait(itemId, loadBarrier, null, flushLatch, commitLatch); + Future second = updateFlushWait(itemId, loadBarrier, null, flushLatch, commitLatch); + awaitOrThrow(flushLatch); + + Map contents = Caches.entrySet(entityCache).toMap(); + assertEquals(1, contents.size()); + assertEquals(FutureUpdate.class, contents.get(itemId).getClass()); + commitLatch.countDown(); + first.get(WAIT_TIMEOUT, TimeUnit.SECONDS); + second.get(WAIT_TIMEOUT, TimeUnit.SECONDS); + + // since we had two concurrent updates, the result should be invalid + contents = Caches.entrySet(entityCache).toMap(); + assertEquals(1, contents.size()); + Object value = contents.get(itemId); + if (value instanceof FutureUpdate) { + // DB did not blocked two concurrent updates + TIME_SERVICE.advance(TOMBSTONE_TIMEOUT + 1); + assertNull(entityCache.get(itemId)); + contents = Caches.entrySet(entityCache).toMap(); + assertEquals(Collections.EMPTY_MAP, contents); + } else { + // DB left only one update to proceed, and the entry should not be expired + assertNotNull(value); + assertEquals(StandardCacheEntryImpl.class, value.getClass()); + TIME_SERVICE.advance(TOMBSTONE_TIMEOUT + 1); + assertEquals(value, entityCache.get(itemId)); + } + } + + @Test + public void testRemoveUpdateExpiration() throws Exception { + CyclicBarrier loadBarrier = new CyclicBarrier(2); + CountDownLatch preFlushLatch = new CountDownLatch(1); + CountDownLatch flushLatch = new CountDownLatch(1); + CountDownLatch commitLatch = new CountDownLatch(1); + + Future first = removeFlushWait(itemId, loadBarrier, null, flushLatch, commitLatch); + Future second = updateFlushWait(itemId, loadBarrier, preFlushLatch, null, commitLatch); + awaitOrThrow(flushLatch); + + Map contents = Caches.entrySet(entityCache).toMap(); + assertEquals(1, contents.size()); + assertEquals(Tombstone.class, contents.get(itemId).getClass()); + + preFlushLatch.countDown(); + commitLatch.countDown(); + first.get(WAIT_TIMEOUT, TimeUnit.SECONDS); + second.get(WAIT_TIMEOUT, TimeUnit.SECONDS); + + contents = Caches.entrySet(entityCache).toMap(); + assertEquals(1, contents.size()); + assertEquals(Tombstone.class, contents.get(itemId).getClass()); + + TIME_SERVICE.advance(TOMBSTONE_TIMEOUT + 1); + assertNull(entityCache.get(itemId)); // force expiration + contents = Caches.entrySet(entityCache).toMap(); + assertEquals(Collections.EMPTY_MAP, contents); + } + + @Test + public void testUpdateRemoveExpiration() throws Exception { + CyclicBarrier loadBarrier = new CyclicBarrier(2); + CountDownLatch preFlushLatch = new CountDownLatch(1); + CountDownLatch flushLatch = new CountDownLatch(1); + CountDownLatch commitLatch = new CountDownLatch(1); + + Future first = updateFlushWait(itemId, loadBarrier, null, flushLatch, commitLatch); + Future second = removeFlushWait(itemId, loadBarrier, preFlushLatch, null, commitLatch); + awaitOrThrow(flushLatch); + + Map contents = Caches.entrySet(entityCache).toMap(); + assertEquals(1, contents.size()); + assertEquals(FutureUpdate.class, contents.get(itemId).getClass()); + + preFlushLatch.countDown(); + commitLatch.countDown(); + first.get(WAIT_TIMEOUT, TimeUnit.SECONDS); + boolean removeSucceeded = second.get(WAIT_TIMEOUT, TimeUnit.SECONDS); + + contents = Caches.entrySet(entityCache).toMap(); + assertEquals(1, contents.size()); + Object value = contents.get(itemId); + if (removeSucceeded) { + assertEquals(Tombstone.class, value.getClass()); + TIME_SERVICE.advance(TOMBSTONE_TIMEOUT + 1); + assertNull(entityCache.get(itemId)); // force expiration + contents = Caches.entrySet(entityCache).toMap(); + assertEquals(Collections.EMPTY_MAP, contents); + } else { + assertNotNull(value); + assertEquals(StandardCacheEntryImpl.class, value.getClass()); + TIME_SERVICE.advance(TOMBSTONE_TIMEOUT + 1); + assertEquals(value, entityCache.get(itemId)); + } + } + + @Test + public void testUpdateEvictExpiration() throws Exception { + CyclicBarrier loadBarrier = new CyclicBarrier(2); + CountDownLatch preEvictLatch = new CountDownLatch(1); + CountDownLatch flushLatch = new CountDownLatch(1); + CountDownLatch commitLatch = new CountDownLatch(1); + + Future first = updateFlushWait(itemId, loadBarrier, null, flushLatch, commitLatch); + Future second = evictWait(itemId, loadBarrier, preEvictLatch, null); + awaitOrThrow(flushLatch); + + Map contents = Caches.entrySet(entityCache).toMap(); + assertEquals(1, contents.size()); + assertEquals(FutureUpdate.class, contents.get(itemId).getClass()); + + preEvictLatch.countDown(); + commitLatch.countDown(); + first.get(WAIT_TIMEOUT, TimeUnit.SECONDS); + second.get(WAIT_TIMEOUT, TimeUnit.SECONDS); + + contents = Caches.entrySet(entityCache).toMap(); + assertEquals(0, contents.size()); + assertNull(contents.get(itemId)); + } + + @Test + public void testEvictUpdateExpiration() throws Exception { + CyclicBarrier loadBarrier = new CyclicBarrier(2); + CountDownLatch preFlushLatch = new CountDownLatch(1); + CountDownLatch postEvictLatch = new CountDownLatch(1); + CountDownLatch flushLatch = new CountDownLatch(1); + CountDownLatch commitLatch = new CountDownLatch(1); + + Future first = evictWait(itemId, loadBarrier, null, postEvictLatch); + Future second = updateFlushWait(itemId, loadBarrier, preFlushLatch, flushLatch, commitLatch); + awaitOrThrow(postEvictLatch); + + Map contents = Caches.entrySet(entityCache).toMap(); + assertEquals(Collections.EMPTY_MAP, contents); + assertNull(contents.get(itemId)); + + preFlushLatch.countDown(); + awaitOrThrow(flushLatch); + contents = Caches.entrySet(entityCache).toMap(); + assertEquals(1, contents.size()); + assertEquals(FutureUpdate.class, contents.get(itemId).getClass()); + + commitLatch.countDown(); + first.get(WAIT_TIMEOUT, TimeUnit.SECONDS); + second.get(WAIT_TIMEOUT, TimeUnit.SECONDS); + + contents = Caches.entrySet(entityCache).toMap(); + assertEquals(1, contents.size()); + Object value = contents.get(itemId); + assertNotNull(value); + assertEquals(StandardCacheEntryImpl.class, value.getClass()); + TIME_SERVICE.advance(TOMBSTONE_TIMEOUT + 1); + assertEquals(value, entityCache.get(itemId)); + } + + protected Future removeFlushWait(long id, CyclicBarrier loadBarrier, CountDownLatch preFlushLatch, CountDownLatch flushLatch, CountDownLatch commitLatch) throws Exception { + return EXECUTOR.submit(() -> withTxSessionApply(s -> { + try { + Item item = s.load(Item.class, id); + item.getName(); // force load & putFromLoad before the barrier + loadBarrier.await(WAIT_TIMEOUT, TimeUnit.SECONDS); + s.delete(item); + if (preFlushLatch != null) { + awaitOrThrow(preFlushLatch); + } + s.flush(); + } catch (StaleStateException e) { + log.info("Exception thrown: ", e); + markRollbackOnly(s); + return false; + } catch (PessimisticLockException e) { + log.info("Exception thrown: ", e); + markRollbackOnly(s); + return false; + } finally { + if (flushLatch != null) { + flushLatch.countDown(); + } + } + awaitOrThrow(commitLatch); + return true; + })); + } + + protected Future updateFlushWait(long id, CyclicBarrier loadBarrier, CountDownLatch preFlushLatch, CountDownLatch flushLatch, CountDownLatch commitLatch) throws Exception { + return EXECUTOR.submit(() -> withTxSessionApply(s -> { + try { + Item item = s.load(Item.class, id); + item.getName(); // force load & putFromLoad before the barrier + loadBarrier.await(WAIT_TIMEOUT, TimeUnit.SECONDS); + item.setDescription("Updated item"); + s.update(item); + if (preFlushLatch != null) { + awaitOrThrow(preFlushLatch); + } + s.flush(); + } catch (StaleStateException e) { + log.info("Exception thrown: ", e); + markRollbackOnly(s); + return false; + } catch (PessimisticLockException e) { + log.info("Exception thrown: ", e); + markRollbackOnly(s); + return false; + } finally { + if (flushLatch != null) { + flushLatch.countDown(); + } + } + awaitOrThrow(commitLatch); + return true; + })); + } + + protected Future evictWait(long id, CyclicBarrier loadBarrier, CountDownLatch preEvictLatch, CountDownLatch postEvictLatch) throws Exception { + return EXECUTOR.submit(() -> { + try { + loadBarrier.await(WAIT_TIMEOUT, TimeUnit.SECONDS); + if (preEvictLatch != null) { + awaitOrThrow(preEvictLatch); + } + sessionFactory().getCache().evictEntity(Item.class, id); + } finally { + if (postEvictLatch != null) { + postEvictLatch.countDown(); + } + } + return true; + }); + } + + protected void awaitOrThrow(CountDownLatch latch) throws InterruptedException, TimeoutException { + if (!latch.await(WAIT_TIMEOUT, TimeUnit.SECONDS)) { + throw new TimeoutException(); + } + } +} diff --git a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/ClusterAwareRegionFactory.java b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/ClusterAwareRegionFactory.java index f7ac97d3a2..41efe8032b 100644 --- a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/ClusterAwareRegionFactory.java +++ b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/ClusterAwareRegionFactory.java @@ -6,6 +6,7 @@ */ package org.hibernate.test.cache.infinispan.functional.cluster; +import java.lang.reflect.Constructor; import java.util.Hashtable; import java.util.Properties; @@ -22,6 +23,7 @@ import org.hibernate.cache.spi.TimestampsRegion; import org.hibernate.cache.spi.access.AccessType; import org.hibernate.internal.util.ReflectHelper; +import org.hibernate.test.cache.infinispan.util.CacheTestUtil; import org.infinispan.manager.EmbeddedCacheManager; import org.infinispan.util.logging.Log; import org.infinispan.util.logging.LogFactory; @@ -33,7 +35,6 @@ import org.infinispan.util.logging.LogFactory; * @since 3.5 */ public class ClusterAwareRegionFactory implements RegionFactory { - private static final Log log = LogFactory.getLog(ClusterAwareRegionFactory.class); private static final Hashtable cacheManagers = new Hashtable(); @@ -42,11 +43,9 @@ public class ClusterAwareRegionFactory implements RegionFactory { private boolean locallyAdded; public ClusterAwareRegionFactory(Properties props) { - try { - delegate = (InfinispanRegionFactory) ReflectHelper.classForName(props.getProperty(DualNodeTest.REGION_FACTORY_DELEGATE)).newInstance(); - } catch (Exception e) { - throw new IllegalStateException(e); - } + Class regionFactoryClass = + (Class) props.get(DualNodeTest.REGION_FACTORY_DELEGATE); + delegate = CacheTestUtil.createRegionFactory(regionFactoryClass, props); } public static EmbeddedCacheManager getCacheManager(String name) { @@ -97,7 +96,6 @@ public class ClusterAwareRegionFactory implements RegionFactory { return delegate.buildEntityRegion(regionName, properties, metadata); } - @Override public NaturalIdRegion buildNaturalIdRegion(String regionName, Properties properties, CacheDataDescription metadata) throws CacheException { return delegate.buildNaturalIdRegion( regionName, properties, metadata ); diff --git a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/DualNodeTest.java b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/DualNodeTest.java index 9a90b06d1b..6a17a99ec1 100644 --- a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/DualNodeTest.java +++ b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/DualNodeTest.java @@ -74,7 +74,7 @@ public abstract class DualNodeTest extends AbstractFunctionalTest { settings.put( NODE_ID_PROP, LOCAL ); settings.put( NODE_ID_FIELD, LOCAL ); - settings.put( REGION_FACTORY_DELEGATE, regionFactoryClass.getName() ); + settings.put( REGION_FACTORY_DELEGATE, getRegionFactoryClass() ); } @Override diff --git a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/EntityCollectionInvalidationTest.java b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/EntityCollectionInvalidationTest.java index 26808dac69..440fc6f914 100644 --- a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/EntityCollectionInvalidationTest.java +++ b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/EntityCollectionInvalidationTest.java @@ -19,6 +19,7 @@ import java.util.concurrent.atomic.AtomicReference; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.cache.infinispan.InfinispanRegionFactory; +import org.hibernate.cache.infinispan.util.Caches; import org.hibernate.test.cache.infinispan.functional.entities.Contact; import org.hibernate.test.cache.infinispan.functional.entities.Customer; import org.hibernate.testing.TestForIssue; @@ -62,7 +63,7 @@ public class EntityCollectionInvalidationTest extends DualNodeTest { @Override public List getParameters() { - return Arrays.asList(TRANSACTIONAL, READ_WRITE); + return getParameters(true, true, false); } @Override @@ -159,14 +160,23 @@ public class EntityCollectionInvalidationTest extends DualNodeTest { sleep( 250 ); assertLoadedFromCache( remoteListener, ids.customerId, ids.contactIds ); - // After modification, local cache should have been invalidated and hence should be empty assertEquals( 0, localCollectionCache.size() ); - assertEquals( 0, localCustomerCache.size() ); + if (localCustomerCache.getCacheConfiguration().clustering().cacheMode().isInvalidation()) { + // After modification, local cache should have been invalidated and hence should be empty + assertEquals(0, localCustomerCache.size()); + } else { + // Replicated cache is updated, not invalidated + assertEquals(1, localCustomerCache.size()); + } } @TestForIssue(jiraKey = "HHH-9881") @Test public void testConcurrentLoadAndRemoval() throws Exception { + if (!remoteCustomerCache.getCacheConfiguration().clustering().cacheMode().isInvalidation()) { + // This test is tailored for invalidation-based strategies, using pending puts cache + return; + } AtomicReference getException = new AtomicReference<>(); AtomicReference deleteException = new AtomicReference<>(); diff --git a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/NaturalIdInvalidationTest.java b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/NaturalIdInvalidationTest.java index a2e52df3aa..b3156ec667 100644 --- a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/NaturalIdInvalidationTest.java +++ b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/NaturalIdInvalidationTest.java @@ -45,7 +45,7 @@ public class NaturalIdInvalidationTest extends DualNodeTest { @Override public List getParameters() { - return Arrays.asList(TRANSACTIONAL, READ_WRITE, READ_ONLY); + return getParameters(true, true, true); } @Override diff --git a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/SessionRefreshTest.java b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/SessionRefreshTest.java index 561d9f1d0c..a5c1e17b07 100644 --- a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/SessionRefreshTest.java +++ b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/functional/cluster/SessionRefreshTest.java @@ -40,7 +40,7 @@ public class SessionRefreshTest extends DualNodeTest { @Override public List getParameters() { - return Arrays.asList(TRANSACTIONAL, READ_WRITE); + return getParameters(true, true, false); } @Override diff --git a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/query/QueryRegionImplTest.java b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/query/QueryRegionImplTest.java index 04535dd09e..cf8581bd20 100644 --- a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/query/QueryRegionImplTest.java +++ b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/query/QueryRegionImplTest.java @@ -138,7 +138,7 @@ public class QueryRegionImplTest extends AbstractGeneralDataRegionTest { // Start the reader reader.start(); - assertTrue("Reader finished promptly", readerLatch.await(1000000000, TimeUnit.MILLISECONDS)); + assertTrue("Reader finished promptly", readerLatch.await(100, TimeUnit.MILLISECONDS)); writerLatch.countDown(); diff --git a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/stress/CorrectnessTestCase.java b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/stress/CorrectnessTestCase.java index 74200a73d4..8ce7e5c323 100644 --- a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/stress/CorrectnessTestCase.java +++ b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/stress/CorrectnessTestCase.java @@ -43,22 +43,24 @@ import org.hibernate.test.cache.infinispan.stress.entities.Person; import org.hibernate.test.cache.infinispan.util.TestInfinispanRegionFactory; import org.hibernate.testing.jta.JtaAwareConnectionProviderImpl; import org.hibernate.testing.jta.TestingJtaPlatformImpl; +import org.hibernate.testing.junit4.CustomParameterized; import org.infinispan.commands.VisitableCommand; import org.infinispan.commands.tx.CommitCommand; import org.infinispan.commands.tx.RollbackCommand; import org.infinispan.commons.util.ByRef; +import org.infinispan.configuration.cache.CacheMode; import org.infinispan.configuration.cache.ConfigurationBuilder; import org.infinispan.configuration.cache.InterceptorConfiguration; -import org.infinispan.configuration.parsing.ConfigurationBuilderHolder; import org.infinispan.context.InvocationContext; import org.infinispan.interceptors.base.BaseCustomInterceptor; import org.infinispan.remoting.RemoteException; -import org.infinispan.transaction.TransactionMode; import org.infinispan.util.logging.LogFactory; import org.junit.After; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; import javax.transaction.RollbackException; import javax.transaction.Status; @@ -85,6 +87,7 @@ import java.util.stream.Collectors; * * @author Radim Vansa */ +@RunWith(CustomParameterized.class) public abstract class CorrectnessTestCase { static final org.infinispan.util.logging.Log log = LogFactory.getLog(CorrectnessTestCase.class); static final long EXECUTION_TIME = TimeUnit.MINUTES.toMillis(10); @@ -96,6 +99,12 @@ public abstract class CorrectnessTestCase { static final int MAX_MEMBERS = 10; private final static Comparator> WALL_CLOCK_TIME_COMPARATOR = (o1, o2) -> Long.compare(o1.wallClockTime, o2.wallClockTime); + @Parameterized.Parameter(0) + public String name; + + @Parameterized.Parameter(1) + public CacheMode cacheMode; + static ThreadLocal threadNode = new ThreadLocal<>(); final AtomicInteger timestampGenerator = new AtomicInteger(); @@ -121,20 +130,41 @@ public abstract class CorrectnessTestCase { return getClass().getName().replaceAll("\\W", "_"); } - public abstract static class Jta extends CorrectnessTestCase { + @Ignore + public static class Jta extends CorrectnessTestCase { private final TransactionManager transactionManager = TestingJtaPlatformImpl.transactionManager(); + @Parameterized.Parameter(2) + public boolean transactional; + + @Parameterized.Parameter(3) + public boolean readOnly; + + @Parameterized.Parameters(name = "{0}") + public List getParameters() { + return Arrays.asList( + new Object[] { "transactional, invalidation", CacheMode.INVALIDATION_SYNC, true, false }, + new Object[] { "read-only, invalidation", CacheMode.INVALIDATION_SYNC, false, true }, // maybe not needed + new Object[] { "read-write, invalidation", CacheMode.INVALIDATION_SYNC, false, false }, + new Object[] { "read-write, replicated", CacheMode.REPL_SYNC, false, false }, + new Object[] { "read-write, distributed", CacheMode.DIST_SYNC, false, false } + ); + } + @Override protected void applySettings(StandardServiceRegistryBuilder ssrb) { ssrb .applySetting( Environment.JTA_PLATFORM, TestingJtaPlatformImpl.class.getName() ) .applySetting( Environment.CONNECTION_PROVIDER, JtaAwareConnectionProviderImpl.class.getName() ) - .applySetting( Environment.TRANSACTION_COORDINATOR_STRATEGY, JtaTransactionCoordinatorBuilderImpl.class.getName() ); + .applySetting( Environment.TRANSACTION_COORDINATOR_STRATEGY, JtaTransactionCoordinatorBuilderImpl.class.getName() ) + .applySetting(TestInfinispanRegionFactory.TRANSACTIONAL, transactional); + if (readOnly) { + ssrb.applySetting(Environment.DEFAULT_CACHE_CONCURRENCY_STRATEGY, CacheConcurrencyStrategy.READ_ONLY.toAccessType().getExternalName()); + } } @Override protected void withTx(Runnable runnable, boolean rolledBack) throws Exception { - int node = threadNode.get(); TransactionManager tm = transactionManager; tm.begin(); try { @@ -155,45 +185,35 @@ public abstract class CorrectnessTestCase { } } } - } - @Ignore // as long-running test, we'll execute it only by hand - public static class JtaTransactional extends Jta { - } - - @Ignore // as long-running test, we'll execute it only by hand - public static class JtaReadOnly extends Jta { @Override protected Operation getOperation() { - ThreadLocalRandom random = ThreadLocalRandom.current(); - Operation operation; - int r = random.nextInt(30); - if (r == 0) operation = new InvalidateCache(); - else if (r < 5) operation = new QueryFamilies(); - else if (r < 10) operation = new RemoveFamily(r < 12); - else operation = new ReadFamily(r < 20); - return operation; - } - - @Override - protected void applySettings(StandardServiceRegistryBuilder ssrb) { - super.applySettings(ssrb); - ssrb.applySetting(Environment.DEFAULT_CACHE_CONCURRENCY_STRATEGY, CacheConcurrencyStrategy.READ_ONLY.toAccessType().getExternalName()); - ssrb.applySetting(Environment.CACHE_REGION_FACTORY, ForceNonTxInfinispanRegionFactory.class.getName()); + if (readOnly) { + ThreadLocalRandom random = ThreadLocalRandom.current(); + Operation operation; + int r = random.nextInt(30); + if (r == 0) operation = new InvalidateCache(); + else if (r < 5) operation = new QueryFamilies(); + else if (r < 10) operation = new RemoveFamily(r < 12); + else operation = new ReadFamily(r < 20); + return operation; + } else { + return super.getOperation(); + } } } - @Ignore // as long-running test, we'll execute it only by hand - public static class JtaNonTransactional extends Jta { - @Override - protected void applySettings(StandardServiceRegistryBuilder ssrb) { - super.applySettings(ssrb); - ssrb.applySetting(Environment.CACHE_REGION_FACTORY, ForceNonTxInfinispanRegionFactory.class.getName()); - } - } - - @Ignore // as long-running test, we'll execute it only by hand + @Ignore public static class NonJta extends CorrectnessTestCase { + @Parameterized.Parameters(name = "{0}") + public List getParameters() { + return Arrays.asList( + new Object[] { "read-write, invalidation", CacheMode.INVALIDATION_SYNC }, + new Object[] { "read-write, replicated", CacheMode.REPL_SYNC }, + new Object[] { "read-write, distributed", CacheMode.DIST_SYNC } + ); + } + @Override protected void withTx(Runnable runnable, boolean rolledBack) throws Exception { // no transaction on JTA TM @@ -205,7 +225,7 @@ public abstract class CorrectnessTestCase { super.applySettings(ssrb); ssrb.applySetting(Environment.JTA_PLATFORM, NoJtaPlatform.class.getName()); ssrb.applySetting(Environment.TRANSACTION_COORDINATOR_STRATEGY, JdbcResourceLocalTransactionCoordinatorBuilderImpl.class.getName()); - ssrb.applySetting(Environment.CACHE_REGION_FACTORY, ForceNonTxInfinispanRegionFactory.class.getName()); + ssrb.applySetting(TestInfinispanRegionFactory.TRANSACTIONAL, false); } } @@ -222,6 +242,7 @@ public abstract class CorrectnessTestCase { .applySetting( Environment.DIALECT, H2Dialect.class.getName() ) .applySetting( Environment.HBM2DDL_AUTO, "create-drop" ) .applySetting( Environment.CACHE_REGION_FACTORY, FailingInfinispanRegionFactory.class.getName()) + .applySetting( TestInfinispanRegionFactory.CACHE_MODE, cacheMode ) .applySetting( Environment.GENERATE_STATISTICS, "false" ); applySettings(ssrb); @@ -291,30 +312,21 @@ public abstract class CorrectnessTestCase { } public static class FailingInfinispanRegionFactory extends TestInfinispanRegionFactory { - @Override - protected void amendConfiguration(ConfigurationBuilderHolder holder) { - for (Map.Entry entry : holder.getNamedConfigurationBuilders().entrySet()) { - // failure to write into timestamps would cause failure even though both DB and cache has been updated - if (!entry.getKey().equals("timestamps") && !entry.getKey().endsWith(InfinispanRegionFactory.PENDING_PUTS_CACHE_NAME)) { - entry.getValue().customInterceptors().addInterceptor() - .interceptorClass(FailureInducingInterceptor.class) - .position(InterceptorConfiguration.Position.FIRST); - log.trace("Injecting FailureInducingInterceptor into " + entry.getKey()); - } - else { - log.trace("Not injecting into " + entry.getKey()); - } - } + public FailingInfinispanRegionFactory(Properties properties) { + super(properties); } - } - public static class ForceNonTxInfinispanRegionFactory extends FailingInfinispanRegionFactory { @Override - protected void amendConfiguration(ConfigurationBuilderHolder holder) { - super.amendConfiguration(holder); - holder.getDefaultConfigurationBuilder().transaction().transactionMode(TransactionMode.NON_TRANSACTIONAL); - for (ConfigurationBuilder cb : holder.getNamedConfigurationBuilders().values()) { - cb.transaction().transactionMode(TransactionMode.NON_TRANSACTIONAL); + protected void amendCacheConfiguration(String cacheName, ConfigurationBuilder configurationBuilder) { + super.amendCacheConfiguration(cacheName, configurationBuilder); + // failure to write into timestamps would cause failure even though both DB and cache has been updated + if (!cacheName.equals("timestamps") && !cacheName.endsWith(InfinispanRegionFactory.PENDING_PUTS_CACHE_NAME)) { + configurationBuilder.customInterceptors().addInterceptor() + .interceptorClass(FailureInducingInterceptor.class) + .position(InterceptorConfiguration.Position.FIRST); + log.trace("Injecting FailureInducingInterceptor into " + cacheName); + } else { + log.trace("Not injecting into " + cacheName); } } } @@ -974,12 +986,34 @@ public abstract class CorrectnessTestCase { if (strategy == null) { return null; } - Field delegateField = strategy.getClass().getDeclaredField("delegate"); - delegateField.setAccessible(true); + Field delegateField = getField(strategy.getClass(), "delegate"); Object delegate = delegateField.get(strategy); - Field validatorField = InvalidationCacheAccessDelegate.class.getDeclaredField("putValidator"); - validatorField.setAccessible(true); - return (PutFromLoadValidator) validatorField.get(delegate); + if (delegate == null) { + return null; + } + if (InvalidationCacheAccessDelegate.class.isInstance(delegate)) { + Field validatorField = InvalidationCacheAccessDelegate.class.getDeclaredField("putValidator"); + validatorField.setAccessible(true); + return (PutFromLoadValidator) validatorField.get(delegate); + } else { + return null; + } + } + + private Field getField(Class clazz, String fieldName) { + Field f = null; + while (clazz != null && clazz != Object.class) { + try { + f = clazz.getDeclaredField(fieldName); + break; + } catch (NoSuchFieldException e) { + clazz = clazz.getSuperclass(); + } + } + if (f != null) { + f.setAccessible(true); + } + return f; } protected SessionFactory sessionFactory(int node) { diff --git a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/timestamp/TimestampsRegionImplTest.java b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/timestamp/TimestampsRegionImplTest.java index b6381b163c..cb7d145e68 100644 --- a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/timestamp/TimestampsRegionImplTest.java +++ b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/timestamp/TimestampsRegionImplTest.java @@ -47,7 +47,7 @@ import org.infinispan.notifications.cachelistener.event.Event; */ public class TimestampsRegionImplTest extends AbstractGeneralDataRegionTest { - @Override + @Override protected String getStandardRegionName(String regionPrefix) { return regionPrefix + "/" + UpdateTimestampsCache.class.getName(); } @@ -110,7 +110,8 @@ public class TimestampsRegionImplTest extends AbstractGeneralDataRegionTest { public static class MockInfinispanRegionFactory extends TestInfinispanRegionFactory { - public MockInfinispanRegionFactory() { + public MockInfinispanRegionFactory(Properties properties) { + super(properties); } @Override @@ -149,5 +150,4 @@ public class TimestampsRegionImplTest extends AbstractGeneralDataRegionTest { } } } - } diff --git a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/util/BatchModeTransactionCoordinator.java b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/util/BatchModeTransactionCoordinator.java index 629eeb2e2e..1db2a1dc65 100644 --- a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/util/BatchModeTransactionCoordinator.java +++ b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/util/BatchModeTransactionCoordinator.java @@ -2,24 +2,30 @@ package org.hibernate.test.cache.infinispan.util; import org.hibernate.HibernateException; import org.hibernate.Transaction; +import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess; +import org.hibernate.engine.jdbc.spi.SqlExceptionHelper; import org.hibernate.engine.transaction.spi.IsolationDelegate; import org.hibernate.engine.transaction.spi.TransactionObserver; import org.hibernate.resource.transaction.SynchronizationRegistry; import org.hibernate.resource.transaction.TransactionCoordinator; import org.hibernate.resource.transaction.TransactionCoordinatorBuilder; +import org.hibernate.resource.transaction.backend.jta.internal.JtaIsolationDelegate; import org.hibernate.resource.transaction.backend.jta.internal.StatusTranslator; import org.hibernate.resource.transaction.spi.TransactionStatus; import org.infinispan.transaction.tm.BatchModeTransactionManager; import org.infinispan.transaction.tm.DummyTransaction; -import javax.transaction.HeuristicMixedException; -import javax.transaction.HeuristicRollbackException; -import javax.transaction.NotSupportedException; import javax.transaction.RollbackException; import javax.transaction.Status; import javax.transaction.Synchronization; import javax.transaction.SystemException; +import java.sql.Connection; +import java.sql.SQLException; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + /** * Mocks transaction coordinator when {@link org.hibernate.engine.spi.SessionImplementor} is only mocked * and {@link org.infinispan.transaction.tm.BatchModeTransactionManager} is used. @@ -117,7 +123,13 @@ public class BatchModeTransactionCoordinator implements TransactionCoordinator { @Override public IsolationDelegate createIsolationDelegate() { - throw new UnsupportedOperationException(); + Connection connection = mock(Connection.class); + JdbcConnectionAccess jdbcConnectionAccess = mock(JdbcConnectionAccess.class); + try { + when(jdbcConnectionAccess.obtainConnection()).thenReturn(connection); + } catch (SQLException e) { + } + return new JtaIsolationDelegate(jdbcConnectionAccess, mock(SqlExceptionHelper.class), tm); } @Override diff --git a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/util/CacheTestUtil.java b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/util/CacheTestUtil.java index 1baad09699..0ae549b545 100644 --- a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/util/CacheTestUtil.java +++ b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/util/CacheTestUtil.java @@ -6,6 +6,7 @@ */ package org.hibernate.test.cache.infinispan.util; +import java.lang.reflect.Constructor; import java.util.HashMap; import java.util.Map; import java.util.Properties; @@ -85,18 +86,41 @@ public class CacheTestUtil { return ssrb; } + public static InfinispanRegionFactory createRegionFactory(Class clazz, Properties properties) { + try { + try { + Constructor constructor = clazz.getConstructor(Properties.class); + return constructor.newInstance(properties); + } + catch (NoSuchMethodException e) { + return clazz.newInstance(); + } + } + catch (RuntimeException e) { + throw e; + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + public static InfinispanRegionFactory startRegionFactory(ServiceRegistry serviceRegistry) { try { final ConfigurationService cfgService = serviceRegistry.getService( ConfigurationService.class ); + final Properties properties = toProperties( cfgService.getSettings() ); String factoryType = cfgService.getSetting( AvailableSettings.CACHE_REGION_FACTORY, StandardConverters.STRING ); Class clazz = Thread.currentThread().getContextClassLoader().loadClass( factoryType ); InfinispanRegionFactory regionFactory; if (clazz == InfinispanRegionFactory.class) { - regionFactory = new TestInfinispanRegionFactory(); + regionFactory = new TestInfinispanRegionFactory(properties); } else { - regionFactory = (InfinispanRegionFactory) clazz.newInstance(); + if (InfinispanRegionFactory.class.isAssignableFrom(clazz)) { + regionFactory = createRegionFactory(clazz, properties); + } else { + throw new IllegalArgumentException(clazz + " is not InfinispanRegionFactory"); + } } final SessionFactoryOptionsImpl sessionFactoryOptions = new SessionFactoryOptionsImpl( @@ -104,7 +128,6 @@ public class CacheTestUtil { (StandardServiceRegistry) serviceRegistry ) ); - final Properties properties = toProperties( cfgService.getSettings() ); regionFactory.start( sessionFactoryOptions, properties ); diff --git a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/util/TestInfinispanRegionFactory.java b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/util/TestInfinispanRegionFactory.java index 1084227f67..c4956860f9 100644 --- a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/util/TestInfinispanRegionFactory.java +++ b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/util/TestInfinispanRegionFactory.java @@ -2,16 +2,18 @@ package org.hibernate.test.cache.infinispan.util; import org.hibernate.cache.infinispan.InfinispanRegionFactory; import org.infinispan.commons.executors.CachedThreadPoolExecutorFactory; +import org.infinispan.configuration.cache.CacheMode; import org.infinispan.configuration.cache.ConfigurationBuilder; import org.infinispan.configuration.global.TransportConfigurationBuilder; import org.infinispan.configuration.parsing.ConfigurationBuilderHolder; import org.infinispan.manager.DefaultCacheManager; import org.infinispan.manager.EmbeddedCacheManager; -import org.infinispan.remoting.transport.jgroups.JGroupsTransport; import org.infinispan.test.fwk.TestResourceTracker; import org.infinispan.transaction.TransactionMode; +import org.infinispan.util.TimeService; import java.util.Map; +import java.util.Properties; import java.util.concurrent.atomic.AtomicInteger; /** @@ -20,12 +22,30 @@ import java.util.concurrent.atomic.AtomicInteger; * @author Radim Vansa <rvansa@redhat.com> */ public class TestInfinispanRegionFactory extends InfinispanRegionFactory { - private static AtomicInteger counter = new AtomicInteger(); + protected static final String PREFIX = TestInfinispanRegionFactory.class.getName() + "."; + public static final String TRANSACTIONAL = PREFIX + "transactional"; + public static final String CACHE_MODE = PREFIX + "cacheMode"; + public static final String TIME_SERVICE = PREFIX + "timeService"; + + private final boolean transactional; + private final CacheMode cacheMode; + private final TimeService timeService; + + public TestInfinispanRegionFactory(Properties properties) { + transactional = (boolean) properties.getOrDefault(TRANSACTIONAL, false); + cacheMode = (CacheMode) properties.getOrDefault(CACHE_MODE, null); + timeService = (TimeService) properties.getOrDefault(TIME_SERVICE, null); + } @Override protected EmbeddedCacheManager createCacheManager(ConfigurationBuilderHolder holder) { amendConfiguration(holder); - return new DefaultCacheManager(holder, true); + DefaultCacheManager cacheManager = new DefaultCacheManager(holder, true); + if (timeService != null) { + cacheManager.getGlobalComponentRegistry().registerComponent(timeService, TimeService.class); + cacheManager.getGlobalComponentRegistry().rewire(); + } + return cacheManager; } protected void amendConfiguration(ConfigurationBuilderHolder holder) { @@ -41,26 +61,27 @@ public class TestInfinispanRegionFactory extends InfinispanRegionFactory { } } - private String buildNodeName() { - StringBuilder sb = new StringBuilder("Node"); - int id = counter.getAndIncrement(); - int alphabet = 'Z' - 'A'; - do { - sb.append((char) (id % alphabet + 'A')); - id /= alphabet; - } while (id > alphabet); - return sb.toString(); - } - protected void amendCacheConfiguration(String cacheName, ConfigurationBuilder configurationBuilder) { - } - - public static class Transactional extends TestInfinispanRegionFactory { - @Override - protected void amendCacheConfiguration(String cacheName, ConfigurationBuilder configurationBuilder) { + if (transactional) { if (!cacheName.endsWith("query") && !cacheName.equals(DEF_TIMESTAMPS_RESOURCE)) { configurationBuilder.transaction().transactionMode(TransactionMode.TRANSACTIONAL).useSynchronization(true); } + } else { + configurationBuilder.transaction().transactionMode(TransactionMode.NON_TRANSACTIONAL); + } + if (cacheMode != null) { + if (configurationBuilder.clustering().cacheMode().isInvalidation()) { + configurationBuilder.clustering().cacheMode(cacheMode); + } + } + } + + @Override + public long nextTimestamp() { + if (timeService == null) { + return super.nextTimestamp(); + } else { + return timeService.wallClockTime(); } } } diff --git a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/util/TestTimeService.java b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/util/TestTimeService.java new file mode 100644 index 0000000000..3ef53a0a0f --- /dev/null +++ b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/util/TestTimeService.java @@ -0,0 +1,26 @@ +package org.hibernate.test.cache.infinispan.util; + +import org.infinispan.util.DefaultTimeService; + +import java.util.concurrent.TimeUnit; + +/** + * @author Radim Vansa <rvansa@redhat.com> + */ +public class TestTimeService extends DefaultTimeService { + private long time = super.wallClockTime(); + + @Override + public long wallClockTime() { + return time; + } + + @Override + public long time() { + return TimeUnit.MILLISECONDS.toNanos(time); + } + + public void advance(long millis) { + time += millis; + } +}