/* * 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.util.future; import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.IgniteLogger; import org.apache.ignite.internal.IgniteFutureCancelledCheckedException; import org.apache.ignite.internal.IgniteInternalFuture; import org.apache.ignite.internal.NodeStoppingException; import org.apache.ignite.internal.cluster.ClusterTopologyCheckedException; import org.apache.ignite.internal.transactions.IgniteTxOptimisticCheckedException; import org.apache.ignite.internal.util.tostring.GridToStringInclude; import org.apache.ignite.internal.util.typedef.C1; import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.internal.util.typedef.internal.S; import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.lang.IgniteInClosure; import org.apache.ignite.lang.IgniteReducer; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; /** * Future composed of multiple inner futures. */ public class GridCompoundFuture<T, R> extends GridFutureAdapter<R> implements IgniteInClosure<IgniteInternalFuture<T>> { /** */ private static final long serialVersionUID = 0L; /** Initialization flag. */ private static final int INIT_FLAG = 0x1; /** Flags updater. */ private static final AtomicIntegerFieldUpdater<GridCompoundFuture> FLAGS_UPD = AtomicIntegerFieldUpdater.newUpdater(GridCompoundFuture.class, "initFlag"); /** Listener calls updater. */ private static final AtomicIntegerFieldUpdater<GridCompoundFuture> LSNR_CALLS_UPD = AtomicIntegerFieldUpdater.newUpdater(GridCompoundFuture.class, "lsnrCalls"); /** Possible values: null (no future), IgniteInternalFuture instance (single future) or List of futures */ private volatile Object futs; /** Reducer. */ @GridToStringInclude private final IgniteReducer<T, R> rdc; /** Initialization flag. Updated via {@link #FLAGS_UPD}. */ @SuppressWarnings("unused") private volatile int initFlag; /** Listener calls. Updated via {@link #LSNR_CALLS_UPD}. */ @SuppressWarnings("unused") private volatile int lsnrCalls; /** * Default constructor. */ public GridCompoundFuture() { this(null); } /** * @param rdc Reducer. */ public GridCompoundFuture(@Nullable IgniteReducer<T, R> rdc) { this.rdc = rdc; } /** {@inheritDoc} */ @Override public final void apply(IgniteInternalFuture<T> fut) { try { T t = fut.get(); try { if (rdc != null && !rdc.collect(t)) onDone(rdc.reduce()); } catch (RuntimeException e) { U.error(null, "Failed to execute compound future reducer: " + this, e); // Exception in reducer is a bug, so we bypass checkComplete here. onDone(e); } catch (AssertionError e) { U.error(null, "Failed to execute compound future reducer: " + this, e); // Bypass checkComplete because need to rethrow. onDone(e); throw e; } } catch (IgniteTxOptimisticCheckedException | IgniteFutureCancelledCheckedException | ClusterTopologyCheckedException e) { if (!ignoreFailure(e)) onDone(e); } catch (IgniteCheckedException e) { if (!ignoreFailure(e)) { if (e instanceof NodeStoppingException) { IgniteLogger log = logger(); if (log != null && log.isDebugEnabled()) log.debug("Failed to execute compound future reducer, node stopped."); } else U.error(null, "Failed to execute compound future reducer: " + this, e); onDone(e); } } catch (RuntimeException e) { U.error(null, "Failed to execute compound future reducer: " + this, e); onDone(e); } catch (AssertionError e) { U.error(null, "Failed to execute compound future reducer: " + this, e); // Bypass checkComplete because need to rethrow. onDone(e); throw e; } LSNR_CALLS_UPD.incrementAndGet(this); checkComplete(); } /** {@inheritDoc} */ @Override public boolean cancel() throws IgniteCheckedException { if (onCancelled()) { for (IgniteInternalFuture<T> fut : futures()) fut.cancel(); return true; } return false; } /** * Gets collection of futures. * * @return Collection of futures. */ @SuppressWarnings("unchecked") public synchronized final Collection<IgniteInternalFuture<T>> futures() { if (futs == null) return Collections.emptyList(); if (futs instanceof IgniteInternalFuture) return Collections.singletonList((IgniteInternalFuture<T>)futs); return new ArrayList<>((Collection<IgniteInternalFuture<T>>)futs); } /** * Checks if this compound future should ignore this particular exception. * * @param err Exception to check. * @return {@code True} if this error should be ignored. */ protected boolean ignoreFailure(Throwable err) { return false; } /** * Checks if there are pending futures. This is not the same as * {@link #isDone()} because child classes may override {@link #onDone(Object, Throwable)} * call and delay completion. * * @return {@code True} if there are pending futures. */ @SuppressWarnings("ForLoopReplaceableByForEach") protected final boolean hasPending() { synchronized (this) { int size = futuresCountNoLock(); // Avoid iterator creation and collection copy. for (int i = 0; i < size; i++) { IgniteInternalFuture<T> fut = future(i); if (!fut.isDone()) return true; } } return false; } /** * Adds a future to this compound future. * * @param fut Future to add. */ @SuppressWarnings("unchecked") public final void add(IgniteInternalFuture<T> fut) { assert fut != null; synchronized (this) { if (futs == null) futs = fut; else if (futs instanceof IgniteInternalFuture) { Collection<IgniteInternalFuture> futs0 = new ArrayList<>(4); futs0.add((IgniteInternalFuture)futs); futs0.add(fut); futs = futs0; } else ((Collection<IgniteInternalFuture>)futs).add(fut); } fut.listen(this); if (isCancelled()) { try { fut.cancel(); } catch (IgniteCheckedException e) { onDone(e); } } } /** * Clear futures. */ protected synchronized final void clear() { futs = null; } /** * @return {@code True} if this future was initialized. Initialization happens when {@link #markInitialized()} * method is called on future. */ public final boolean initialized() { return initFlag == INIT_FLAG; } /** * Mark this future as initialized. */ public final void markInitialized() { if (FLAGS_UPD.compareAndSet(this, 0, INIT_FLAG)) checkComplete(); } /** * Check completeness of the future. */ private void checkComplete() { if (initialized() && !isDone() && lsnrCalls == futuresCount()) { try { onDone(rdc != null ? rdc.reduce() : null); } catch (RuntimeException e) { U.error(null, "Failed to execute compound future reducer: " + this, e); onDone(e); } catch (AssertionError e) { U.error(null, "Failed to execute compound future reducer: " + this, e); onDone(e); throw e; } } } /** * Returns future at the specified position in this list. * * @param idx - index index of the element to return * @return Future. */ @SuppressWarnings("unchecked") protected final IgniteInternalFuture<T> future(int idx) { assert Thread.holdsLock(this); assert futs != null && idx >= 0 && idx < futuresCountNoLock(); if (futs instanceof IgniteInternalFuture) { assert idx == 0; return (IgniteInternalFuture<T>)futs; } else return ((List<IgniteInternalFuture>)futs).get(idx); } /** * @return Futures size. */ @SuppressWarnings("unchecked") protected final int futuresCountNoLock() { assert Thread.holdsLock(this); if (futs == null) return 0; if (futs instanceof IgniteInternalFuture) return 1; return ((Collection<IgniteInternalFuture>)futs).size(); } /** * @return Futures size. */ private synchronized int futuresCount() { return futuresCountNoLock(); } /** * @return {@code True} if has at least one future. */ protected synchronized final boolean hasFutures() { return futs != null; } /** {@inheritDoc} */ @Override public String toString() { return S.toString(GridCompoundFuture.class, this, "done", isDone(), "cancelled", isCancelled(), "err", error(), "futs", F.viewReadOnly(futures(), new C1<IgniteInternalFuture<T>, String>() { @Override public String apply(IgniteInternalFuture<T> f) { return Boolean.toString(f.isDone()); } }) ); } }