/* * 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.atomic; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.UUID; import java.util.concurrent.atomic.AtomicReference; import javax.cache.expiry.ExpiryPolicy; import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.IgniteLogger; import org.apache.ignite.cache.CacheWriteSynchronizationMode; import org.apache.ignite.cluster.ClusterNode; import org.apache.ignite.internal.IgniteInternalFuture; 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.CachePartialUpdateCheckedException; import org.apache.ignite.internal.processors.cache.GridCacheAtomicFuture; import org.apache.ignite.internal.processors.cache.GridCacheContext; import org.apache.ignite.internal.processors.cache.GridCacheFutureAdapter; import org.apache.ignite.internal.processors.cache.GridCacheMvccManager; import org.apache.ignite.internal.processors.cache.GridCacheOperation; import org.apache.ignite.internal.processors.cache.GridCacheReturn; import org.apache.ignite.internal.processors.cache.KeyCacheObject; import org.apache.ignite.internal.util.future.GridFutureAdapter; import org.apache.ignite.internal.util.tostring.GridToStringInclude; import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.internal.util.typedef.internal.CU; import org.apache.ignite.internal.util.typedef.internal.S; import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.lang.IgniteUuid; import org.jetbrains.annotations.Nullable; import static org.apache.ignite.cache.CacheWriteSynchronizationMode.FULL_ASYNC; import static org.apache.ignite.cache.CacheWriteSynchronizationMode.FULL_SYNC; import static org.apache.ignite.cache.CacheWriteSynchronizationMode.PRIMARY_SYNC; import static org.apache.ignite.internal.processors.cache.GridCacheOperation.TRANSFORM; /** * Base for near atomic update futures. */ public abstract class GridNearAtomicAbstractUpdateFuture extends GridCacheFutureAdapter<Object> implements GridCacheAtomicFuture<Object> { /** Logger reference. */ private static final AtomicReference<IgniteLogger> logRef = new AtomicReference<>(); /** Logger. */ protected static IgniteLogger log; /** Logger. */ protected static IgniteLogger msgLog; /** Cache context. */ protected final GridCacheContext cctx; /** Cache. */ protected final GridDhtAtomicCache cache; /** Write synchronization mode. */ protected final CacheWriteSynchronizationMode syncMode; /** Update operation. */ protected final GridCacheOperation op; /** Optional arguments for entry processor. */ protected final Object[] invokeArgs; /** Return value require flag. */ protected final boolean retval; /** Raw return value flag. */ protected final boolean rawRetval; /** Expiry policy. */ protected final ExpiryPolicy expiryPlc; /** Optional filter. */ protected final CacheEntryPredicate[] filter; /** Subject ID. */ protected final UUID subjId; /** Task name hash. */ protected final int taskNameHash; /** Skip store flag. */ protected final boolean skipStore; /** Keep binary flag. */ protected final boolean keepBinary; /** Recovery flag. */ protected final boolean recovery; /** Near cache flag. */ protected final boolean nearEnabled; /** Topology locked flag. Set if atomic update is performed inside a TX or explicit lock. */ protected boolean topLocked; /** Remap count. */ @GridToStringInclude protected int remapCnt; /** Current topology version. */ @GridToStringInclude protected AffinityTopologyVersion topVer = AffinityTopologyVersion.ZERO; /** */ @GridToStringInclude protected AffinityTopologyVersion remapTopVer; /** Error. */ @GridToStringInclude protected CachePartialUpdateCheckedException err; /** Future ID, changes when operation is remapped. */ @GridToStringInclude protected long futId; /** Operation result. */ protected GridCacheReturn opRes; /** * Constructor. * * @param cctx Cache context. * @param cache Cache. * @param syncMode Synchronization mode. * @param op Operation. * @param invokeArgs Invoke arguments. * @param retval Return value flag. * @param rawRetval Raw return value flag. * @param expiryPlc Expiry policy. * @param filter Filter. * @param subjId Subject ID. * @param taskNameHash Task name hash. * @param skipStore Skip store flag. * @param keepBinary Keep binary flag. * @param recovery {@code True} if cache operation is called in recovery mode. * @param remapCnt Remap count. */ protected GridNearAtomicAbstractUpdateFuture( GridCacheContext cctx, GridDhtAtomicCache cache, CacheWriteSynchronizationMode syncMode, GridCacheOperation op, @Nullable Object[] invokeArgs, boolean retval, boolean rawRetval, @Nullable ExpiryPolicy expiryPlc, CacheEntryPredicate[] filter, UUID subjId, int taskNameHash, boolean skipStore, boolean keepBinary, boolean recovery, int remapCnt ) { if (log == null) { msgLog = cctx.shared().atomicMessageLogger(); log = U.logger(cctx.kernalContext(), logRef, GridFutureAdapter.class); } this.cctx = cctx; this.cache = cache; this.syncMode = syncMode; this.op = op; this.invokeArgs = invokeArgs; this.retval = retval; this.rawRetval = rawRetval; this.expiryPlc = expiryPlc; this.filter = filter; this.subjId = subjId; this.taskNameHash = taskNameHash; this.skipStore = skipStore; this.keepBinary = keepBinary; this.recovery = recovery; nearEnabled = CU.isNearEnabled(cctx); this.remapCnt = remapCnt; } /** * @return {@code True} if future was initialized and waits for responses. */ final boolean futureMapped() { return topVer != AffinityTopologyVersion.ZERO; } /** * @param futId Expected future ID. * @return {@code True} if future was initialized with the same ID. */ final boolean checkFutureId(long futId) { return topVer != AffinityTopologyVersion.ZERO && this.futId == futId; } /** {@inheritDoc} */ @Override public final IgniteInternalFuture<Void> completeFuture(AffinityTopologyVersion topVer) { return null; } /** * @param req Request. */ void sendCheckUpdateRequest(GridNearAtomicCheckUpdateRequest req) { try { cctx.io().send(req.updateRequest().nodeId(), req, cctx.ioPolicy()); } catch (ClusterTopologyCheckedException e) { onSendError(req, e); } catch (IgniteCheckedException e) { completeFuture(null, e, req.futureId()); } } /** * Performs future mapping. */ public final void map() { AffinityTopologyVersion topVer = cctx.shared().lockedTopologyVersion(null); if (topVer == null) mapOnTopology(); else { topLocked = true; // Cannot remap. remapCnt = 1; map(topVer); } } /** * @param topVer Topology version. */ protected abstract void map(AffinityTopologyVersion topVer); /** * Maps future on ready topology. */ protected abstract void mapOnTopology(); /** {@inheritDoc} */ @Override public IgniteUuid futureId() { throw new UnsupportedOperationException(); } /** {@inheritDoc} */ @Override public boolean trackable() { return true; } /** {@inheritDoc} */ @Override public void markNotTrackable() { // No-op. } /** * @return {@code True} future is stored by {@link GridCacheMvccManager#addAtomicFuture}. */ final boolean storeFuture() { return syncMode != FULL_ASYNC; } /** * Maps future to single node. * * @param nodeId Node ID. * @param req Request. */ final void sendSingleRequest(UUID nodeId, GridNearAtomicAbstractUpdateRequest req) { if (cctx.localNodeId().equals(nodeId)) { cache.updateAllAsyncInternal(nodeId, req, new GridDhtAtomicCache.UpdateReplyClosure() { @Override public void apply(GridNearAtomicAbstractUpdateRequest req, GridNearAtomicUpdateResponse res) { if (syncMode != FULL_ASYNC) onPrimaryResponse(res.nodeId(), res, false); else if (res.remapTopologyVersion() != null) ((GridDhtAtomicCache)cctx.cache()).remapToNewPrimary(req); } }); } else { try { cctx.io().send(req.nodeId(), req, cctx.ioPolicy()); if (msgLog.isDebugEnabled()) { msgLog.debug("Near update fut, sent request [futId=" + req.futureId() + ", node=" + req.nodeId() + ']'); } } catch (IgniteCheckedException e) { if (msgLog.isDebugEnabled()) { msgLog.debug("Near update fut, failed to send request [futId=" + req.futureId() + ", node=" + req.nodeId() + ", err=" + e + ']'); } onSendError(req, e); } } } /** * Response callback. * * @param nodeId Node ID. * @param res Update response. * @param nodeErr {@code True} if response was created on node failure. */ public abstract void onPrimaryResponse(UUID nodeId, GridNearAtomicUpdateResponse res, boolean nodeErr); /** * @param nodeId Node ID. * @param res Response. */ public abstract void onDhtResponse(UUID nodeId, GridDhtAtomicNearResponse res); /** * @param ret Result. * @param err Error. * @param futId Not null ID if need remove future. */ final void completeFuture(@Nullable GridCacheReturn ret, Throwable err, @Nullable Long futId) { Object retval = ret == null ? null : rawRetval ? ret : (this.retval || op == TRANSFORM) ? cctx.unwrapBinaryIfNeeded(ret.value(), keepBinary) : ret.success(); if (op == TRANSFORM && retval == null) retval = Collections.emptyMap(); if (futId != null) cctx.mvcc().removeAtomicFuture(futId); super.onDone(retval, err); } /** {@inheritDoc} */ @SuppressWarnings("ConstantConditions") @Override public final boolean onDone(@Nullable Object res, @Nullable Throwable err) { assert err != null : "onDone should be called only to finish future with error on cache/node stop"; Long futId = null; synchronized (this) { if (futureMapped()) { futId = this.futId; topVer = AffinityTopologyVersion.ZERO; this.futId = 0; } } if (super.onDone(null, err)) { if (futId != null) cctx.mvcc().removeAtomicFuture(futId); return true; } return false; } /** * @param req Request. * @param res Response. */ final void onPrimaryError(GridNearAtomicAbstractUpdateRequest req, GridNearAtomicUpdateResponse res) { assert res.error() != null; if (err == null) err = new CachePartialUpdateCheckedException("Failed to update keys (retry update if possible)."); Collection<KeyCacheObject> keys0 = res.failedKeys() != null ? res.failedKeys() : req.keys(); Collection<Object> keys = new ArrayList<>(keys0.size()); for (KeyCacheObject key : keys0) keys.add(cctx.cacheObjectContext().unwrapBinaryIfNeeded(key, keepBinary, false)); err.add(keys, res.error(), req.topologyVersion()); } /** * @param req Request. * @return Response to notify about primary failure. */ final GridNearAtomicUpdateResponse primaryFailedResponse(GridNearAtomicAbstractUpdateRequest req) { assert req.response() == null : req; assert req.nodeId() != null : req; if (msgLog.isDebugEnabled()) { msgLog.debug("Near update fut, node left [futId=" + req.futureId() + ", node=" + req.nodeId() + ']'); } GridNearAtomicUpdateResponse res = new GridNearAtomicUpdateResponse(cctx.cacheId(), req.nodeId(), req.futureId(), req.partition(), true, cctx.deploymentEnabled()); ClusterTopologyCheckedException e = new ClusterTopologyCheckedException("Primary node left grid " + "before response is received: " + req.nodeId()); e.retryReadyFuture(cctx.shared().nextAffinityReadyFuture(req.topologyVersion())); res.addFailedKeys(req.keys(), e); return res; } /** * @param req Request. * @param e Error. */ final void onSendError(GridNearAtomicAbstractUpdateRequest req, IgniteCheckedException e) { GridNearAtomicUpdateResponse res = new GridNearAtomicUpdateResponse(cctx.cacheId(), req.nodeId(), req.futureId(), req.partition(), e instanceof ClusterTopologyCheckedException, cctx.deploymentEnabled()); res.addFailedKeys(req.keys(), e); onPrimaryResponse(req.nodeId(), res, true); } /** * @param req Request. * @param e Error. */ private void onSendError(GridNearAtomicCheckUpdateRequest req, IgniteCheckedException e) { GridNearAtomicUpdateResponse res = new GridNearAtomicUpdateResponse(cctx.cacheId(), req.updateRequest().nodeId(), req.futureId(), req.partition(), e instanceof ClusterTopologyCheckedException, cctx.deploymentEnabled()); res.addFailedKeys(req.updateRequest().keys(), e); onPrimaryResponse(req.updateRequest().nodeId(), res, true); } /** * */ static class PrimaryRequestState { /** */ final GridNearAtomicAbstractUpdateRequest req; /** */ @GridToStringInclude Set<UUID> dhtNodes; /** */ @GridToStringInclude private Set<UUID> rcvd; /** */ private boolean hasRes; /** * @param req Request. * @param nodes Affinity nodes. * @param single {@code True} if created for sigle-key operation. */ PrimaryRequestState(GridNearAtomicAbstractUpdateRequest req, List<ClusterNode> nodes, boolean single) { assert req != null && req.nodeId() != null : req; this.req = req; if (req.initMappingLocally()) { if (single) { if (nodes.size() > 1) { dhtNodes = U.newHashSet(nodes.size() - 1); for (int i = 1; i < nodes.size(); i++) dhtNodes.add(nodes.get(i).id()); } else dhtNodes = Collections.emptySet(); } else { dhtNodes = new HashSet<>(); for (int i = 1; i < nodes.size(); i++) dhtNodes.add(nodes.get(i).id()); } } } /** * @return Primary node ID. */ UUID primaryId() { return req.nodeId(); } /** * @param nodes Nodes. */ void addMapping(List<ClusterNode> nodes) { assert req.initMappingLocally(); for (int i = 1; i < nodes.size(); i++) dhtNodes.add(nodes.get(i).id()); } /** * @param cctx Context. * @return Check result. */ DhtLeftResult checkDhtNodes(GridCacheContext cctx) { assert req.initMappingLocally() : req; if (finished()) return DhtLeftResult.NOT_DONE; boolean finished = false; for (Iterator<UUID> it = dhtNodes.iterator(); it.hasNext();) { UUID nodeId = it.next(); if (!cctx.discovery().alive(nodeId)) { it.remove(); if (finished()) { finished = true; break; } } } if (finished) return DhtLeftResult.DONE; if (dhtNodes.isEmpty()) return !req.needPrimaryResponse() ? DhtLeftResult.ALL_RCVD_CHECK_PRIMARY : DhtLeftResult.NOT_DONE; return DhtLeftResult.NOT_DONE; } /** * @return {@code True} if all expected responses are received. */ private boolean finished() { if (req.writeSynchronizationMode() == PRIMARY_SYNC) return hasRes; return (dhtNodes != null && dhtNodes.isEmpty()) && hasRes; } /** * @return Request if need process primary fail response, {@code null} otherwise. */ @Nullable GridNearAtomicAbstractUpdateRequest onPrimaryFail() { if (finished() || req.nodeFailedResponse()) return null; /* * When primary failed, even if primary response is received, it is possible it failed to send * request to backup(s), need remap operation. */ if (req.fullSync() && !req.nodeFailedResponse()) { req.resetResponse(); return req; } return req.response() == null ? req : null; } /** * @param nodeId Node ID. * @param res Response. * @return Request if need process primary response, {@code null} otherwise. */ @Nullable GridNearAtomicAbstractUpdateRequest processPrimaryResponse(UUID nodeId, GridNearAtomicUpdateResponse res) { assert req.nodeId().equals(nodeId); if (res.nodeLeftResponse()) return onPrimaryFail(); if (finished()) return null; return req.response() == null ? req : null; } /** * @param nodeId Node ID. * @return {@code True} if request processing finished. */ DhtLeftResult onDhtNodeLeft(UUID nodeId) { if (req.writeSynchronizationMode() != FULL_SYNC || dhtNodes == null || finished()) return DhtLeftResult.NOT_DONE; if (dhtNodes.remove(nodeId) && dhtNodes.isEmpty()) { if (hasRes) return DhtLeftResult.DONE; else return !req.needPrimaryResponse() ? DhtLeftResult.ALL_RCVD_CHECK_PRIMARY : DhtLeftResult.NOT_DONE; } return DhtLeftResult.NOT_DONE; } /** * @param nodeId Node ID. * @param res Response. * @return {@code True} if request processing finished. */ boolean onDhtResponse(UUID nodeId, GridDhtAtomicNearResponse res) { assert req.writeSynchronizationMode() == FULL_SYNC : req; if (finished()) return false; if (res.hasResult()) hasRes = true; if (dhtNodes == null) { if (rcvd == null) rcvd = new HashSet<>(); rcvd.add(nodeId); return false; } return dhtNodes.remove(nodeId) && finished(); } /** * @param res Response. * @param cctx Cache context. * @return {@code True} if request processing finished. */ boolean onPrimaryResponse(GridNearAtomicUpdateResponse res, GridCacheContext cctx) { assert !finished() : this; hasRes = true; boolean onRes = req.onResponse(res); assert onRes; if (res.error() != null || res.remapTopologyVersion() != null) { dhtNodes = Collections.emptySet(); // Mark as finished. return true; } assert res.returnValue() != null : res; if (res.dhtNodes() != null) initDhtNodes(res.dhtNodes(), cctx); return finished(); } /** * @param nodeIds Node IDs. * @param cctx Context. */ private void initDhtNodes(List<UUID> nodeIds, GridCacheContext cctx) { assert dhtNodes == null || req.initMappingLocally(); Set<UUID> dhtNodes0 = dhtNodes; dhtNodes = null; for (UUID dhtNodeId : nodeIds) { if (F.contains(rcvd, dhtNodeId)) continue; if (req.initMappingLocally() && !F.contains(dhtNodes0, dhtNodeId)) continue; if (cctx.discovery().node(dhtNodeId) != null) { if (dhtNodes == null) dhtNodes = U.newHashSet(nodeIds.size()); dhtNodes.add(dhtNodeId); } } if (dhtNodes == null) dhtNodes = Collections.emptySet(); } /** {@inheritDoc} */ @Override public String toString() { return S.toString(PrimaryRequestState.class, this, "primary", primaryId(), "needPrimaryRes", req.needPrimaryResponse(), "primaryRes", req.response() != null, "done", finished()); } } /** * */ enum DhtLeftResult { /** All responses and operation result are received. */ DONE, /** Not all responses are received. */ NOT_DONE, /** * All backups failed and response from primary is not required, * in this case in FULL_SYNC mode need send additional request * on primary to ensure FULL_SYNC guarantee. */ ALL_RCVD_CHECK_PRIMARY } /** {@inheritDoc} */ @Override public String toString() { return S.toString(GridNearAtomicAbstractUpdateFuture.class, this, super.toString()); } }