/**
   * @param nodeId Primary node ID.
   * @param req Request.
   * @return Remote transaction.
   * @throws GridException If failed.
   * @throws GridDistributedLockCancelledException If lock has been cancelled.
   */
  @SuppressWarnings({"RedundantTypeArguments"})
  @Nullable
  public GridNearTxRemote<K, V> startRemoteTxForFinish(
      UUID nodeId, GridDhtTxFinishRequest<K, V> req)
      throws GridException, GridDistributedLockCancelledException {
    GridNearTxRemote<K, V> tx = null;

    ClassLoader ldr = ctx.deploy().globalLoader();

    if (ldr != null) {
      for (GridCacheTxEntry<K, V> txEntry : req.nearWrites()) {
        GridDistributedCacheEntry<K, V> entry = null;

        while (true) {
          try {
            entry = peekExx(txEntry.key());

            if (entry != null) {
              entry.keyBytes(txEntry.keyBytes());

              // Handle implicit locks for pessimistic transactions.
              tx = ctx.tm().tx(req.version());

              if (tx != null) {
                if (tx.local()) return null;

                if (tx.markFinalizing()) tx.addWrite(txEntry.key(), txEntry.keyBytes());
                else return null;
              } else {
                tx =
                    new GridNearTxRemote<K, V>(
                        nodeId,
                        req.nearNodeId(),
                        req.threadId(),
                        req.version(),
                        null,
                        PESSIMISTIC,
                        req.isolation(),
                        req.isInvalidate(),
                        0,
                        txEntry.key(),
                        txEntry.keyBytes(),
                        txEntry.value(),
                        txEntry.valueBytes(),
                        ctx);

                if (tx.empty()) return tx;

                tx = ctx.tm().onCreated(tx);

                if (tx == null || !ctx.tm().onStarted(tx))
                  throw new GridCacheTxRollbackException(
                      "Failed to acquire lock "
                          + "(transaction has been completed): "
                          + req.version());

                if (!tx.markFinalizing()) return null;
              }

              // Add remote candidate before reordering.
              if (txEntry.explicitVersion() == null)
                entry.addRemote(
                    req.nearNodeId(),
                    nodeId,
                    req.threadId(),
                    req.version(),
                    0,
                    tx.ec(),
                    /*tx*/ true,
                    tx.implicitSingle());

              // Remote candidates for ordered lock queuing.
              entry.addRemoteCandidates(
                  Collections.<GridCacheMvccCandidate<K>>emptyList(),
                  req.version(),
                  req.committedVersions(),
                  req.rolledbackVersions());
            }

            // Double-check in case if sender node left the grid.
            if (ctx.discovery().node(req.nearNodeId()) == null) {
              if (log.isDebugEnabled())
                log.debug("Node requesting lock left grid (lock request will be ignored): " + req);

              if (tx != null) tx.rollback();

              return null;
            }

            // Entry is legit.
            break;
          } catch (GridCacheEntryRemovedException ignored) {
            assert entry.obsoleteVersion() != null
                : "Obsolete flag not set on removed entry: " + entry;

            if (log.isDebugEnabled())
              log.debug("Received entry removed exception (will retry on renewed entry): " + entry);

            if (tx != null) {
              tx.clearEntry(entry.key());

              if (log.isDebugEnabled())
                log.debug(
                    "Cleared removed entry from remote transaction (will retry) [entry="
                        + entry
                        + ", tx="
                        + tx
                        + ']');
            }
          }
        }
      }
    } else {
      String err = "Failed to acquire deployment class loader for message: " + req;

      U.warn(log, err);

      throw new GridException(err);
    }

    return tx;
  }
Example #2
0
 /** Clears all readers (usually when partition becomes invalid and ready for eviction). */
 @Override
 public void clearReaders() {
   synchronized (mux) {
     readers = Collections.emptyList();
   }
 }
Example #3
0
/**
 * Task session.
 *
 * @author 2012 Copyright (C) GridGain Systems
 * @version 4.0.2c.12042012
 */
public class GridTaskSessionImpl extends GridMetadataAwareAdapter
    implements GridTaskSessionInternal {
  /** */
  private final String taskName;

  /** */
  private final GridDeployment dep;

  /** */
  private final String taskClsName;

  /** */
  private final GridUuid sesId;

  /** */
  private final long startTime;

  /** */
  private final long endTime;

  /** */
  private final UUID taskNodeId;

  /** */
  private final GridKernalContext ctx;

  /** */
  private Collection<GridJobSibling> siblings;

  /** */
  private final Map<Object, Object> attrs = new HashMap<Object, Object>(1);

  /** */
  private List<GridTaskSessionAttributeListener> lsnrs = Collections.emptyList();

  /** */
  private ClassLoader clsLdr;

  /** */
  private boolean closed;

  /** */
  private String topSpi;

  /** */
  private String cpSpi;

  /** */
  private String failSpi;

  /** */
  private String loadSpi;

  /** */
  private final Object mux = new Object();

  /**
   * @param taskNodeId Task node ID.
   * @param taskName Task name.
   * @param dep Deployment.
   * @param taskClsName Task class name.
   * @param sesId Task session ID.
   * @param startTime Task execution start time.
   * @param endTime Task execution end time.
   * @param siblings Collection of siblings.
   * @param attrs Session attributes.
   * @param ctx Grid Kernal Context.
   */
  public GridTaskSessionImpl(
      UUID taskNodeId,
      String taskName,
      @Nullable GridDeployment dep,
      String taskClsName,
      GridUuid sesId,
      long startTime,
      long endTime,
      Collection<GridJobSibling> siblings,
      Map<Object, Object> attrs,
      GridKernalContext ctx) {
    assert taskNodeId != null;
    assert taskName != null;
    assert sesId != null;
    assert attrs != null;
    assert ctx != null;

    this.taskNodeId = taskNodeId;
    this.taskName = taskName;
    this.dep = dep;

    // Note that class name might be null here if task was not explicitly
    // deployed.
    this.taskClsName = taskClsName;
    this.sesId = sesId;
    this.startTime = startTime;
    this.endTime = endTime;
    this.siblings = siblings != null ? Collections.unmodifiableCollection(siblings) : null;
    this.ctx = ctx;

    this.attrs.putAll(attrs);
  }

  /** {@inheritDoc} */
  @Override
  public GridTaskSessionInternal session() {
    return this;
  }

  /** {@inheritDoc} */
  @Nullable
  @Override
  public GridUuid getJobId() {
    return null;
  }

  /** {@inheritDoc} */
  @Override
  public void onClosed() {
    synchronized (mux) {
      closed = true;

      mux.notifyAll();
    }
  }

  /** {@inheritDoc} */
  @Override
  public boolean isClosed() {
    synchronized (mux) {
      return closed;
    }
  }

  /** @return Task node ID. */
  @Override
  public UUID getTaskNodeId() {
    return taskNodeId;
  }

  /** {@inheritDoc} */
  @Override
  public long getStartTime() {
    return startTime;
  }

  /** {@inheritDoc} */
  @SuppressWarnings("unchecked")
  @Override
  public <K, V> V waitForAttribute(K key) throws InterruptedException {
    return (V) waitForAttribute(key, 0);
  }

  /** {@inheritDoc} */
  @Override
  public boolean waitForAttribute(Object key, Object val) throws InterruptedException {
    return waitForAttribute(key, val, 0);
  }

  /** {@inheritDoc} */
  @SuppressWarnings("unchecked")
  @Override
  public <K, V> V waitForAttribute(K key, long timeout) throws InterruptedException {
    A.notNull(key, "key");

    if (timeout == 0) timeout = Long.MAX_VALUE;

    long now = System.currentTimeMillis();

    // Prevent overflow.
    long end = now + timeout < 0 ? Long.MAX_VALUE : now + timeout;

    // Don't wait longer than session timeout.
    if (end > endTime) end = endTime;

    synchronized (mux) {
      while (!closed && !attrs.containsKey(key) && now < end) {
        mux.wait(end - now);

        now = System.currentTimeMillis();
      }

      if (closed) throw new InterruptedException("Session was closed: " + this);

      return (V) attrs.get(key);
    }
  }

  /** {@inheritDoc} */
  @Override
  public boolean waitForAttribute(Object key, Object val, long timeout)
      throws InterruptedException {
    A.notNull(key, "key");

    if (timeout == 0) timeout = Long.MAX_VALUE;

    long now = System.currentTimeMillis();

    // Prevent overflow.
    long end = now + timeout < 0 ? Long.MAX_VALUE : now + timeout;

    // Don't wait longer than session timeout.
    if (end > endTime) end = endTime;

    synchronized (mux) {
      boolean isFound = false;

      while (!closed && !(isFound = isAttributeSet(key, val)) && now < end) {
        mux.wait(end - now);

        now = System.currentTimeMillis();
      }

      if (closed) throw new InterruptedException("Session was closed: " + this);

      return isFound;
    }
  }

  /**
   * {@inheritDoc}
   *
   * @param keys Attribute keys.
   */
  @Override
  public Map<?, ?> waitForAttributes(Collection<?> keys) throws InterruptedException {
    return waitForAttributes(keys, 0);
  }

  /** {@inheritDoc} */
  @Override
  public boolean waitForAttributes(Map<?, ?> attrs) throws InterruptedException {
    return waitForAttributes(attrs, 0);
  }

  /** {@inheritDoc} */
  @Override
  public Map<?, ?> waitForAttributes(Collection<?> keys, long timeout) throws InterruptedException {
    A.notNull(keys, "keys");

    if (keys.isEmpty()) return Collections.emptyMap();

    if (timeout == 0) timeout = Long.MAX_VALUE;

    long now = System.currentTimeMillis();

    // Prevent overflow.
    long end = now + timeout < 0 ? Long.MAX_VALUE : now + timeout;

    // Don't wait longer than session timeout.
    if (end > endTime) end = endTime;

    synchronized (mux) {
      while (!closed && !attrs.keySet().containsAll(keys) && now < end) {
        mux.wait(end - now);

        now = System.currentTimeMillis();
      }

      if (closed) throw new InterruptedException("Session was closed: " + this);

      Map<Object, Object> retVal = new HashMap<Object, Object>(keys.size());

      for (Object key : keys) retVal.put(key, attrs.get(key));

      return retVal;
    }
  }

  /** {@inheritDoc} */
  @Override
  public boolean waitForAttributes(Map<?, ?> attrs, long timeout) throws InterruptedException {
    A.notNull(attrs, "attrs");

    if (attrs.isEmpty()) {
      return true;
    }

    if (timeout == 0) timeout = Long.MAX_VALUE;

    long now = System.currentTimeMillis();

    // Prevent overflow.
    long end = now + timeout < 0 ? Long.MAX_VALUE : now + timeout;

    // Don't wait longer than session timeout.
    if (end > endTime) end = endTime;

    synchronized (mux) {
      boolean isFound = false;

      while (!closed
          && !(isFound = this.attrs.entrySet().containsAll(attrs.entrySet()))
          && now < end) {
        mux.wait(end - now);

        now = System.currentTimeMillis();
      }

      if (closed) throw new InterruptedException("Session was closed: " + this);

      return isFound;
    }
  }

  /** {@inheritDoc} */
  @Override
  public String getTaskName() {
    return taskName;
  }

  /**
   * Returns task class name.
   *
   * @return Task class name.
   */
  public String getTaskClassName() {
    return taskClsName;
  }

  /** {@inheritDoc} */
  @Override
  public GridUuid getId() {
    return sesId;
  }

  /** {@inheritDoc} */
  @Override
  public long getEndTime() {
    return endTime;
  }

  /** @return Task version. */
  public String getUserVersion() {
    return dep == null ? "" : dep.userVersion();
  }

  /** {@inheritDoc} */
  @Override
  public ClassLoader getClassLoader() {
    synchronized (mux) {
      return clsLdr;
    }
  }

  /** @param clsLdr Class loader. */
  public void setClassLoader(ClassLoader clsLdr) {
    assert clsLdr != null;

    synchronized (mux) {
      this.clsLdr = clsLdr;
    }
  }

  /** {@inheritDoc} */
  @Override
  public boolean isTaskNode() {
    return taskNodeId.equals(ctx.discovery().localNode().id());
  }

  /** {@inheritDoc} */
  @Override
  public Collection<GridJobSibling> refreshJobSiblings() throws GridException {
    return getJobSiblings();
  }

  /** {@inheritDoc} */
  @Override
  public Collection<GridJobSibling> getJobSiblings() throws GridException {
    synchronized (mux) {
      return siblings;
    }
  }

  /** @param siblings Siblings. */
  public void setJobSiblings(Collection<GridJobSibling> siblings) {
    synchronized (mux) {
      this.siblings = Collections.unmodifiableCollection(siblings);
    }
  }

  /** @param siblings Siblings. */
  public void addJobSiblings(Collection<GridJobSibling> siblings) {
    assert isTaskNode();

    synchronized (mux) {
      Collection<GridJobSibling> tmp = new ArrayList<GridJobSibling>(this.siblings);

      tmp.addAll(siblings);

      this.siblings = Collections.unmodifiableCollection(tmp);
    }
  }

  /** {@inheritDoc} */
  @Override
  public GridJobSibling getJobSibling(GridUuid jobId) throws GridException {
    A.notNull(jobId, "jobId");

    Collection<GridJobSibling> tmp = getJobSiblings();

    for (GridJobSibling sibling : tmp) if (sibling.getJobId().equals(jobId)) return sibling;

    return null;
  }

  /** {@inheritDoc} */
  @Override
  public void setAttribute(Object key, Object val) throws GridException {
    A.notNull(key, "key");

    setAttributes(Collections.singletonMap(key, val));
  }

  /** {@inheritDoc} */
  @SuppressWarnings("unchecked")
  @Override
  public <K, V> V getAttribute(K key) {
    A.notNull(key, "key");

    synchronized (mux) {
      return (V) attrs.get(key);
    }
  }

  /** {@inheritDoc} */
  @Override
  public void setAttributes(Map<?, ?> attrs) throws GridException {
    A.notNull(attrs, "attrs");

    if (attrs.isEmpty()) return;

    // Note that there is no mux notification in this block.
    // The reason is that we wait for ordered attributes to
    // come back from task prior to notification. The notification
    // will happen in 'setInternal(...)' method.
    synchronized (mux) {
      this.attrs.putAll(attrs);
    }

    if (isTaskNode()) ctx.task().setAttributes(this, attrs);
  }

  /** {@inheritDoc} */
  @Override
  public Map<Object, Object> getAttributes() {
    synchronized (mux) {
      return U.sealMap(attrs);
    }
  }

  /** @param attrs Attributes to set. */
  public void setInternal(Map<?, ?> attrs) {
    A.notNull(attrs, "attrs");

    if (attrs.isEmpty()) return;

    List<GridTaskSessionAttributeListener> lsnrs;

    synchronized (mux) {
      this.attrs.putAll(attrs);

      lsnrs = this.lsnrs;

      mux.notifyAll();
    }

    for (Map.Entry<?, ?> entry : attrs.entrySet())
      for (GridTaskSessionAttributeListener lsnr : lsnrs)
        lsnr.onAttributeSet(entry.getKey(), entry.getValue());
  }

  /** {@inheritDoc} */
  @Override
  public void addAttributeListener(GridTaskSessionAttributeListener lsnr, boolean rewind) {
    A.notNull(lsnr, "lsnr");

    Map<Object, Object> attrs = null;

    List<GridTaskSessionAttributeListener> lsnrs;

    synchronized (mux) {
      lsnrs = new ArrayList<GridTaskSessionAttributeListener>(this.lsnrs.size());

      lsnrs.addAll(this.lsnrs);

      lsnrs.add(lsnr);

      lsnrs = Collections.unmodifiableList(lsnrs);

      this.lsnrs = lsnrs;

      if (rewind) attrs = new HashMap<Object, Object>(this.attrs);
    }

    if (rewind) {
      for (Map.Entry<Object, Object> entry : attrs.entrySet()) {
        for (GridTaskSessionAttributeListener l : lsnrs) {
          l.onAttributeSet(entry.getKey(), entry.getValue());
        }
      }
    }
  }

  /** {@inheritDoc} */
  @Override
  public boolean removeAttributeListener(GridTaskSessionAttributeListener lsnr) {
    A.notNull(lsnr, "lsnr");

    synchronized (mux) {
      List<GridTaskSessionAttributeListener> lsnrs =
          new ArrayList<GridTaskSessionAttributeListener>(this.lsnrs);

      boolean rmv = lsnrs.remove(lsnr);

      this.lsnrs = Collections.unmodifiableList(lsnrs);

      return rmv;
    }
  }

  /** {@inheritDoc} */
  @Override
  public void saveCheckpoint(String key, Object state) throws GridException {
    saveCheckpoint(key, state, GridTaskSessionScope.SESSION_SCOPE, 0);
  }

  /** {@inheritDoc} */
  @Override
  public void saveCheckpoint(String key, Object state, GridTaskSessionScope scope, long timeout)
      throws GridException {
    saveCheckpoint(key, state, scope, timeout, true);
  }

  /** {@inheritDoc} */
  @Override
  public void saveCheckpoint(
      String key, Object state, GridTaskSessionScope scope, long timeout, boolean overwrite)
      throws GridException {
    A.notNull(key, "key");
    A.ensure(timeout >= 0, "timeout >= 0");

    synchronized (mux) {
      if (closed) throw new GridException("Failed to save checkpoint (session closed): " + this);
    }

    ctx.checkpoint().storeCheckpoint(this, key, state, scope, timeout, overwrite);
  }

  /** {@inheritDoc} */
  @SuppressWarnings("unchecked")
  @Override
  public <T> T loadCheckpoint(String key) throws GridException {
    A.notNull(key, "key");

    synchronized (mux) {
      if (closed) throw new GridException("Failed to load checkpoint (session closed): " + this);
    }

    return (T) ctx.checkpoint().loadCheckpoint(this, key);
  }

  /** {@inheritDoc} */
  @Override
  public boolean removeCheckpoint(String key) throws GridException {
    A.notNull(key, "key");

    synchronized (mux) {
      if (closed) throw new GridException("Failed to remove checkpoint (session closed): " + this);
    }

    return ctx.checkpoint().removeCheckpoint(this, key);
  }

  /** {@inheritDoc} */
  @Override
  public Collection<UUID> getTopology() throws GridException {
    return F.nodeIds(ctx.topology().getTopology(this, ctx.discovery().allNodes()));
  }

  /**
   * @param key Key.
   * @param val Value.
   * @return {@code true} if key/value pair was set.
   */
  private boolean isAttributeSet(Object key, Object val) {
    if (attrs.containsKey(key)) {
      Object stored = attrs.get(key);

      if (val == null && stored == null) return true;

      if (val != null && stored != null) return val.equals(stored);
    }

    return false;
  }

  /** @return Topology SPI name. */
  @Override
  public String getTopologySpi() {
    return topSpi;
  }

  /** @param topSpi Topology SPI name. */
  public void setTopologySpi(String topSpi) {
    this.topSpi = topSpi;
  }

  /** {@inheritDoc} */
  @Override
  public String getCheckpointSpi() {
    return cpSpi;
  }

  /** @param cpSpi Checkpoint SPI name. */
  public void setCheckpointSpi(String cpSpi) {
    this.cpSpi = cpSpi;
  }

  /** @return Failover SPI name. */
  public String getFailoverSpi() {
    return failSpi;
  }

  /** @param failSpi Failover SPI name. */
  public void setFailoverSpi(String failSpi) {
    this.failSpi = failSpi;
  }

  /** @return Load balancing SPI name. */
  public String getLoadBalancingSpi() {
    return loadSpi;
  }

  /** @param loadSpi Load balancing SPI name. */
  public void setLoadBalancingSpi(String loadSpi) {
    this.loadSpi = loadSpi;
  }

  /** @return Task internal version. */
  public long getSequenceNumber() {
    return dep == null ? 0 : dep.sequenceNumber();
  }

  /** @return Deployment. */
  public GridDeployment deployment() {
    return dep;
  }

  /** {@inheritDoc} */
  @Override
  public String toString() {
    return S.toString(GridTaskSessionImpl.class, this);
  }
}
Example #4
0
/**
 * Replicated cache entry.
 *
 * @author 2005-2011 Copyright (C) GridGain Systems, Inc.
 * @version 3.1.1c.19062011
 */
public class GridDhtCacheEntry<K, V> extends GridDistributedCacheEntry<K, V> {
  /** Gets node value from reader ID. */
  private static final GridClosure<ReaderId, UUID> R2N =
      new C1<ReaderId, UUID>() {
        @Override
        public UUID apply(ReaderId e) {
          return e.nodeId();
        }
      };

  /** Reader clients. */
  @GridToStringInclude private volatile List<ReaderId> readers = Collections.emptyList();

  /** Local partition. */
  private final GridDhtLocalPartition<K, V> locPart;

  /** Transactions future for added readers. */
  private volatile GridCacheMultiTxFuture<K, V> txFut;

  /**
   * @param ctx Cache context.
   * @param key Cache key.
   * @param hash Key hash value.
   * @param val Entry value.
   * @param next Next entry in the linked list.
   * @param ttl Time to live.
   */
  public GridDhtCacheEntry(
      GridCacheContext<K, V> ctx, K key, int hash, V val, GridCacheMapEntry<K, V> next, long ttl) {
    super(ctx, key, hash, val, next, ttl);

    // Record this entry with partition.
    locPart = ctx.dht().topology().onAdded(this);
  }

  /** {@inheritDoc} */
  @Override
  public boolean partitionValid() {
    return locPart.valid();
  }

  /** {@inheritDoc} */
  @Override
  public boolean markObsolete(GridCacheVersion ver) {
    boolean rmv;

    synchronized (mux) {
      rmv = super.markObsolete(ver);
    }

    // Remove this entry from partition mapping.
    if (rmv) cctx.dht().topology().onRemoved(this);

    return rmv;
  }

  /**
   * Add local candidate.
   *
   * @param nearNodeId Near node ID.
   * @param nearVer Near version.
   * @param threadId Owning thread ID.
   * @param ver Lock version.
   * @param timeout Timeout to acquire lock.
   * @param reenter Reentry flag.
   * @param ec Eventually consistent flag.
   * @param tx Tx flag.
   * @return New candidate.
   * @throws GridCacheEntryRemovedException If entry has been removed.
   * @throws GridDistributedLockCancelledException If lock was cancelled.
   */
  @Nullable
  public GridCacheMvccCandidate<K> addDhtLocal(
      UUID nearNodeId,
      GridCacheVersion nearVer,
      long threadId,
      GridCacheVersion ver,
      long timeout,
      boolean reenter,
      boolean ec,
      boolean tx)
      throws GridCacheEntryRemovedException, GridDistributedLockCancelledException {
    GridCacheMvccCandidate<K> cand;
    GridCacheMvccCandidate<K> prev;
    GridCacheMvccCandidate<K> owner;

    V val;

    synchronized (mux) {
      // Check removed locks prior to obsolete flag.
      checkRemoved(ver);

      checkObsolete();

      prev = mvcc.anyOwner();

      boolean emptyBefore = mvcc.isEmpty();

      cand =
          mvcc.addLocal(this, nearNodeId, nearVer, threadId, ver, timeout, reenter, ec, tx, true);

      owner = mvcc.anyOwner();

      boolean emptyAfter = mvcc.isEmpty();

      if (prev != owner) mux.notifyAll();

      checkCallbacks(emptyBefore, emptyAfter);

      val = rawGet();
    }

    // Don't link reentries.
    if (cand != null && !cand.reentry())
      // Link with other candidates in the same thread.
      cctx.mvcc().addNext(cand);

    checkOwnerChanged(prev, owner, val);

    return cand;
  }

  /** {@inheritDoc} */
  @Override
  public boolean tmLock(GridCacheTxEx<K, V> tx, long timeout)
      throws GridCacheEntryRemovedException, GridDistributedLockCancelledException {
    if (tx.local()) {
      GridDhtTxLocal<K, V> dhtTx = (GridDhtTxLocal<K, V>) tx;

      // Null is returned if timeout is negative and there is other lock owner.
      return addDhtLocal(
              dhtTx.nearNodeId(),
              dhtTx.nearXidVersion(),
              tx.threadId(),
              tx.xidVersion(),
              timeout,
              false,
              tx.ec(),
              true)
          != null;
    }

    try {
      addRemote(
          tx.nodeId(),
          tx.otherNodeId(),
          tx.threadId(),
          tx.xidVersion(),
          tx.timeout(),
          tx.ec(),
          true);

      return true;
    } catch (GridDistributedLockCancelledException ignored) {
      if (log.isDebugEnabled())
        log.debug("Attempted to enter tx lock for cancelled ID (will ignore): " + tx);

      return false;
    }
  }

  /** {@inheritDoc} */
  @Override
  public GridCacheMvccCandidate<K> removeLock() {
    GridCacheMvccCandidate<K> ret = super.removeLock();

    locPart.onUnlock();

    return ret;
  }

  /** {@inheritDoc} */
  @Override
  public boolean removeLock(GridCacheVersion ver) throws GridCacheEntryRemovedException {
    boolean ret = super.removeLock(ver);

    locPart.onUnlock();

    return ret;
  }

  /**
   * @return Readers.
   * @throws GridCacheEntryRemovedException If removed.
   */
  public Collection<UUID> readers() throws GridCacheEntryRemovedException {
    return F.viewReadOnly(checkReaders(), R2N);
  }

  /**
   * @param nodeId Node ID.
   * @return reader ID.
   */
  @Nullable
  private ReaderId readerId(UUID nodeId) {
    for (ReaderId reader : readers) if (reader.nodeId().equals(nodeId)) return reader;

    return null;
  }

  /**
   * @param nodeId Reader to add.
   * @param msgId Message ID.
   * @return Future for all relevant transactions that were active at the time of adding reader, or
   *     {@code null} if reader was added
   * @throws GridCacheEntryRemovedException If entry was removed.
   */
  @Nullable
  public GridFuture<Boolean> addReader(UUID nodeId, long msgId)
      throws GridCacheEntryRemovedException {
    // Don't add local node as reader.
    if (cctx.nodeId().equals(nodeId)) return null;

    GridNode node = cctx.discovery().node(nodeId);

    // If remote node has no near cache, don't add it.
    if (node == null || !U.hasNearCache(node, cctx.dht().near().name())) return null;

    // If remote node is (primary?) or back up, don't add it as a reader.
    if (U.nodeIds(cctx.affinity(partition(), CU.allNodes(cctx))).contains(nodeId)) return null;

    boolean ret = false;

    GridCacheMultiTxFuture<K, V> txFut;

    Collection<GridCacheMvccCandidate<K>> cands = null;

    synchronized (mux) {
      checkObsolete();

      txFut = this.txFut;

      ReaderId reader = readerId(nodeId);

      if (reader == null) {
        reader = new ReaderId(nodeId, msgId);

        readers = new LinkedList<ReaderId>(readers);

        readers.add(reader);

        // Seal.
        readers = Collections.unmodifiableList(readers);

        txFut = this.txFut = new GridCacheMultiTxFuture<K, V>(cctx);

        cands = localCandidates();

        ret = true;
      } else {
        long id = reader.messageId();

        if (id < msgId) reader.messageId(msgId);
      }
    }

    if (ret) {
      assert txFut != null;

      if (!F.isEmpty(cands)) {
        for (GridCacheMvccCandidate<K> c : cands) {
          GridCacheTxEx<K, V> tx = cctx.tm().<GridCacheTxEx<K, V>>tx(c.version());

          if (tx != null) {
            assert tx.local();

            txFut.addTx(tx);
          }
        }
      }

      txFut.init();

      if (!txFut.isDone()) {
        txFut.listenAsync(
            new CI1<GridFuture<?>>() {
              @Override
              public void apply(GridFuture<?> f) {
                synchronized (mux) {
                  // Release memory.
                  GridDhtCacheEntry.this.txFut = null;
                }
              }
            });
      } else
        // Release memory.
        txFut = this.txFut = null;
    }

    return txFut;
  }

  /**
   * @param nodeId Reader to remove.
   * @param msgId Message ID.
   * @return {@code True} if reader was removed as a result of this operation.
   * @throws GridCacheEntryRemovedException If entry was removed.
   */
  public boolean removeReader(UUID nodeId, long msgId) throws GridCacheEntryRemovedException {
    synchronized (mux) {
      checkObsolete();

      ReaderId reader = readerId(nodeId);

      if (reader == null || reader.messageId() > msgId) return false;

      readers = new LinkedList<ReaderId>(readers);

      readers.remove(reader);

      // Seal.
      readers = Collections.unmodifiableList(readers);

      return true;
    }
  }

  /** Clears all readers (usually when partition becomes invalid and ready for eviction). */
  @Override
  public void clearReaders() {
    synchronized (mux) {
      readers = Collections.emptyList();
    }
  }

  /**
   * @return Collection of readers after check.
   * @throws GridCacheEntryRemovedException If removed.
   */
  public Collection<ReaderId> checkReaders() throws GridCacheEntryRemovedException {
    synchronized (mux) {
      checkObsolete();

      if (!readers.isEmpty()) {
        List<ReaderId> rmv = null;

        for (ReaderId reader : readers) {
          if (!cctx.discovery().alive(reader.nodeId())) {
            if (rmv == null) rmv = new LinkedList<ReaderId>();

            rmv.add(reader);
          }
        }

        if (rmv != null) {
          readers = new LinkedList<ReaderId>(readers);

          for (ReaderId rdr : rmv) readers.remove(rdr);

          readers = Collections.unmodifiableList(readers);
        }
      }

      return readers;
    }
  }

  /** {@inheritDoc} */
  @Override
  protected boolean hasReaders() throws GridCacheEntryRemovedException {
    synchronized (mux) {
      checkReaders();

      return !readers.isEmpty();
    }
  }

  /**
   * @param ver Version.
   * @param mappings Mappings to set.
   * @return Candidate, if one existed for the version, or {@code null} if candidate was not found.
   * @throws GridCacheEntryRemovedException If removed.
   */
  public GridCacheMvccCandidate<K> mappings(GridCacheVersion ver, Collection<UUID> mappings)
      throws GridCacheEntryRemovedException {
    synchronized (mux) {
      checkObsolete();

      GridCacheMvccCandidate<K> cand = mvcc.candidate(ver);

      if (cand != null) cand.mappedNodeIds(mappings);

      return cand;
    }
  }

  /** {@inheritDoc} */
  @Override
  public GridCacheEntry<K, V> wrap(boolean prjAware) {
    GridCacheContext<K, V> nearCtx = cctx.dht().near().context();

    GridCacheProjectionImpl<K, V> prjPerCall = nearCtx.projectionPerCall();

    if (prjPerCall != null && prjAware)
      return new GridPartitionedCacheEntryImpl<K, V>(prjPerCall, nearCtx, key, this);

    GridCacheEntryImpl<K, V> wrapper = this.wrapper;

    if (wrapper == null)
      this.wrapper = wrapper = new GridPartitionedCacheEntryImpl<K, V>(null, nearCtx, key, this);

    return wrapper;
  }

  /** {@inheritDoc} */
  @Override
  public String toString() {
    return S.toString(GridDhtCacheEntry.class, this, "super", super.toString());
  }

  /** Reader ID. */
  private static class ReaderId {
    /** Node ID. */
    private UUID nodeId;

    /** Message ID. */
    private long msgId;

    /**
     * @param nodeId Node ID.
     * @param msgId Message ID.
     */
    ReaderId(UUID nodeId, long msgId) {
      this.nodeId = nodeId;
      this.msgId = msgId;
    }

    /** @return Node ID. */
    UUID nodeId() {
      return nodeId;
    }

    /** @return Message ID. */
    long messageId() {
      return msgId;
    }

    /** @param msgId Message ID. */
    void messageId(long msgId) {
      this.msgId = msgId;
    }

    /** {@inheritDoc} */
    @Override
    public String toString() {
      return S.toString(ReaderId.class, this);
    }
  }
}