/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ignite.internal.processors.cache.distributed.dht;
import java.io.Externalizable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.UUID;
import org.apache.ignite.IgniteCheckedException;
import org.apache.ignite.cluster.ClusterNode;
import org.apache.ignite.internal.IgniteInternalFuture;
import org.apache.ignite.internal.NodeStoppingException;
import org.apache.ignite.internal.cluster.ClusterTopologyCheckedException;
import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
import org.apache.ignite.internal.processors.cache.CacheEntryPredicate;
import org.apache.ignite.internal.processors.cache.CacheObject;
import org.apache.ignite.internal.processors.cache.CacheOperationContext;
import org.apache.ignite.internal.processors.cache.GridCacheConcurrentMap;
import org.apache.ignite.internal.processors.cache.GridCacheContext;
import org.apache.ignite.internal.processors.cache.GridCacheEntryEx;
import org.apache.ignite.internal.processors.cache.GridCacheEntryInfo;
import org.apache.ignite.internal.processors.cache.GridCacheEntryRemovedException;
import org.apache.ignite.internal.processors.cache.GridCacheLockTimeoutException;
import org.apache.ignite.internal.processors.cache.GridCacheMvccCandidate;
import org.apache.ignite.internal.processors.cache.GridCacheReturn;
import org.apache.ignite.internal.processors.cache.KeyCacheObject;
import org.apache.ignite.internal.processors.cache.distributed.GridDistributedCacheEntry;
import org.apache.ignite.internal.processors.cache.distributed.GridDistributedLockCancelledException;
import org.apache.ignite.internal.processors.cache.distributed.GridDistributedUnlockRequest;
import org.apache.ignite.internal.processors.cache.distributed.dht.preloader.GridDhtPreloader;
import org.apache.ignite.internal.processors.cache.distributed.near.GridNearGetRequest;
import org.apache.ignite.internal.processors.cache.distributed.near.GridNearLockRequest;
import org.apache.ignite.internal.processors.cache.distributed.near.GridNearLockResponse;
import org.apache.ignite.internal.processors.cache.distributed.near.GridNearSingleGetRequest;
import org.apache.ignite.internal.processors.cache.distributed.near.GridNearTransactionalCache;
import org.apache.ignite.internal.processors.cache.distributed.near.GridNearTxRemote;
import org.apache.ignite.internal.processors.cache.distributed.near.GridNearUnlockRequest;
import org.apache.ignite.internal.processors.cache.transactions.IgniteTxEntry;
import org.apache.ignite.internal.processors.cache.transactions.IgniteTxKey;
import org.apache.ignite.internal.processors.cache.transactions.IgniteTxLocalEx;
import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
import org.apache.ignite.internal.transactions.IgniteTxRollbackCheckedException;
import org.apache.ignite.internal.util.F0;
import org.apache.ignite.internal.util.GridLeanSet;
import org.apache.ignite.internal.util.future.GridFinishedFuture;
import org.apache.ignite.internal.util.lang.GridClosureException;
import org.apache.ignite.internal.util.lang.IgnitePair;
import org.apache.ignite.internal.util.typedef.C1;
import org.apache.ignite.internal.util.typedef.C2;
import org.apache.ignite.internal.util.typedef.CI1;
import org.apache.ignite.internal.util.typedef.CI2;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.internal.util.typedef.X;
import org.apache.ignite.internal.util.typedef.internal.CU;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.apache.ignite.transactions.TransactionIsolation;
import org.jetbrains.annotations.Nullable;
import static org.apache.ignite.cache.CacheWriteSynchronizationMode.FULL_SYNC;
import static org.apache.ignite.internal.processors.cache.GridCacheOperation.NOOP;
import static org.apache.ignite.internal.processors.cache.GridCacheUtils.isNearEnabled;
import static org.apache.ignite.transactions.TransactionConcurrency.PESSIMISTIC;
import static org.apache.ignite.transactions.TransactionState.COMMITTING;
/**
* Base class for transactional DHT caches.
*/
@SuppressWarnings("unchecked")
public abstract class GridDhtTransactionalCacheAdapter<K, V> extends GridDhtCacheAdapter<K, V> {
/** */
private static final long serialVersionUID = 0L;
/**
* Empty constructor required for {@link Externalizable}.
*/
protected GridDhtTransactionalCacheAdapter() {
// No-op.
}
/**
* @param ctx Context.
*/
protected GridDhtTransactionalCacheAdapter(GridCacheContext<K, V> ctx) {
super(ctx);
}
/**
* Constructor used for near-only cache.
*
* @param ctx Cache context.
* @param map Cache map.
*/
protected GridDhtTransactionalCacheAdapter(GridCacheContext<K, V> ctx, GridCacheConcurrentMap map) {
super(ctx, map);
}
/** {@inheritDoc} */
@Override public void start() throws IgniteCheckedException {
super.start();
preldr = new GridDhtPreloader(ctx);
preldr.start();
ctx.io().addHandler(ctx.cacheId(), GridNearGetRequest.class, new CI2<UUID, GridNearGetRequest>() {
@Override public void apply(UUID nodeId, GridNearGetRequest req) {
processNearGetRequest(nodeId, req);
}
});
ctx.io().addHandler(ctx.cacheId(), GridNearSingleGetRequest.class, new CI2<UUID, GridNearSingleGetRequest>() {
@Override public void apply(UUID nodeId, GridNearSingleGetRequest req) {
processNearSingleGetRequest(nodeId, req);
}
});
ctx.io().addHandler(ctx.cacheId(), GridNearLockRequest.class, new CI2<UUID, GridNearLockRequest>() {
@Override public void apply(UUID nodeId, GridNearLockRequest req) {
processNearLockRequest(nodeId, req);
}
});
ctx.io().addHandler(ctx.cacheId(), GridDhtLockRequest.class, new CI2<UUID, GridDhtLockRequest>() {
@Override public void apply(UUID nodeId, GridDhtLockRequest req) {
processDhtLockRequest(nodeId, req);
}
});
ctx.io().addHandler(ctx.cacheId(), GridDhtLockResponse.class, new CI2<UUID, GridDhtLockResponse>() {
@Override public void apply(UUID nodeId, GridDhtLockResponse req) {
processDhtLockResponse(nodeId, req);
}
});
ctx.io().addHandler(ctx.cacheId(), GridNearUnlockRequest.class, new CI2<UUID, GridNearUnlockRequest>() {
@Override public void apply(UUID nodeId, GridNearUnlockRequest req) {
processNearUnlockRequest(nodeId, req);
}
});
ctx.io().addHandler(ctx.cacheId(), GridDhtUnlockRequest.class, new CI2<UUID, GridDhtUnlockRequest>() {
@Override public void apply(UUID nodeId, GridDhtUnlockRequest req) {
processDhtUnlockRequest(nodeId, req);
}
});
}
/** {@inheritDoc} */
@Override public abstract GridNearTransactionalCache<K, V> near();
/**
* @param nodeId Primary node ID.
* @param req Request.
* @param res Response.
* @return Remote transaction.
* @throws IgniteCheckedException If failed.
* @throws GridDistributedLockCancelledException If lock has been cancelled.
*/
@SuppressWarnings({"RedundantTypeArguments"})
@Nullable private GridDhtTxRemote startRemoteTx(UUID nodeId,
GridDhtLockRequest req,
GridDhtLockResponse res)
throws IgniteCheckedException, GridDistributedLockCancelledException {
List<KeyCacheObject> keys = req.keys();
GridDhtTxRemote tx = null;
int size = F.size(keys);
for (int i = 0; i < size; i++) {
KeyCacheObject key = keys.get(i);
if (key == null)
continue;
IgniteTxKey txKey = ctx.txKey(key);
if (log.isDebugEnabled())
log.debug("Unmarshalled key: " + key);
GridDistributedCacheEntry entry = null;
while (true) {
try {
int part = ctx.affinity().partition(key);
GridDhtLocalPartition locPart = ctx.topology().localPartition(part, req.topologyVersion(),
false);
if (locPart == null || !locPart.reserve()) {
if (log.isDebugEnabled())
log.debug("Local partition for given key is already evicted (will add to invalid " +
"partition list) [key=" + key + ", part=" + part + ", locPart=" + locPart + ']');
res.addInvalidPartition(part);
// Invalidate key in near cache, if any.
if (isNearEnabled(cacheCfg))
obsoleteNearEntry(key);
break;
}
try {
// Handle implicit locks for pessimistic transactions.
if (req.inTx()) {
if (tx == null)
tx = ctx.tm().tx(req.version());
if (tx == null) {
tx = new GridDhtTxRemote(
ctx.shared(),
req.nodeId(),
req.futureId(),
nodeId,
req.nearXidVersion(),
req.topologyVersion(),
req.version(),
/*commitVer*/null,
ctx.systemTx(),
ctx.ioPolicy(),
PESSIMISTIC,
req.isolation(),
req.isInvalidate(),
req.timeout(),
req.txSize(),
req.subjectId(),
req.taskNameHash());
tx = ctx.tm().onCreated(null, tx);
if (tx == null || !ctx.tm().onStarted(tx))
throw new IgniteTxRollbackCheckedException("Failed to acquire lock (transaction " +
"has been completed) [ver=" + req.version() + ", tx=" + tx + ']');
}
tx.addWrite(
ctx,
NOOP,
txKey,
null,
null,
req.accessTtl(),
req.skipStore(),
req.keepBinary());
}
entry = entryExx(key, req.topologyVersion());
// Add remote candidate before reordering.
entry.addRemote(
req.nodeId(),
nodeId,
req.threadId(),
req.version(),
tx != null,
tx != null && tx.implicitSingle(),
null
);
// Invalidate key in near cache, if any.
if (isNearEnabled(cacheCfg) && req.invalidateNearEntry(i))
invalidateNearEntry(key, req.version());
// Get entry info after candidate is added.
if (req.needPreloadKey(i)) {
entry.unswap();
GridCacheEntryInfo info = entry.info();
if (info != null && !info.isNew() && !info.isDeleted())
res.addPreloadEntry(info);
}
// Double-check in case if sender node left the grid.
if (ctx.discovery().node(req.nodeId()) == null) {
if (log.isDebugEnabled())
log.debug("Node requesting lock left grid (lock request will be ignored): " + req);
entry.removeLock(req.version());
if (tx != null) {
tx.clearEntry(txKey);
// If there is a concurrent salvage, there could be a case when tx is moved to
// COMMITTING state, but this lock is never acquired.
if (tx.state() == COMMITTING)
tx.forceCommit();
else
tx.rollbackRemoteTx();
}
return null;
}
// Entry is legit.
break;
}
finally {
locPart.release();
}
}
catch (GridDhtInvalidPartitionException e) {
if (log.isDebugEnabled())
log.debug("Received invalid partition exception [e=" + e + ", req=" + req + ']');
res.addInvalidPartition(e.partition());
// Invalidate key in near cache, if any.
if (isNearEnabled(cacheCfg))
obsoleteNearEntry(key);
if (tx != null) {
tx.clearEntry(txKey);
if (log.isDebugEnabled())
log.debug("Cleared invalid entry from remote transaction (will skip) [entry=" +
entry + ", tx=" + tx + ']');
}
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(txKey);
if (log.isDebugEnabled())
log.debug("Cleared removed entry from remote transaction (will retry) [entry=" +
entry + ", tx=" + tx + ']');
}
}
}
}
if (tx != null && tx.empty()) {
if (log.isDebugEnabled())
log.debug("Rolling back remote DHT transaction because it is empty [req=" + req + ", res=" + res + ']');
tx.rollbackRemoteTx();
tx = null;
}
return tx;
}
/**
* @param nodeId Node ID.
* @param req Request.
*/
private void processDhtLockRequest(final UUID nodeId, final GridDhtLockRequest req) {
if (txLockMsgLog.isDebugEnabled()) {
txLockMsgLog.debug("Received dht lock request [txId=" + req.nearXidVersion() +
", dhtTxId=" + req.version() +
", inTx=" + req.inTx() +
", node=" + nodeId + ']');
}
IgniteInternalFuture<Object> keyFut = F.isEmpty(req.keys()) ? null :
ctx.dht().dhtPreloader().request(req.keys(), req.topologyVersion());
if (keyFut == null || keyFut.isDone()) {
if (keyFut != null) {
try {
keyFut.get();
}
catch (NodeStoppingException ignored) {
return;
}
catch (IgniteCheckedException e) {
onForceKeysError(nodeId, req, e);
return;
}
}
processDhtLockRequest0(nodeId, req);
}
else {
keyFut.listen(new CI1<IgniteInternalFuture<Object>>() {
@Override public void apply(IgniteInternalFuture<Object> fut) {
try {
fut.get();
}
catch (NodeStoppingException ignored) {
return;
}
catch (IgniteCheckedException e) {
onForceKeysError(nodeId, req, e);
return;
}
processDhtLockRequest0(nodeId, req);
}
});
}
}
/**
* @param nodeId Node ID.
* @param req Request.
* @param e Error.
*/
private void onForceKeysError(UUID nodeId, GridDhtLockRequest req, IgniteCheckedException e) {
GridDhtLockResponse res = new GridDhtLockResponse(ctx.cacheId(),
req.version(),
req.futureId(),
req.miniId(),
e,
ctx.deploymentEnabled());
try {
ctx.io().send(nodeId, res, ctx.ioPolicy());
}
catch (ClusterTopologyCheckedException ignored) {
if (log.isDebugEnabled())
log.debug("Failed to send lock reply to remote node because it left grid: " + nodeId);
}
catch (IgniteCheckedException ignored) {
U.error(log, "Failed to send lock reply to node: " + nodeId, e);
}
}
/**
* @param nodeId Node ID.
* @param req Request.
*/
private void processDhtLockRequest0(UUID nodeId, GridDhtLockRequest req) {
assert nodeId != null;
assert req != null;
assert !nodeId.equals(locNodeId);
int cnt = F.size(req.keys());
GridDhtLockResponse res;
GridDhtTxRemote dhtTx = null;
GridNearTxRemote nearTx = null;
boolean fail = false;
boolean cancelled = false;
try {
res = new GridDhtLockResponse(ctx.cacheId(), req.version(), req.futureId(), req.miniId(), cnt,
ctx.deploymentEnabled());
dhtTx = startRemoteTx(nodeId, req, res);
nearTx = isNearEnabled(cacheCfg) ? near().startRemoteTx(nodeId, req) : null;
if (nearTx != null && !nearTx.empty())
res.nearEvicted(nearTx.evicted());
else {
if (!F.isEmpty(req.nearKeys())) {
Collection<IgniteTxKey> nearEvicted = new ArrayList<>(req.nearKeys().size());
nearEvicted.addAll(F.viewReadOnly(req.nearKeys(), new C1<KeyCacheObject, IgniteTxKey>() {
@Override public IgniteTxKey apply(KeyCacheObject k) {
return ctx.txKey(k);
}
}));
res.nearEvicted(nearEvicted);
}
}
}
catch (IgniteTxRollbackCheckedException e) {
String err = "Failed processing DHT lock request (transaction has been completed): " + req;
U.error(log, err, e);
res = new GridDhtLockResponse(ctx.cacheId(), req.version(), req.futureId(), req.miniId(),
new IgniteTxRollbackCheckedException(err, e), ctx.deploymentEnabled());
fail = true;
}
catch (IgniteCheckedException e) {
String err = "Failed processing DHT lock request: " + req;
U.error(log, err, e);
res = new GridDhtLockResponse(ctx.cacheId(),
req.version(),
req.futureId(),
req.miniId(),
new IgniteCheckedException(err, e), ctx.deploymentEnabled());
fail = true;
}
catch (GridDistributedLockCancelledException ignored) {
// Received lock request for cancelled lock.
if (log.isDebugEnabled())
log.debug("Received lock request for canceled lock (will ignore): " + req);
res = null;
fail = true;
cancelled = true;
}
boolean releaseAll = false;
if (res != null) {
try {
// Reply back to sender.
ctx.io().send(nodeId, res, ctx.ioPolicy());
if (txLockMsgLog.isDebugEnabled()) {
txLockMsgLog.debug("Sent dht lock response [txId=" + req.nearXidVersion() +
", dhtTxId=" + req.version() +
", inTx=" + req.inTx() +
", node=" + nodeId + ']');
}
}
catch (ClusterTopologyCheckedException ignored) {
U.warn(txLockMsgLog, "Failed to send dht lock response, node failed [" +
"txId=" + req.nearXidVersion() +
", dhtTxId=" + req.version() +
", inTx=" + req.inTx() +
", node=" + nodeId + ']');
fail = true;
releaseAll = true;
}
catch (IgniteCheckedException e) {
U.error(txLockMsgLog, "Failed to send dht lock response (lock will not be acquired) " +
"txId=" + req.nearXidVersion() +
", dhtTxId=" + req.version() +
", inTx=" + req.inTx() +
", node=" + nodeId + ']', e);
fail = true;
}
}
if (fail) {
if (dhtTx != null)
dhtTx.rollbackRemoteTx();
if (nearTx != null) // Even though this should never happen, we leave this check for consistency.
nearTx.rollbackRemoteTx();
List<KeyCacheObject> keys = req.keys();
if (keys != null) {
for (KeyCacheObject key : keys) {
while (true) {
GridDistributedCacheEntry entry = peekExx(key);
try {
if (entry != null) {
// Release all locks because sender node left grid.
if (releaseAll)
entry.removeExplicitNodeLocks(req.nodeId());
else
entry.removeLock(req.version());
}
break;
}
catch (GridCacheEntryRemovedException ignore) {
if (log.isDebugEnabled())
log.debug("Attempted to remove lock on removed entity during during failure " +
"handling for dht lock request (will retry): " + entry);
}
}
}
}
if (releaseAll && !cancelled)
U.warn(log, "Sender node left grid in the midst of lock acquisition (locks have been released).");
}
}
/**
* @param nodeId Node ID.
* @param req Request.
*/
private void processDhtUnlockRequest(UUID nodeId, GridDhtUnlockRequest req) {
clearLocks(nodeId, req);
if (isNearEnabled(cacheCfg))
near().clearLocks(nodeId, req);
}
/**
* @param nodeId Node ID.
* @param req Request.
*/
private void processNearLockRequest(UUID nodeId, GridNearLockRequest req) {
assert ctx.affinityNode();
assert nodeId != null;
assert req != null;
if (txLockMsgLog.isDebugEnabled()) {
txLockMsgLog.debug("Received near lock request [txId=" + req.version() +
", inTx=" + req.inTx() +
", node=" + nodeId + ']');
}
ClusterNode nearNode = ctx.discovery().node(nodeId);
if (nearNode == null) {
U.warn(txLockMsgLog, "Received near lock request from unknown node (will ignore) [txId=" + req.version() +
", inTx=" + req.inTx() +
", node=" + nodeId + ']');
return;
}
IgniteInternalFuture<?> f = lockAllAsync(ctx, nearNode, req, null);
// Register listener just so we print out errors.
// Exclude lock timeout exception since it's not a fatal exception.
f.listen(CU.errorLogger(log, GridCacheLockTimeoutException.class,
GridDistributedLockCancelledException.class));
}
/**
* @param nodeId Node ID.
* @param res Response.
*/
private void processDhtLockResponse(UUID nodeId, GridDhtLockResponse res) {
assert nodeId != null;
assert res != null;
GridDhtLockFuture fut = (GridDhtLockFuture)ctx.mvcc().<Boolean>mvccFuture(res.version(), res.futureId());
if (fut == null) {
if (txLockMsgLog.isDebugEnabled())
txLockMsgLog.debug("Received dht lock response for unknown future [txId=null" +
", dhtTxId=" + res.version() +
", node=" + nodeId + ']');
return;
}
else if (txLockMsgLog.isDebugEnabled()) {
txLockMsgLog.debug("Received dht lock response [txId=" + fut.nearLockVersion() +
", dhtTxId=" + res.version() +
", node=" + nodeId + ']');
}
fut.onResult(nodeId, res);
}
/** {@inheritDoc} */
@Override public IgniteInternalFuture<Boolean> lockAllAsync(
@Nullable Collection<KeyCacheObject> keys,
long timeout,
IgniteTxLocalEx txx,
boolean isInvalidate,
boolean isRead,
boolean retval,
TransactionIsolation isolation,
long createTtl,
long accessTtl) {
CacheOperationContext opCtx = ctx.operationContextPerCall();
return lockAllAsyncInternal(
keys,
timeout,
txx,
isInvalidate,
isRead,
retval,
isolation,
createTtl,
accessTtl,
CU.empty0(),
opCtx != null && opCtx.skipStore(),
opCtx != null && opCtx.isKeepBinary());
}
/**
* Acquires locks in partitioned cache.
*
* @param keys Keys to lock.
* @param timeout Lock timeout.
* @param txx Transaction.
* @param isInvalidate Invalidate flag.
* @param isRead Read flag.
* @param retval Return value flag.
* @param isolation Transaction isolation.
* @param createTtl TTL for create operation.
* @param accessTtl TTL for read operation.
* @param filter Optional filter.
* @param skipStore Skip store flag.
* @return Lock future.
*/
public GridDhtFuture<Boolean> lockAllAsyncInternal(@Nullable Collection<KeyCacheObject> keys,
long timeout,
IgniteTxLocalEx txx,
boolean isInvalidate,
boolean isRead,
boolean retval,
TransactionIsolation isolation,
long createTtl,
long accessTtl,
CacheEntryPredicate[] filter,
boolean skipStore,
boolean keepBinary) {
if (keys == null || keys.isEmpty())
return new GridDhtFinishedFuture<>(true);
GridDhtTxLocalAdapter tx = (GridDhtTxLocalAdapter)txx;
assert tx != null;
GridDhtLockFuture fut = new GridDhtLockFuture(
ctx,
tx.nearNodeId(),
tx.nearXidVersion(),
tx.topologyVersion(),
keys.size(),
isRead,
retval,
timeout,
tx,
tx.threadId(),
createTtl,
accessTtl,
filter,
skipStore,
keepBinary);
for (KeyCacheObject key : keys) {
try {
while (true) {
GridDhtCacheEntry entry = entryExx(key, tx.topologyVersion());
try {
fut.addEntry(entry);
// Possible in case of cancellation or time out.
if (fut.isDone())
return fut;
break;
}
catch (GridCacheEntryRemovedException ignore) {
if (log.isDebugEnabled())
log.debug("Got removed entry when adding lock (will retry): " + entry);
}
catch (GridDistributedLockCancelledException e) {
if (log.isDebugEnabled())
log.debug("Failed to add entry [err=" + e + ", entry=" + entry + ']');
return new GridDhtFinishedFuture<>(e);
}
}
}
catch (GridDhtInvalidPartitionException e) {
fut.addInvalidPartition(ctx, e.partition());
if (log.isDebugEnabled())
log.debug("Added invalid partition to DHT lock future [part=" + e.partition() + ", fut=" +
fut + ']');
}
}
ctx.mvcc().addFuture(fut);
fut.map();
return fut;
}
/**
* @param cacheCtx Cache context.
* @param nearNode Near node.
* @param req Request.
* @param filter0 Filter.
* @return Future.
*/
public IgniteInternalFuture<GridNearLockResponse> lockAllAsync(
final GridCacheContext<?, ?> cacheCtx,
final ClusterNode nearNode,
final GridNearLockRequest req,
@Nullable final CacheEntryPredicate[] filter0) {
final List<KeyCacheObject> keys = req.keys();
CacheEntryPredicate[] filter = filter0;
// Set message into thread context.
GridDhtTxLocal tx = null;
try {
int cnt = keys.size();
if (req.inTx()) {
GridCacheVersion dhtVer = ctx.tm().mappedVersion(req.version());
if (dhtVer != null)
tx = ctx.tm().tx(dhtVer);
}
final List<GridCacheEntryEx> entries = new ArrayList<>(cnt);
// Unmarshal filter first.
if (filter == null)
filter = req.filter();
GridDhtLockFuture fut = null;
if (!req.inTx()) {
GridDhtPartitionTopology top = null;
if (req.firstClientRequest()) {
assert CU.clientNode(nearNode);
top = topology();
topology().readLock();
}
try {
if (top != null && needRemap(req.topologyVersion(), top.topologyVersion())) {
if (log.isDebugEnabled()) {
log.debug("Client topology version mismatch, need remap lock request [" +
"reqTopVer=" + req.topologyVersion() +
", locTopVer=" + top.topologyVersion() +
", req=" + req + ']');
}
GridNearLockResponse res = sendClientLockRemapResponse(nearNode,
req,
top.topologyVersion());
return new GridFinishedFuture<>(res);
}
fut = new GridDhtLockFuture(ctx,
nearNode.id(),
req.version(),
req.topologyVersion(),
cnt,
req.txRead(),
req.needReturnValue(),
req.timeout(),
tx,
req.threadId(),
req.createTtl(),
req.accessTtl(),
filter,
req.skipStore(),
req.keepBinary());
// Add before mapping.
if (!ctx.mvcc().addFuture(fut))
throw new IllegalStateException("Duplicate future ID: " + fut);
}
finally {
if (top != null)
top.readUnlock();
}
}
boolean timedout = false;
for (KeyCacheObject key : keys) {
if (timedout)
break;
while (true) {
// Specify topology version to make sure containment is checked
// based on the requested version, not the latest.
GridDhtCacheEntry entry = entryExx(key, req.topologyVersion());
try {
if (fut != null) {
// This method will add local candidate.
// Entry cannot become obsolete after this method succeeded.
fut.addEntry(key == null ? null : entry);
if (fut.isDone()) {
timedout = true;
break;
}
}
entries.add(entry);
break;
}
catch (GridCacheEntryRemovedException ignore) {
if (log.isDebugEnabled())
log.debug("Got removed entry when adding lock (will retry): " + entry);
}
catch (GridDistributedLockCancelledException e) {
if (log.isDebugEnabled())
log.debug("Got lock request for cancelled lock (will ignore): " +
entry);
fut.onError(e);
return new GridDhtFinishedFuture<>(e);
}
}
}
// Handle implicit locks for pessimistic transactions.
if (req.inTx()) {
if (tx == null) {
GridDhtPartitionTopology top = null;
if (req.firstClientRequest()) {
assert CU.clientNode(nearNode);
top = topology();
topology().readLock();
}
try {
if (top != null && needRemap(req.topologyVersion(), top.topologyVersion())) {
if (log.isDebugEnabled()) {
log.debug("Client topology version mismatch, need remap lock request [" +
"reqTopVer=" + req.topologyVersion() +
", locTopVer=" + top.topologyVersion() +
", req=" + req + ']');
}
GridNearLockResponse res = sendClientLockRemapResponse(nearNode,
req,
top.topologyVersion());
return new GridFinishedFuture<>(res);
}
tx = new GridDhtTxLocal(
ctx.shared(),
req.topologyVersion(),
nearNode.id(),
req.version(),
req.futureId(),
req.miniId(),
req.threadId(),
/*implicitTx*/false,
/*implicitSingleTx*/false,
ctx.systemTx(),
false,
ctx.ioPolicy(),
PESSIMISTIC,
req.isolation(),
req.timeout(),
req.isInvalidate(),
!req.skipStore(),
false,
req.txSize(),
null,
req.subjectId(),
req.taskNameHash());
if (req.syncCommit())
tx.syncMode(FULL_SYNC);
tx = ctx.tm().onCreated(null, tx);
if (tx == null || !tx.init()) {
String msg = "Failed to acquire lock (transaction has been completed): " +
req.version();
U.warn(log, msg);
if (tx != null)
tx.rollbackDhtLocal();
return new GridDhtFinishedFuture<>(new IgniteCheckedException(msg));
}
tx.topologyVersion(req.topologyVersion());
}
finally {
if (top != null)
top.readUnlock();
}
}
ctx.tm().txContext(tx);
if (log.isDebugEnabled())
log.debug("Performing DHT lock [tx=" + tx + ", entries=" + entries + ']');
IgniteInternalFuture<GridCacheReturn> txFut = tx.lockAllAsync(
cacheCtx,
entries,
req.messageId(),
req.txRead(),
req.needReturnValue(),
req.createTtl(),
req.accessTtl(),
req.skipStore(),
req.keepBinary());
final GridDhtTxLocal t = tx;
return new GridDhtEmbeddedFuture(
txFut,
new C2<GridCacheReturn, Exception, IgniteInternalFuture<GridNearLockResponse>>() {
@Override public IgniteInternalFuture<GridNearLockResponse> apply(
GridCacheReturn o, Exception e) {
if (e != null)
e = U.unwrap(e);
assert !t.empty();
// Create response while holding locks.
final GridNearLockResponse resp = createLockReply(nearNode,
entries,
req,
t,
t.xidVersion(),
e);
assert !t.implicit() : t;
assert !t.onePhaseCommit() : t;
sendLockReply(nearNode, t, req, resp);
return new GridFinishedFuture<>(resp);
}
}
);
}
else {
assert fut != null;
// This will send remote messages.
fut.map();
final GridCacheVersion mappedVer = fut.version();
return new GridDhtEmbeddedFuture<>(
new C2<Boolean, Exception, GridNearLockResponse>() {
@Override public GridNearLockResponse apply(Boolean b, Exception e) {
if (e != null)
e = U.unwrap(e);
else if (!b)
e = new GridCacheLockTimeoutException(req.version());
GridNearLockResponse res = createLockReply(nearNode,
entries,
req,
null,
mappedVer,
e);
sendLockReply(nearNode, null, req, res);
return res;
}
},
fut);
}
}
catch (IgniteCheckedException | RuntimeException e) {
String err = "Failed to unmarshal at least one of the keys for lock request message: " + req;
U.error(log, err, e);
if (tx != null) {
try {
tx.rollbackDhtLocal();
}
catch (IgniteCheckedException ex) {
U.error(log, "Failed to rollback the transaction: " + tx, ex);
}
}
return new GridDhtFinishedFuture<>(
new IgniteCheckedException(err, e));
}
}
/**
* @param nearNode Client node.
* @param req Request.
* @param topVer Remap version.
* @return Response.
*/
private GridNearLockResponse sendClientLockRemapResponse(ClusterNode nearNode,
GridNearLockRequest req,
AffinityTopologyVersion topVer) {
assert topVer != null;
GridNearLockResponse res = new GridNearLockResponse(
ctx.cacheId(),
req.version(),
req.futureId(),
req.miniId(),
false,
0,
null,
topVer,
ctx.deploymentEnabled());
try {
ctx.io().send(nearNode, res, ctx.ioPolicy());
}
catch (ClusterTopologyCheckedException ignored) {
if (log.isDebugEnabled())
log.debug("Failed to send client lock remap response, client node failed " +
"[node=" + nearNode + ", req=" + req + ']');
}
catch (IgniteCheckedException e) {
U.error(log, "Failed to send client lock remap response [node=" + nearNode + ", req=" + req + ']', e);
}
return res;
}
/**
* @param nearNode Near node.
* @param entries Entries.
* @param req Lock request.
* @param tx Transaction.
* @param mappedVer Mapped version.
* @param err Error.
* @return Response.
*/
private GridNearLockResponse createLockReply(
ClusterNode nearNode,
List<GridCacheEntryEx> entries,
GridNearLockRequest req,
@Nullable GridDhtTxLocalAdapter tx,
GridCacheVersion mappedVer,
Throwable err) {
assert mappedVer != null;
assert tx == null || tx.xidVersion().equals(mappedVer);
try {
// Send reply back to originating near node.
GridNearLockResponse res = new GridNearLockResponse(ctx.cacheId(),
req.version(),
req.futureId(),
req.miniId(),
tx != null && tx.onePhaseCommit(),
entries.size(),
err,
null,
ctx.deploymentEnabled());
if (err == null) {
res.pending(localDhtPendingVersions(entries, mappedVer));
// We have to add completed versions for cases when nearLocal and remote transactions
// execute concurrently.
IgnitePair<Collection<GridCacheVersion>> versPair = ctx.tm().versions(req.version());
res.completedVersions(versPair.get1(), versPair.get2());
int i = 0;
for (ListIterator<GridCacheEntryEx> it = entries.listIterator(); it.hasNext();) {
GridCacheEntryEx e = it.next();
assert e != null;
while (true) {
try {
// Don't return anything for invalid partitions.
if (tx == null || !tx.isRollbackOnly()) {
GridCacheVersion dhtVer = req.dhtVersion(i);
GridCacheVersion ver = e.version();
boolean ret = req.returnValue(i) || dhtVer == null || !dhtVer.equals(ver);
CacheObject val = null;
if (ret)
val = e.innerGet(
null,
tx,
/*read-through*/false,
/*update-metrics*/true,
/*event notification*/req.returnValue(i),
CU.subjectId(tx, ctx.shared()),
null,
tx != null ? tx.resolveTaskName() : null,
null,
req.keepBinary());
assert e.lockedBy(mappedVer) ||
(ctx.mvcc().isRemoved(e.context(), mappedVer) && req.timeout() > 0) :
"Entry does not own lock for tx [locNodeId=" + ctx.localNodeId() +
", entry=" + e +
", mappedVer=" + mappedVer + ", ver=" + ver +
", tx=" + tx + ", req=" + req +
", err=" + err + ']';
boolean filterPassed = false;
if (tx != null && tx.onePhaseCommit()) {
IgniteTxEntry writeEntry = tx.entry(ctx.txKey(e.key()));
assert writeEntry != null :
"Missing tx entry for locked cache entry: " + e;
filterPassed = writeEntry.filtersPassed();
}
if (ret && val == null)
val = e.valueBytes(null);
// We include values into response since they are required for local
// calls and won't be serialized. We are also including DHT version.
res.addValueBytes(
ret ? val : null,
filterPassed,
ver,
mappedVer);
}
else {
// We include values into response since they are required for local
// calls and won't be serialized. We are also including DHT version.
res.addValueBytes(null, false, e.version(), mappedVer);
}
break;
}
catch (GridCacheEntryRemovedException ignore) {
if (log.isDebugEnabled())
log.debug("Got removed entry when sending reply to DHT lock request " +
"(will retry): " + e);
e = entryExx(e.key());
it.set(e);
}
}
i++;
}
}
return res;
}
catch (IgniteCheckedException e) {
U.error(log, "Failed to get value for lock reply message for node [node=" +
U.toShortString(nearNode) + ", req=" + req + ']', e);
return new GridNearLockResponse(ctx.cacheId(),
req.version(),
req.futureId(),
req.miniId(),
false,
entries.size(),
e,
null,
ctx.deploymentEnabled());
}
}
/**
* Send lock reply back to near node.
*
* @param nearNode Near node.
* @param tx Transaction.
* @param req Lock request.
* @param res Lock response.
*/
private void sendLockReply(
ClusterNode nearNode,
@Nullable GridDhtTxLocal tx,
GridNearLockRequest req,
GridNearLockResponse res
) {
Throwable err = res.error();
// Log error before sending reply.
if (err != null && !(err instanceof GridCacheLockTimeoutException) && !ctx.kernalContext().isStopping())
U.error(log, "Failed to acquire lock for request: " + req, err);
try {
// Don't send reply message to this node or if lock was cancelled.
if (!nearNode.id().equals(ctx.nodeId()) && !X.hasCause(err, GridDistributedLockCancelledException.class)) {
ctx.io().send(nearNode, res, ctx.ioPolicy());
if (txLockMsgLog.isDebugEnabled()) {
txLockMsgLog.debug("Sent near lock response [txId=" + req.version() +
", inTx=" + req.inTx() +
", node=" + nearNode.id() + ']');
}
}
else {
if (txLockMsgLog.isDebugEnabled() && !nearNode.id().equals(ctx.nodeId())) {
txLockMsgLog.debug("Skip send near lock response [txId=" + req.version() +
", inTx=" + req.inTx() +
", node=" + nearNode.id() +
", err=" + err + ']');
}
}
}
catch (IgniteCheckedException e) {
U.error(txLockMsgLog, "Failed to send near lock response (will rollback transaction) [" +
"txId=" + req.version() +
", inTx=" + req.inTx() +
", node=" + nearNode.id() +
", res=" + res + ']', e);
if (tx != null)
tx.rollbackDhtLocalAsync();
// Convert to closure exception as this method is only called form closures.
throw new GridClosureException(e);
}
}
/**
* Collects versions of pending candidates versions less then base.
*
* @param entries Tx entries to process.
* @param baseVer Base version.
* @return Collection of pending candidates versions.
*/
private Collection<GridCacheVersion> localDhtPendingVersions(Iterable<GridCacheEntryEx> entries,
GridCacheVersion baseVer) {
Collection<GridCacheVersion> lessPending = new GridLeanSet<>(5);
for (GridCacheEntryEx entry : entries) {
// Since entries were collected before locks are added, some of them may become obsolete.
while (true) {
try {
for (GridCacheMvccCandidate cand : entry.localCandidates()) {
if (cand.version().isLess(baseVer))
lessPending.add(cand.version());
}
break; // While.
}
catch (GridCacheEntryRemovedException ignored) {
if (log.isDebugEnabled())
log.debug("Got removed entry is localDhtPendingVersions (will retry): " + entry);
entry = entryExx(entry.key());
}
}
}
return lessPending;
}
/**
* @param nodeId Node ID.
* @param req Request.
*/
@SuppressWarnings({"RedundantTypeArguments"})
private void clearLocks(UUID nodeId, GridDistributedUnlockRequest req) {
assert nodeId != null;
List<KeyCacheObject> keys = req.keys();
if (keys != null) {
for (KeyCacheObject key : keys) {
while (true) {
GridDistributedCacheEntry entry = peekExx(key);
if (entry == null)
// Nothing to unlock.
break;
try {
entry.doneRemote(
req.version(),
req.version(),
null,
null,
null,
/*system invalidate*/false);
// Note that we don't reorder completed versions here,
// as there is no point to reorder relative to the version
// we are about to remove.
if (entry.removeLock(req.version())) {
if (log.isDebugEnabled())
log.debug("Removed lock [lockId=" + req.version() + ", key=" + key + ']');
}
else {
if (log.isDebugEnabled())
log.debug("Received unlock request for unknown candidate " +
"(added to cancelled locks set): " + req);
}
ctx.evicts().touch(entry, ctx.affinity().affinityTopologyVersion());
break;
}
catch (GridCacheEntryRemovedException ignored) {
if (log.isDebugEnabled())
log.debug("Received remove lock request for removed entry (will retry) [entry=" +
entry + ", req=" + req + ']');
}
}
}
}
}
/**
* @param nodeId Sender ID.
* @param req Request.
*/
@SuppressWarnings({"RedundantTypeArguments", "TypeMayBeWeakened"})
private void processNearUnlockRequest(UUID nodeId, GridNearUnlockRequest req) {
assert ctx.affinityNode();
assert nodeId != null;
removeLocks(nodeId, req.version(), req.keys(), true);
}
/**
* @param nodeId Sender node ID.
* @param topVer Topology version.
* @param cached Entry.
* @param readers Readers for this entry.
* @param dhtMap DHT map.
* @param nearMap Near map.
* @throws IgniteCheckedException If failed.
*/
private void map(UUID nodeId,
AffinityTopologyVersion topVer,
GridCacheEntryEx cached,
Collection<UUID> readers,
Map<ClusterNode, List<KeyCacheObject>> dhtMap,
Map<ClusterNode, List<KeyCacheObject>> nearMap)
throws IgniteCheckedException {
List<ClusterNode> dhtNodes = ctx.dht().topology().nodes(cached.partition(), topVer);
ClusterNode primary = dhtNodes.get(0);
assert primary != null;
if (!primary.id().equals(ctx.nodeId())) {
if (log.isDebugEnabled())
log.debug("Primary node mismatch for unlock [entry=" + cached + ", expected=" + ctx.nodeId() +
", actual=" + U.toShortString(primary) + ']');
return;
}
if (log.isDebugEnabled())
log.debug("Mapping entry to DHT nodes [nodes=" + U.toShortString(dhtNodes) + ", entry=" + cached + ']');
Collection<ClusterNode> nearNodes = null;
if (!F.isEmpty(readers)) {
nearNodes = ctx.discovery().nodes(readers, F0.not(F.idForNodeId(nodeId)));
if (log.isDebugEnabled())
log.debug("Mapping entry to near nodes [nodes=" + U.toShortString(nearNodes) + ", entry=" + cached +
']');
}
else {
if (log.isDebugEnabled())
log.debug("Entry has no near readers: " + cached);
}
map(cached, F.view(dhtNodes, F.remoteNodes(ctx.nodeId())), dhtMap); // Exclude local node.
map(cached, nearNodes, nearMap);
}
/**
* @param entry Entry.
* @param nodes Nodes.
* @param map Map.
*/
@SuppressWarnings( {"MismatchedQueryAndUpdateOfCollection"})
private void map(GridCacheEntryEx entry,
@Nullable Iterable<? extends ClusterNode> nodes,
Map<ClusterNode, List<KeyCacheObject>> map) {
if (nodes != null) {
for (ClusterNode n : nodes) {
List<KeyCacheObject> keys = map.get(n);
if (keys == null)
map.put(n, keys = new LinkedList<>());
keys.add(entry.key());
}
}
}
/**
* @param nodeId Node ID.
* @param ver Version.
* @param keys Keys.
* @param unmap Flag for un-mapping version.
*/
public void removeLocks(UUID nodeId, GridCacheVersion ver, Iterable<KeyCacheObject> keys, boolean unmap) {
assert nodeId != null;
assert ver != null;
if (F.isEmpty(keys))
return;
// Remove mapped versions.
GridCacheVersion dhtVer = unmap ? ctx.mvcc().unmapVersion(ver) : ver;
Map<ClusterNode, List<KeyCacheObject>> dhtMap = new HashMap<>();
Map<ClusterNode, List<KeyCacheObject>> nearMap = new HashMap<>();
GridCacheVersion obsoleteVer = null;
for (KeyCacheObject key : keys) {
while (true) {
boolean created = false;
GridDhtCacheEntry entry = peekExx(key);
if (entry == null) {
entry = entryExx(key);
created = true;
}
try {
GridCacheMvccCandidate cand = null;
if (dhtVer == null) {
cand = entry.localCandidateByNearVersion(ver, true);
if (cand != null)
dhtVer = cand.version();
else {
if (log.isDebugEnabled())
log.debug("Failed to locate lock candidate based on dht or near versions [nodeId=" +
nodeId + ", ver=" + ver + ", unmap=" + unmap + ", keys=" + keys + ']');
entry.removeLock(ver);
if (created) {
if (obsoleteVer == null)
obsoleteVer = ctx.versions().next();
if (entry.markObsolete(obsoleteVer))
removeEntry(entry);
}
break;
}
}
if (cand == null)
cand = entry.candidate(dhtVer);
AffinityTopologyVersion topVer = cand == null
? AffinityTopologyVersion.NONE
: cand.topologyVersion();
// Note that we obtain readers before lock is removed.
// Even in case if entry would be removed just after lock is removed,
// we must send release messages to backups and readers.
Collection<UUID> readers = entry.readers();
// Note that we don't reorder completed versions here,
// as there is no point to reorder relative to the version
// we are about to remove.
if (entry.removeLock(dhtVer)) {
// Map to backups and near readers.
map(nodeId, topVer, entry, readers, dhtMap, nearMap);
if (log.isDebugEnabled())
log.debug("Removed lock [lockId=" + ver + ", key=" + key + ']');
}
else if (log.isDebugEnabled())
log.debug("Received unlock request for unknown candidate " +
"(added to cancelled locks set) [ver=" + ver + ", entry=" + entry + ']');
if (created && entry.markObsolete(dhtVer))
removeEntry(entry);
ctx.evicts().touch(entry, topVer);
break;
}
catch (GridCacheEntryRemovedException ignored) {
if (log.isDebugEnabled())
log.debug("Received remove lock request for removed entry (will retry): " + entry);
}
catch (IgniteCheckedException e) {
U.error(log, "Failed to remove locks for keys: " + keys, e);
}
}
}
IgnitePair<Collection<GridCacheVersion>> versPair = ctx.tm().versions(ver);
Collection<GridCacheVersion> committed = versPair.get1();
Collection<GridCacheVersion> rolledback = versPair.get2();
// Backups.
for (Map.Entry<ClusterNode, List<KeyCacheObject>> entry : dhtMap.entrySet()) {
ClusterNode n = entry.getKey();
List<KeyCacheObject> keyBytes = entry.getValue();
GridDhtUnlockRequest req = new GridDhtUnlockRequest(ctx.cacheId(), keyBytes.size(),
ctx.deploymentEnabled());
req.version(dhtVer);
try {
for (KeyCacheObject key : keyBytes)
req.addKey(key, ctx);
keyBytes = nearMap.get(n);
if (keyBytes != null)
for (KeyCacheObject key : keyBytes)
req.addNearKey(key);
req.completedVersions(committed, rolledback);
ctx.io().send(n, req, ctx.ioPolicy());
}
catch (ClusterTopologyCheckedException ignore) {
if (log.isDebugEnabled())
log.debug("Node left while sending unlock request: " + n);
}
catch (IgniteCheckedException e) {
U.error(log, "Failed to send unlock request to node (will make best effort to complete): " + n, e);
}
}
// Readers.
for (Map.Entry<ClusterNode, List<KeyCacheObject>> entry : nearMap.entrySet()) {
ClusterNode n = entry.getKey();
if (!dhtMap.containsKey(n)) {
List<KeyCacheObject> keyBytes = entry.getValue();
GridDhtUnlockRequest req = new GridDhtUnlockRequest(ctx.cacheId(), keyBytes.size(),
ctx.deploymentEnabled());
req.version(dhtVer);
try {
for (KeyCacheObject key : keyBytes)
req.addNearKey(key);
req.completedVersions(committed, rolledback);
ctx.io().send(n, req, ctx.ioPolicy());
}
catch (ClusterTopologyCheckedException ignore) {
if (log.isDebugEnabled())
log.debug("Node left while sending unlock request: " + n);
}
catch (IgniteCheckedException e) {
U.error(log, "Failed to send unlock request to node (will make best effort to complete): " + n, e);
}
}
}
}
/**
* @param key Key
* @param ver Version.
* @throws IgniteCheckedException If invalidate failed.
*/
private void invalidateNearEntry(KeyCacheObject key, GridCacheVersion ver) throws IgniteCheckedException {
GridCacheEntryEx nearEntry = near().peekEx(key);
if (nearEntry != null)
nearEntry.invalidate(null, ver);
}
/**
* @param key Key
*/
private void obsoleteNearEntry(KeyCacheObject key) {
GridCacheEntryEx nearEntry = near().peekEx(key);
if (nearEntry != null)
nearEntry.markObsolete(ctx.versions().next());
}
}