HHH-10344 Make transactional invalidation backwards compatible
(cherry picked from commit 05aaeb1963
)
This commit is contained in:
parent
e6d211466e
commit
52f123fd12
|
@ -178,6 +178,11 @@ public class PutFromLoadValidator {
|
||||||
}
|
}
|
||||||
boolean transactional = cache.getCacheConfiguration().transaction().transactionMode().isTransactional();
|
boolean transactional = cache.getCacheConfiguration().transaction().transactionMode().isTransactional();
|
||||||
if (transactional) {
|
if (transactional) {
|
||||||
|
cache.removeInterceptor(invalidationPosition);
|
||||||
|
TxInvalidationInterceptor txInvalidationInterceptor = new TxInvalidationInterceptor();
|
||||||
|
cache.getComponentRegistry().registerComponent(txInvalidationInterceptor, TxInvalidationInterceptor.class);
|
||||||
|
cache.addInterceptor(txInvalidationInterceptor, invalidationPosition);
|
||||||
|
|
||||||
// Note that invalidation does *NOT* acquire locks; therefore, we have to start invalidating before
|
// Note that invalidation does *NOT* acquire locks; therefore, we have to start invalidating before
|
||||||
// wrapping the entry, since if putFromLoad was invoked between wrap and beginInvalidatingKey, the invalidation
|
// wrapping the entry, since if putFromLoad was invoked between wrap and beginInvalidatingKey, the invalidation
|
||||||
// would not commit the entry removal (as during wrap the entry was not in cache)
|
// would not commit the entry removal (as during wrap the entry was not in cache)
|
||||||
|
@ -223,6 +228,13 @@ public class PutFromLoadValidator {
|
||||||
cache.removeInterceptor(NonTxInvalidationInterceptor.class);
|
cache.removeInterceptor(NonTxInvalidationInterceptor.class);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
else if (i instanceof TxInvalidationInterceptor) {
|
||||||
|
InvalidationInterceptor invalidationInterceptor = new InvalidationInterceptor();
|
||||||
|
cache.getComponentRegistry().registerComponent(invalidationInterceptor, InvalidationInterceptor.class);
|
||||||
|
cache.addInterceptorBefore(invalidationInterceptor, TxInvalidationInterceptor.class);
|
||||||
|
cache.removeInterceptor(TxInvalidationInterceptor.class);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
CacheCommandInitializer cci = cache.getComponentRegistry().getComponent(CacheCommandInitializer.class);
|
CacheCommandInitializer cci = cache.getComponentRegistry().getComponent(CacheCommandInitializer.class);
|
||||||
cci.removePutFromLoadValidator(cache.getName());
|
cci.removePutFromLoadValidator(cache.getName());
|
||||||
|
|
|
@ -0,0 +1,300 @@
|
||||||
|
/*
|
||||||
|
* 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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||||
|
*/
|
||||||
|
package org.hibernate.cache.infinispan.access;
|
||||||
|
|
||||||
|
import org.infinispan.commands.AbstractVisitor;
|
||||||
|
import org.infinispan.commands.CommandsFactory;
|
||||||
|
import org.infinispan.commands.FlagAffectedCommand;
|
||||||
|
import org.infinispan.commands.ReplicableCommand;
|
||||||
|
import org.infinispan.commands.control.LockControlCommand;
|
||||||
|
import org.infinispan.commands.tx.PrepareCommand;
|
||||||
|
import org.infinispan.commands.write.ClearCommand;
|
||||||
|
import org.infinispan.commands.write.InvalidateCommand;
|
||||||
|
import org.infinispan.commands.write.PutKeyValueCommand;
|
||||||
|
import org.infinispan.commands.write.PutMapCommand;
|
||||||
|
import org.infinispan.commands.write.RemoveCommand;
|
||||||
|
import org.infinispan.commands.write.ReplaceCommand;
|
||||||
|
import org.infinispan.commands.write.WriteCommand;
|
||||||
|
import org.infinispan.commons.util.InfinispanCollections;
|
||||||
|
import org.infinispan.context.Flag;
|
||||||
|
import org.infinispan.context.InvocationContext;
|
||||||
|
import org.infinispan.context.impl.LocalTxInvocationContext;
|
||||||
|
import org.infinispan.context.impl.TxInvocationContext;
|
||||||
|
import org.infinispan.factories.annotations.Inject;
|
||||||
|
import org.infinispan.factories.annotations.Start;
|
||||||
|
import org.infinispan.interceptors.base.BaseRpcInterceptor;
|
||||||
|
import org.infinispan.jmx.JmxStatisticsExposer;
|
||||||
|
import org.infinispan.jmx.annotations.DataType;
|
||||||
|
import org.infinispan.jmx.annotations.MBean;
|
||||||
|
import org.infinispan.jmx.annotations.ManagedAttribute;
|
||||||
|
import org.infinispan.jmx.annotations.ManagedOperation;
|
||||||
|
import org.infinispan.jmx.annotations.MeasurementType;
|
||||||
|
import org.infinispan.jmx.annotations.Parameter;
|
||||||
|
import org.infinispan.util.logging.Log;
|
||||||
|
import org.infinispan.util.logging.LogFactory;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This interceptor acts as a replacement to the replication interceptor when the CacheImpl is configured with
|
||||||
|
* ClusteredSyncMode as INVALIDATE.
|
||||||
|
* <p/>
|
||||||
|
* The idea is that rather than replicating changes to all caches in a cluster when write methods are called, simply
|
||||||
|
* broadcast an {@link InvalidateCommand} on the remote caches containing all keys modified. This allows the remote
|
||||||
|
* cache to look up the value in a shared cache loader which would have been updated with the changes.
|
||||||
|
*
|
||||||
|
* @author Manik Surtani
|
||||||
|
* @author Galder Zamarreño
|
||||||
|
* @author Mircea.Markus@jboss.com
|
||||||
|
* @since 4.0
|
||||||
|
*/
|
||||||
|
@MBean(objectName = "Invalidation", description = "Component responsible for invalidating entries on remote caches when entries are written to locally.")
|
||||||
|
public class TxInvalidationInterceptor extends BaseRpcInterceptor implements JmxStatisticsExposer {
|
||||||
|
|
||||||
|
private final AtomicLong invalidations = new AtomicLong( 0 );
|
||||||
|
private CommandsFactory commandsFactory;
|
||||||
|
private boolean statisticsEnabled;
|
||||||
|
|
||||||
|
private static final Log log = LogFactory.getLog( TxInvalidationInterceptor.class );
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Log getLog() {
|
||||||
|
return log;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public void injectDependencies(CommandsFactory commandsFactory) {
|
||||||
|
this.commandsFactory = commandsFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Start
|
||||||
|
private void start() {
|
||||||
|
this.setStatisticsEnabled( cacheConfiguration.jmxStatistics().enabled() );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object visitPutKeyValueCommand(InvocationContext ctx, PutKeyValueCommand command) throws Throwable {
|
||||||
|
if ( !isPutForExternalRead( command ) ) {
|
||||||
|
return handleInvalidate( ctx, command, command.getKey() );
|
||||||
|
}
|
||||||
|
return invokeNextInterceptor( ctx, command );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object visitReplaceCommand(InvocationContext ctx, ReplaceCommand command) throws Throwable {
|
||||||
|
return handleInvalidate( ctx, command, command.getKey() );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object visitRemoveCommand(InvocationContext ctx, RemoveCommand command) throws Throwable {
|
||||||
|
return handleInvalidate( ctx, command, command.getKey() );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object visitClearCommand(InvocationContext ctx, ClearCommand command) throws Throwable {
|
||||||
|
Object retval = invokeNextInterceptor( ctx, command );
|
||||||
|
if ( !isLocalModeForced( command ) ) {
|
||||||
|
// just broadcast the clear command - this is simplest!
|
||||||
|
if ( ctx.isOriginLocal() ) {
|
||||||
|
rpcManager.invokeRemotely( null, command, rpcManager.getDefaultRpcOptions( defaultSynchronous ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object visitPutMapCommand(InvocationContext ctx, PutMapCommand command) throws Throwable {
|
||||||
|
Object[] keys = command.getMap() == null ? null : command.getMap().keySet().toArray();
|
||||||
|
return handleInvalidate( ctx, command, keys );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object visitPrepareCommand(TxInvocationContext ctx, PrepareCommand command) throws Throwable {
|
||||||
|
Object retval = invokeNextInterceptor( ctx, command );
|
||||||
|
log.tracef( "Entering InvalidationInterceptor's prepare phase. Ctx flags are empty" );
|
||||||
|
// fetch the modifications before the transaction is committed (and thus removed from the txTable)
|
||||||
|
if ( shouldInvokeRemoteTxCommand( ctx ) ) {
|
||||||
|
if ( ctx.getTransaction() == null ) {
|
||||||
|
throw new IllegalStateException( "We must have an associated transaction" );
|
||||||
|
}
|
||||||
|
|
||||||
|
List<WriteCommand> mods = Arrays.asList( command.getModifications() );
|
||||||
|
broadcastInvalidateForPrepare( mods, ctx );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
log.tracef( "Nothing to invalidate - no modifications in the transaction." );
|
||||||
|
}
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object visitLockControlCommand(TxInvocationContext ctx, LockControlCommand command) throws Throwable {
|
||||||
|
Object retVal = invokeNextInterceptor( ctx, command );
|
||||||
|
if ( ctx.isOriginLocal() ) {
|
||||||
|
//unlock will happen async as it is a best effort
|
||||||
|
boolean sync = !command.isUnlock();
|
||||||
|
( (LocalTxInvocationContext) ctx ).remoteLocksAcquired( rpcManager.getTransport().getMembers() );
|
||||||
|
rpcManager.invokeRemotely( null, command, rpcManager.getDefaultRpcOptions( sync ) );
|
||||||
|
}
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object handleInvalidate(InvocationContext ctx, WriteCommand command, Object... keys) throws Throwable {
|
||||||
|
Object retval = invokeNextInterceptor( ctx, command );
|
||||||
|
if ( command.isSuccessful() && !ctx.isInTxScope() ) {
|
||||||
|
if ( keys != null && keys.length != 0 ) {
|
||||||
|
if ( !isLocalModeForced( command ) ) {
|
||||||
|
invalidateAcrossCluster( isSynchronous( command ), keys, ctx );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void broadcastInvalidateForPrepare(List<WriteCommand> modifications, InvocationContext ctx) throws Throwable {
|
||||||
|
// A prepare does not carry flags, so skip checking whether is local or not
|
||||||
|
if ( ctx.isInTxScope() ) {
|
||||||
|
if ( modifications.isEmpty() ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
InvalidationFilterVisitor filterVisitor = new InvalidationFilterVisitor( modifications.size() );
|
||||||
|
filterVisitor.visitCollection( null, modifications );
|
||||||
|
|
||||||
|
if ( filterVisitor.containsPutForExternalRead ) {
|
||||||
|
log.debug( "Modification list contains a putForExternalRead operation. Not invalidating." );
|
||||||
|
}
|
||||||
|
else if ( filterVisitor.containsLocalModeFlag ) {
|
||||||
|
log.debug( "Modification list contains a local mode flagged operation. Not invalidating." );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
try {
|
||||||
|
invalidateAcrossCluster( defaultSynchronous, filterVisitor.result.toArray(), ctx );
|
||||||
|
}
|
||||||
|
catch (Throwable t) {
|
||||||
|
log.unableToRollbackEvictionsDuringPrepare( t );
|
||||||
|
if ( t instanceof RuntimeException ) {
|
||||||
|
throw (RuntimeException) t;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new RuntimeException( "Unable to broadcast invalidation messages", t );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class InvalidationFilterVisitor extends AbstractVisitor {
|
||||||
|
|
||||||
|
Set<Object> result;
|
||||||
|
public boolean containsPutForExternalRead = false;
|
||||||
|
public boolean containsLocalModeFlag = false;
|
||||||
|
|
||||||
|
public InvalidationFilterVisitor(int maxSetSize) {
|
||||||
|
result = new HashSet<Object>( maxSetSize );
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processCommand(FlagAffectedCommand command) {
|
||||||
|
containsLocalModeFlag = containsLocalModeFlag || ( command.getFlags() != null && command.getFlags().contains( Flag.CACHE_MODE_LOCAL ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object visitPutKeyValueCommand(InvocationContext ctx, PutKeyValueCommand command) throws Throwable {
|
||||||
|
processCommand( command );
|
||||||
|
containsPutForExternalRead =
|
||||||
|
containsPutForExternalRead || ( command.getFlags() != null && command.getFlags().contains( Flag.PUT_FOR_EXTERNAL_READ ) );
|
||||||
|
result.add( command.getKey() );
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object visitRemoveCommand(InvocationContext ctx, RemoveCommand command) throws Throwable {
|
||||||
|
processCommand( command );
|
||||||
|
result.add( command.getKey() );
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object visitPutMapCommand(InvocationContext ctx, PutMapCommand command) throws Throwable {
|
||||||
|
processCommand( command );
|
||||||
|
result.addAll( command.getAffectedKeys() );
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void invalidateAcrossCluster(boolean synchronous, Object[] keys, InvocationContext ctx) throws Throwable {
|
||||||
|
// increment invalidations counter if statistics maintained
|
||||||
|
incrementInvalidations();
|
||||||
|
final InvalidateCommand invalidateCommand = commandsFactory.buildInvalidateCommand( InfinispanCollections.<Flag>emptySet(), keys );
|
||||||
|
if ( log.isDebugEnabled() ) {
|
||||||
|
log.debug( "Cache [" + rpcManager.getAddress() + "] replicating " + invalidateCommand );
|
||||||
|
}
|
||||||
|
|
||||||
|
ReplicableCommand command = invalidateCommand;
|
||||||
|
if ( ctx.isInTxScope() ) {
|
||||||
|
TxInvocationContext txCtx = (TxInvocationContext) ctx;
|
||||||
|
// A Prepare command containing the invalidation command in its 'modifications' list is sent to the remote nodes
|
||||||
|
// so that the invalidation is executed in the same transaction and locks can be acquired and released properly.
|
||||||
|
// This is 1PC on purpose, as an optimisation, even if the current TX is 2PC.
|
||||||
|
// If the cache uses 2PC it's possible that the remotes will commit the invalidation and the originator rolls back,
|
||||||
|
// but this does not impact consistency and the speed benefit is worth it.
|
||||||
|
command = commandsFactory.buildPrepareCommand( txCtx.getGlobalTransaction(), Collections.<WriteCommand>singletonList( invalidateCommand ), true );
|
||||||
|
}
|
||||||
|
rpcManager.invokeRemotely( null, command, rpcManager.getDefaultRpcOptions( synchronous ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
private void incrementInvalidations() {
|
||||||
|
if ( statisticsEnabled ) {
|
||||||
|
invalidations.incrementAndGet();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isPutForExternalRead(FlagAffectedCommand command) {
|
||||||
|
if ( command.hasFlag( Flag.PUT_FOR_EXTERNAL_READ ) ) {
|
||||||
|
log.trace( "Put for external read called. Suppressing clustered invalidation." );
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ManagedOperation(
|
||||||
|
description = "Resets statistics gathered by this component",
|
||||||
|
displayName = "Reset statistics"
|
||||||
|
)
|
||||||
|
public void resetStatistics() {
|
||||||
|
invalidations.set( 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
@ManagedAttribute(
|
||||||
|
displayName = "Statistics enabled",
|
||||||
|
description = "Enables or disables the gathering of statistics by this component",
|
||||||
|
dataType = DataType.TRAIT,
|
||||||
|
writable = true
|
||||||
|
)
|
||||||
|
public boolean getStatisticsEnabled() {
|
||||||
|
return this.statisticsEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatisticsEnabled(
|
||||||
|
@Parameter(name = "enabled", description = "Whether statistics should be enabled or disabled (true/false)") boolean enabled) {
|
||||||
|
this.statisticsEnabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ManagedAttribute(
|
||||||
|
description = "Number of invalidations",
|
||||||
|
displayName = "Number of invalidations",
|
||||||
|
measurementType = MeasurementType.TRENDSUP
|
||||||
|
)
|
||||||
|
public long getInvalidations() {
|
||||||
|
return invalidations.get();
|
||||||
|
}
|
||||||
|
}
|
|
@ -84,11 +84,18 @@ class TxPutFromLoadInterceptor extends BaseRpcInterceptor {
|
||||||
if (wc instanceof InvalidateCommand) {
|
if (wc instanceof InvalidateCommand) {
|
||||||
// ISPN-5605 InvalidateCommand does not correctly implement getAffectedKeys()
|
// ISPN-5605 InvalidateCommand does not correctly implement getAffectedKeys()
|
||||||
for (Object key : ((InvalidateCommand) wc).getKeys()) {
|
for (Object key : ((InvalidateCommand) wc).getKeys()) {
|
||||||
|
if (log.isTraceEnabled()) {
|
||||||
|
log.tracef("Invalidating key %s with lock owner %s", key, ctx.getLockOwner());
|
||||||
|
}
|
||||||
putFromLoadValidator.beginInvalidatingKey(ctx.getLockOwner(), key);
|
putFromLoadValidator.beginInvalidatingKey(ctx.getLockOwner(), key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
for (Object key : wc.getAffectedKeys()) {
|
Set<Object> keys = wc.getAffectedKeys();
|
||||||
|
if (log.isTraceEnabled()) {
|
||||||
|
log.tracef("Invalidating keys %s with lock owner %s", keys, ctx.getLockOwner());
|
||||||
|
}
|
||||||
|
for (Object key : keys ) {
|
||||||
putFromLoadValidator.beginInvalidatingKey(ctx.getLockOwner(), key);
|
putFromLoadValidator.beginInvalidatingKey(ctx.getLockOwner(), key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -99,11 +106,19 @@ class TxPutFromLoadInterceptor extends BaseRpcInterceptor {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object visitCommitCommand(TxInvocationContext ctx, CommitCommand command) throws Throwable {
|
public Object visitCommitCommand(TxInvocationContext ctx, CommitCommand command) throws Throwable {
|
||||||
|
if (log.isTraceEnabled()) {
|
||||||
|
log.tracef( "Commit command received, end invalidation" );
|
||||||
|
}
|
||||||
|
|
||||||
return endInvalidationAndInvokeNextInterceptor(ctx, command);
|
return endInvalidationAndInvokeNextInterceptor(ctx, command);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object visitRollbackCommand(TxInvocationContext ctx, RollbackCommand command) throws Throwable {
|
public Object visitRollbackCommand(TxInvocationContext ctx, RollbackCommand command) throws Throwable {
|
||||||
|
if (log.isTraceEnabled()) {
|
||||||
|
log.tracef( "Rollback command received, end invalidation" );
|
||||||
|
}
|
||||||
|
|
||||||
return endInvalidationAndInvokeNextInterceptor(ctx, command);
|
return endInvalidationAndInvokeNextInterceptor(ctx, command);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,6 +127,11 @@ class TxPutFromLoadInterceptor extends BaseRpcInterceptor {
|
||||||
if (ctx.isOriginLocal()) {
|
if (ctx.isOriginLocal()) {
|
||||||
// send async Commit
|
// send async Commit
|
||||||
Set<Object> affectedKeys = ctx.getAffectedKeys();
|
Set<Object> affectedKeys = ctx.getAffectedKeys();
|
||||||
|
|
||||||
|
if (log.isTraceEnabled()) {
|
||||||
|
log.tracef( "Sending end invalidation for keys %s asynchronously", affectedKeys );
|
||||||
|
}
|
||||||
|
|
||||||
if (!affectedKeys.isEmpty()) {
|
if (!affectedKeys.isEmpty()) {
|
||||||
EndInvalidationCommand commitCommand = cacheCommandInitializer.buildEndInvalidationCommand(
|
EndInvalidationCommand commitCommand = cacheCommandInitializer.buildEndInvalidationCommand(
|
||||||
cacheName, affectedKeys.toArray(), ctx.getGlobalTransaction());
|
cacheName, affectedKeys.toArray(), ctx.getGlobalTransaction());
|
||||||
|
|
|
@ -47,6 +47,7 @@ import java.util.concurrent.CountDownLatch;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ -334,7 +335,7 @@ public abstract class AbstractRegionAccessStrategyTest<R extends BaseRegion, S e
|
||||||
|
|
||||||
// Test whether the get above messes up the optimistic version
|
// Test whether the get above messes up the optimistic version
|
||||||
SessionImplementor s9 = mockedSession();
|
SessionImplementor s9 = mockedSession();
|
||||||
remoteAccessStrategy.putFromLoad(s9, KEY, VALUE1, s9.getTimestamp(), 1);
|
assertTrue(remoteAccessStrategy.putFromLoad(s9, KEY, VALUE1, s9.getTimestamp(), 1));
|
||||||
SessionImplementor s10 = mockedSession();
|
SessionImplementor s10 = mockedSession();
|
||||||
assertEquals(VALUE1, remoteAccessStrategy.get(s10, KEY, s10.getTimestamp()));
|
assertEquals(VALUE1, remoteAccessStrategy.get(s10, KEY, s10.getTimestamp()));
|
||||||
assertEquals(1, remoteRegion.getCache().size());
|
assertEquals(1, remoteRegion.getCache().size());
|
||||||
|
|
Loading…
Reference in New Issue