All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.infinispan.interceptors.distribution.VersionedDistributionInterceptor Maven / Gradle / Ivy

package org.infinispan.interceptors.distribution;

import static org.infinispan.transaction.impl.WriteSkewHelper.readVersionsFromResponse;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;

import org.infinispan.commands.tx.PrepareCommand;
import org.infinispan.container.entries.CacheEntry;
import org.infinispan.container.versioning.EntryVersion;
import org.infinispan.container.versioning.InequalVersionComparisonResult;
import org.infinispan.container.versioning.VersionGenerator;
import org.infinispan.context.InvocationContext;
import org.infinispan.context.impl.TxInvocationContext;
import org.infinispan.factories.annotations.Inject;
import org.infinispan.interceptors.InvocationStage;
import org.infinispan.metadata.Metadata;
import org.infinispan.remoting.responses.Response;
import org.infinispan.remoting.transport.Address;
import org.infinispan.transaction.impl.AbstractCacheTransaction;
import org.infinispan.transaction.impl.LocalTransaction;
import org.infinispan.transaction.xa.CacheTransaction;
import org.infinispan.util.concurrent.CompletableFutures;
import org.infinispan.util.logging.Log;
import org.infinispan.util.logging.LogFactory;

/**
 * A version of the {@link TxDistributionInterceptor} that adds logic to handling prepares when entries are
 * versioned.
 *
 * @author Manik Surtani
 * @author Dan Berindei
 */
public class VersionedDistributionInterceptor extends TxDistributionInterceptor {

   private static final Log log = LogFactory.getLog(VersionedDistributionInterceptor.class);

   private VersionGenerator versionGenerator;

   @Inject
   public void init(VersionGenerator versionGenerator) {
      this.versionGenerator = versionGenerator;
   }

   @Override
   protected Log getLog() {
      return log;
   }

   @Override
   protected void wrapRemoteEntry(InvocationContext ctx, Object key, CacheEntry ice, boolean isWrite) {
      if (ctx.isInTxScope()) {
         EntryVersion seenVersion = ((TxInvocationContext) ctx).getCacheTransaction().getVersionsRead().get(key);
         if (seenVersion != null) {
            EntryVersion newVersion = null;
            if (ice != null && ice.getMetadata() != null) {
               newVersion = ice.getMetadata().version();
            }
            if (newVersion == null) {
               throw new IllegalStateException("Wrapping entry without version");
            }
            if (seenVersion.compareTo(newVersion) != InequalVersionComparisonResult.EQUAL) {
               throw log.writeSkewOnRead(key, key, seenVersion, newVersion);
            }
         }
      }
      super.wrapRemoteEntry(ctx, key, ice, isWrite);
   }

   @Override
   protected InvocationStage wrapFunctionalResultOnNonOriginOnReturn(InvocationStage stage, CacheEntry entry) {
      return stage.thenApply((rCtx, rCommand, rv) -> {
         Metadata metadata = entry.getMetadata();
         EntryVersion version = metadata == null || metadata.version() == null ?
               versionGenerator.nonExistingVersion() : metadata.version();
         return new VersionedResult(rv, version);
      });
   }

   @Override
   protected Object wrapFunctionalManyResultOnNonOrigin(InvocationContext ctx, Collection keys, Object[] values) {
      // note: this relies on the fact that keys are already ordered on remote node
      EntryVersion[] versions = new EntryVersion[keys.size()];
      int i = 0;
      for (Object key : keys) {
         CacheEntry entry = ctx.lookupEntry(key);
         Metadata metadata = entry.getMetadata();
         versions[i++] = metadata == null || metadata.version() == null ?
               versionGenerator.nonExistingVersion() : metadata.version();
      }
      return new VersionedResults(values, versions);
   }

   @Override
   protected Object[] unwrapFunctionalManyResultOnOrigin(InvocationContext ctx, List keys, Object responseValue) {
      if (responseValue instanceof VersionedResults) {
         VersionedResults vrs = (VersionedResults) responseValue;
         if (ctx.isInTxScope()) {
            AbstractCacheTransaction tx = ((TxInvocationContext) ctx).getCacheTransaction();
            for (int i = 0; i < vrs.versions.length; ++i) {
               checkAndAddReadVersion(tx, keys.get(i), vrs.versions[i]);
            }
         }
         return vrs.values;
      } else {
         return null;
      }
   }

   @Override
   protected Object unwrapFunctionalResultOnOrigin(InvocationContext ctx, Object key, Object responseValue) {
      VersionedResult vr = (VersionedResult) responseValue;
      // As an optimization, read-only single-key commands are executed in SingleKeyNonTxInvocationContext
      if (ctx.isInTxScope()) {
         AbstractCacheTransaction tx = ((TxInvocationContext) ctx).getCacheTransaction();
         checkAndAddReadVersion(tx, key, vr.version);
      }
      return vr.result;
   }

   private void checkAndAddReadVersion(AbstractCacheTransaction tx, Object key, EntryVersion version) {
      EntryVersion lastVersion = tx.getLookedUpRemoteVersion(key);
      // TODO: should we check the write skew configuration here?
      // TODO: version seen or looked up remote version?
      if (lastVersion != null && lastVersion.compareTo(version) != InequalVersionComparisonResult.EQUAL) {
         throw log.writeSkewOnRead(key, key, lastVersion, version);
      }
      EntryVersion lastVersionSeen = tx.getVersionsRead().get(key);
      if (lastVersionSeen != null && lastVersionSeen.compareTo(version) != InequalVersionComparisonResult.EQUAL) {
         throw log.writeSkewOnRead(key, key, lastVersionSeen, version);
      }
      tx.addVersionRead(key, version);
   }

   @Override
   protected CompletableFuture prepareOnAffectedNodes(TxInvocationContext ctx, PrepareCommand command, Collection
recipients) { // Perform the RPC CompletableFuture> remoteInvocation = rpcManager.invokeRemotelyAsync(recipients, command, createPrepareRpcOptions()); return remoteInvocation.handle((responses, t) -> { transactionRemotelyPrepared(ctx); CompletableFutures.rethrowException(t); checkTxCommandResponses(responses, command, (TxInvocationContext) ctx, recipients); // Now store newly generated versions from lock owners for use during the commit phase. CacheTransaction ct = ctx.getCacheTransaction(); for (Response r : responses.values()) readVersionsFromResponse(r, ct); return null; }); } }