/*
* 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.write.PutKeyValueCommand;
import org.infinispan.context.Flag;
import org.infinispan.context.InvocationContext;
import org.infinispan.distribution.DistributionManager;
import org.infinispan.distribution.ch.ConsistentHash;
import org.infinispan.factories.annotations.Inject;
import org.infinispan.factories.annotations.Start;
import org.infinispan.interceptors.distribution.NonTxDistributionInterceptor;
import org.infinispan.remoting.inboundhandler.DeliverOrder;
import org.infinispan.remoting.rpc.ResponseMode;
import org.infinispan.remoting.rpc.RpcOptions;
import org.infinispan.remoting.transport.Address;
import org.infinispan.statetransfer.OutdatedTopologyException;
import org.infinispan.util.logging.Log;
import org.infinispan.util.logging.LogFactory;
import java.util.List;
/**
* Since the data handled in {@link TombstoneCallInterceptor} or {@link VersionedCallInterceptor}
* does not rely on the order how these are applied (the updates are commutative), this interceptor
* simply sends any command to all other owners without ordering them through primary owner.
* Note that {@link LockingInterceptor} is required in the stack as locking on backup is not guaranteed
* by primary owner.
*/
public class UnorderedDistributionInterceptor extends NonTxDistributionInterceptor {
private static Log log = LogFactory.getLog(UnorderedDistributionInterceptor.class);
private static final boolean trace = log.isTraceEnabled();
private DistributionManager distributionManager;
private RpcOptions syncRpcOptions, asyncRpcOptions;
@Inject
public void inject(DistributionManager distributionManager) {
this.distributionManager = distributionManager;
}
@Start
public void start() {
syncRpcOptions = rpcManager.getRpcOptionsBuilder(ResponseMode.SYNCHRONOUS_IGNORE_LEAVERS, DeliverOrder.NONE).build();
// We don't have to guarantee ordering even for asynchronous messages
asyncRpcOptions = rpcManager.getRpcOptionsBuilder(ResponseMode.ASYNCHRONOUS, DeliverOrder.NONE).build();
}
@Override
public Object visitPutKeyValueCommand(InvocationContext ctx, PutKeyValueCommand command) throws Throwable {
if (command.hasFlag(Flag.CACHE_MODE_LOCAL)) {
// for state-transfer related writes
return invokeNextInterceptor(ctx, command);
}
int commandTopologyId = command.getTopologyId();
int currentTopologyId = stateTransferManager.getCacheTopology().getTopologyId();
if (commandTopologyId != -1 && currentTopologyId != commandTopologyId) {
throw new OutdatedTopologyException("Cache topology changed while the command was executing: expected " +
commandTopologyId + ", got " + currentTopologyId);
}
ConsistentHash writeCH = distributionManager.getWriteConsistentHash();
List<Address> owners = null;
if (writeCH.isReplicated()) {
// local result is always ignored
invokeNextInterceptor(ctx, command);
}
else {
owners = writeCH.locateOwners(command.getKey());
if (owners.contains(rpcManager.getAddress())) {
invokeNextInterceptor(ctx, command);
}
else {
log.tracef("Not invoking %s on %s since it is not an owner", command, rpcManager.getAddress());
}
}
if (ctx.isOriginLocal() && command.isSuccessful()) {
// This is called with the entry locked. In order to avoid deadlocks we must not wait for RPC while
// holding the lock, therefore we'll return a future and wait for it in LockingInterceptor after
// unlocking (and committing) the entry.
return rpcManager.invokeRemotelyAsync(owners, command, isSynchronous(command) ? syncRpcOptions : asyncRpcOptions);
}
return null;
}
}