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

dev.mccue.guava.concurrent.AggregateFutureState Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2015 The Guava Authors
 *
 * Licensed 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 dev.mccue.guava.concurrent;

import static dev.mccue.guava.collect.Sets.newConcurrentHashSet;
import static java.util.Objects.requireNonNull;
import static java.util.concurrent.atomic.AtomicIntegerFieldUpdater.newUpdater;
import static java.util.concurrent.atomic.AtomicReferenceFieldUpdater.newUpdater;

import java.util.Set;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.lang.System.Logger.Level;
import dev.mccue.jsr305.CheckForNull;
import org.checkerframework.checker.nullness.qual.Nullable;

/**
 * A helper which does some thread-safe operations for aggregate futures, which must be implemented
 * differently in GWT. Namely:
 *
 * 
    *
  • Lazily initializes a set of seen exceptions *
  • Decrements a counter atomically *
*/ @ElementTypesAreNonnullByDefault abstract class AggregateFutureState extends AbstractFuture.TrustedFuture { // Lazily initialized the first time we see an exception; not released until all the input futures // have completed and we have processed them all. @CheckForNull private volatile Set seenExceptions = null; private volatile int remaining; private static final AtomicHelper ATOMIC_HELPER; private static final LazyLogger log = new LazyLogger(AggregateFutureState.class); static { AtomicHelper helper; Throwable thrownReflectionFailure = null; try { helper = new SafeAtomicHelper( newUpdater(AggregateFutureState.class, Set.class, "seenExceptions"), newUpdater(AggregateFutureState.class, "remaining")); } catch (Throwable reflectionFailure) { // sneaky checked exception // Some Android 5.0.x Samsung devices have bugs in JDK reflection APIs that cause // getDeclaredField to throw a NoSuchFieldException when the field is definitely there. // For these users fallback to a suboptimal implementation, based on synchronized. This will // be a definite performance hit to those users. thrownReflectionFailure = reflectionFailure; helper = new SynchronizedAtomicHelper(); } ATOMIC_HELPER = helper; // Log after all static init is finished; if an installed logger uses any Futures methods, it // shouldn't break in cases where reflection is missing/broken. if (thrownReflectionFailure != null) { log.get().log(Level.ERROR, "SafeAtomicHelper is broken!", thrownReflectionFailure); } } AggregateFutureState(int remainingFutures) { this.remaining = remainingFutures; } final Set getOrInitSeenExceptions() { /* * The initialization of seenExceptions has to be more complicated than we'd like. The simple * approach would be for each caller CAS it from null to a Set populated with its exception. But * there's another race: If the first thread fails with an exception and a second thread * immediately fails with the same exception: * * Thread1: calls setException(), which returns true, context switch before it can CAS * seenExceptions to its exception * * Thread2: calls setException(), which returns false, CASes seenExceptions to its exception, * and wrongly believes that its exception is new (leading it to logging it when it shouldn't) * * Our solution is for threads to CAS seenExceptions from null to a Set populated with _the * initial exception_, no matter which thread does the work. This ensures that seenExceptions * always contains not just the current thread's exception but also the initial thread's. */ Set seenExceptionsLocal = seenExceptions; if (seenExceptionsLocal == null) { // TODO(cpovirk): Should we use a simpler (presumably cheaper) data structure? /* * Using weak references here could let us release exceptions earlier, but: * * 1. On Android, querying a WeakReference blocks if the GC is doing an otherwise-concurrent * pass. * * 2. We would probably choose to compare exceptions using == instead of equals() (for * consistency with how weak references are cleared). That's a behavior change -- arguably the * removal of a feature. * * Fortunately, exceptions rarely contain references to expensive resources. */ // seenExceptionsLocal = newConcurrentHashSet(); /* * Other handleException() callers may see this as soon as we publish it. We need to populate * it with the initial failure before we do, or else they may think that the initial failure * has never been seen before. */ addInitialException(seenExceptionsLocal); ATOMIC_HELPER.compareAndSetSeenExceptions(this, null, seenExceptionsLocal); /* * If another handleException() caller created the set, we need to use that copy in case yet * other callers have added to it. * * This read is guaranteed to get us the right value because we only set this once (here). * * requireNonNull is safe because either our compareAndSet succeeded or it failed because * another thread did it for us. */ seenExceptionsLocal = requireNonNull(seenExceptions); } return seenExceptionsLocal; } /** Populates {@code seen} with the exception that was passed to {@code setException}. */ abstract void addInitialException(Set seen); final int decrementRemainingAndGet() { return ATOMIC_HELPER.decrementAndGetRemainingCount(this); } final void clearSeenExceptions() { seenExceptions = null; } private abstract static class AtomicHelper { /** Atomic compare-and-set of the {@code AggregateFutureState#seenExceptions} field. */ abstract void compareAndSetSeenExceptions( AggregateFutureState state, @CheckForNull Set expect, Set update); /** Atomic decrement-and-get of the {@code AggregateFutureState#remaining} field. */ abstract int decrementAndGetRemainingCount(AggregateFutureState state); } private static final class SafeAtomicHelper extends AtomicHelper { final AtomicReferenceFieldUpdater, @Nullable Set> seenExceptionsUpdater; final AtomicIntegerFieldUpdater> remainingCountUpdater; @SuppressWarnings({"rawtypes", "unchecked"}) // Unavoidable with reflection API SafeAtomicHelper( AtomicReferenceFieldUpdater seenExceptionsUpdater, AtomicIntegerFieldUpdater remainingCountUpdater) { this.seenExceptionsUpdater = (AtomicReferenceFieldUpdater, @Nullable Set>) seenExceptionsUpdater; this.remainingCountUpdater = (AtomicIntegerFieldUpdater>) remainingCountUpdater; } @Override void compareAndSetSeenExceptions( AggregateFutureState state, @CheckForNull Set expect, Set update) { seenExceptionsUpdater.compareAndSet(state, expect, update); } @Override int decrementAndGetRemainingCount(AggregateFutureState state) { return remainingCountUpdater.decrementAndGet(state); } } private static final class SynchronizedAtomicHelper extends AtomicHelper { @Override void compareAndSetSeenExceptions( AggregateFutureState state, @CheckForNull Set expect, Set update) { synchronized (state) { if (state.seenExceptions == expect) { state.seenExceptions = update; } } } @Override int decrementAndGetRemainingCount(AggregateFutureState state) { synchronized (state) { return --state.remaining; } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy