AggregateFutureState.java
/*
* Copyright 2019 The Android Open Source Project
*
* 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 androidx.camera.core.impl.utils.futures;
import static java.util.concurrent.atomic.AtomicIntegerFieldUpdater.newUpdater;
import static java.util.concurrent.atomic.AtomicReferenceFieldUpdater.newUpdater;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A helper which does some thread-safe operations for aggregate futures, which must be implemented
* differently in GWT. Namely:
*
* <ul>
* <li>Lazily initializes a set of seen exceptions
* <li>Decrements a counter atomically
* </ul>
*
* <p>Copied and adapted from Guava.
*/
@SuppressWarnings("unchecked") // Casts verified
abstract class AggregateFutureState {
// Lazily initialized the first time we see an exception; not released until all the input
// futures & this future completes. Released when the future releases the reference to the
// running state
@SuppressWarnings("WeakerAccess") // Avoiding synthetic accessor.
volatile Set<Throwable> mSeenExceptions = null;
@SuppressWarnings("WeakerAccess") // Avoiding synthetic accessor.
volatile int mRemaining;
private static final AtomicHelper ATOMIC_HELPER;
private static final Logger sLogger = Logger.getLogger(AggregateFutureState.class.getName());
static {
AtomicHelper helper;
Throwable thrownReflectionFailure = null;
try {
helper =
new SafeAtomicHelper(
newUpdater(AggregateFutureState.class, (Class) Set.class,
"mSeenExceptions"),
newUpdater(AggregateFutureState.class, "mRemaining"));
} catch (Throwable reflectionFailure) {
// 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) {
sLogger.log(Level.SEVERE, "SafeAtomicHelper is broken!", thrownReflectionFailure);
}
}
AggregateFutureState(int remainingFutures) {
this.mRemaining = remainingFutures;
}
final Set<Throwable> getOrInitSeenExceptions() {
/*
* The initialization of mSeenExceptions 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
* mSeenExceptions to its exception
*
* Thread2: calls setException(), which returns false, CASes mSeenExceptions 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 mSeenExceptions from null to a Set population with
* the initial exception, no matter which thread does the work. This ensures that
* mSeenExceptions always contains not just the current thread's exception but also the
* initial thread's.
*/
Set<Throwable> seenExceptionsLocal = mSeenExceptions;
if (seenExceptionsLocal == null) {
ConcurrentHashMap<Throwable, Boolean> backingMap = new ConcurrentHashMap<>();
seenExceptionsLocal = Collections.newSetFromMap(backingMap);
/*
* 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).
*/
seenExceptionsLocal = mSeenExceptions;
}
return seenExceptionsLocal;
}
/** Populates {@code seen} with the exception that was passed to {@code setException}. */
abstract void addInitialException(Set<Throwable> seen);
final int decrementRemainingAndGet() {
return ATOMIC_HELPER.decrementAndGetRemainingCount(this);
}
private abstract static class AtomicHelper {
/** Atomic compare-and-set of the {@link AggregateFutureState#mSeenExceptions} field. */
abstract void compareAndSetSeenExceptions(
AggregateFutureState state, Set<Throwable> expect, Set<Throwable> update);
/** Atomic decrement-and-get of the {@link AggregateFutureState#mRemaining} field. */
abstract int decrementAndGetRemainingCount(AggregateFutureState state);
}
private static final class SafeAtomicHelper extends AtomicHelper {
final AtomicReferenceFieldUpdater<AggregateFutureState, Set<Throwable>>
mSeenExceptionsUpdater;
final AtomicIntegerFieldUpdater<AggregateFutureState> mRemainingCountUpdater;
SafeAtomicHelper(
AtomicReferenceFieldUpdater<AggregateFutureState, Set<Throwable>> exceptionsUpdater,
AtomicIntegerFieldUpdater<AggregateFutureState> remainingCountUpdater) {
this.mSeenExceptionsUpdater = exceptionsUpdater;
this.mRemainingCountUpdater = remainingCountUpdater;
}
@Override
void compareAndSetSeenExceptions(
AggregateFutureState state, Set<Throwable> expect, Set<Throwable> update) {
mSeenExceptionsUpdater.compareAndSet(state, expect, update);
}
@Override
int decrementAndGetRemainingCount(AggregateFutureState state) {
return mRemainingCountUpdater.decrementAndGet(state);
}
}
static final class SynchronizedAtomicHelper extends AtomicHelper {
@Override
void compareAndSetSeenExceptions(
AggregateFutureState state, Set<Throwable> expect, Set<Throwable> update) {
synchronized (state) {
if (state.mSeenExceptions == expect) {
state.mSeenExceptions = update;
}
}
}
@Override
int decrementAndGetRemainingCount(AggregateFutureState state) {
synchronized (state) {
state.mRemaining--;
return state.mRemaining;
}
}
}
}