Coverage Summary for Class: AggregateFutureState (com.google.common.util.concurrent)

Class Method, % Line, %
AggregateFutureState 0% (0/6) 0% (0/25)
AggregateFutureState$AtomicHelper 0% (0/1) 0% (0/1)
AggregateFutureState$SafeAtomicHelper 0% (0/3) 0% (0/5)
AggregateFutureState$SynchronizedAtomicHelper 0% (0/3) 0% (0/8)
Total 0% (0/13) 0% (0/39)


1 /* 2  * Copyright (C) 2015 The Guava Authors 3  * 4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5  * in compliance with the License. You may obtain a copy of the License at 6  * 7  * http://www.apache.org/licenses/LICENSE-2.0 8  * 9  * Unless required by applicable law or agreed to in writing, software distributed under the License 10  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11  * or implied. See the License for the specific language governing permissions and limitations under 12  * the License. 13  */ 14  15 package com.google.common.util.concurrent; 16  17 import static com.google.common.collect.Sets.newConcurrentHashSet; 18 import static java.util.Objects.requireNonNull; 19 import static java.util.concurrent.atomic.AtomicIntegerFieldUpdater.newUpdater; 20 import static java.util.concurrent.atomic.AtomicReferenceFieldUpdater.newUpdater; 21  22 import com.google.common.annotations.GwtCompatible; 23 import com.google.j2objc.annotations.ReflectionSupport; 24 import java.util.Set; 25 import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; 26 import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; 27 import java.util.logging.Level; 28 import java.util.logging.Logger; 29 import javax.annotation.CheckForNull; 30 import org.checkerframework.checker.nullness.qual.Nullable; 31  32 /** 33  * A helper which does some thread-safe operations for aggregate futures, which must be implemented 34  * differently in GWT. Namely: 35  * 36  * <ul> 37  * <li>Lazily initializes a set of seen exceptions 38  * <li>Decrements a counter atomically 39  * </ul> 40  */ 41 @GwtCompatible(emulated = true) 42 @ReflectionSupport(value = ReflectionSupport.Level.FULL) 43 @ElementTypesAreNonnullByDefault 44 abstract class AggregateFutureState<OutputT extends @Nullable Object> 45  extends AbstractFuture.TrustedFuture<OutputT> { 46  // Lazily initialized the first time we see an exception; not released until all the input futures 47  // have completed and we have processed them all. 48  @CheckForNull private volatile Set<Throwable> seenExceptions = null; 49  50  private volatile int remaining; 51  52  private static final AtomicHelper ATOMIC_HELPER; 53  54  private static final Logger log = Logger.getLogger(AggregateFutureState.class.getName()); 55  56  static { 57  AtomicHelper helper; 58  Throwable thrownReflectionFailure = null; 59  try { 60  helper = 61  new SafeAtomicHelper( 62  newUpdater(AggregateFutureState.class, Set.class, "seenExceptions"), 63  newUpdater(AggregateFutureState.class, "remaining")); 64  } catch (Throwable reflectionFailure) { 65  // Some Android 5.0.x Samsung devices have bugs in JDK reflection APIs that cause 66  // getDeclaredField to throw a NoSuchFieldException when the field is definitely there. 67  // For these users fallback to a suboptimal implementation, based on synchronized. This will 68  // be a definite performance hit to those users. 69  thrownReflectionFailure = reflectionFailure; 70  helper = new SynchronizedAtomicHelper(); 71  } 72  ATOMIC_HELPER = helper; 73  // Log after all static init is finished; if an installed logger uses any Futures methods, it 74  // shouldn't break in cases where reflection is missing/broken. 75  if (thrownReflectionFailure != null) { 76  log.log(Level.SEVERE, "SafeAtomicHelper is broken!", thrownReflectionFailure); 77  } 78  } 79  80  AggregateFutureState(int remainingFutures) { 81  this.remaining = remainingFutures; 82  } 83  84  final Set<Throwable> getOrInitSeenExceptions() { 85  /* 86  * The initialization of seenExceptions has to be more complicated than we'd like. The simple 87  * approach would be for each caller CAS it from null to a Set populated with its exception. But 88  * there's another race: If the first thread fails with an exception and a second thread 89  * immediately fails with the same exception: 90  * 91  * Thread1: calls setException(), which returns true, context switch before it can CAS 92  * seenExceptions to its exception 93  * 94  * Thread2: calls setException(), which returns false, CASes seenExceptions to its exception, 95  * and wrongly believes that its exception is new (leading it to logging it when it shouldn't) 96  * 97  * Our solution is for threads to CAS seenExceptions from null to a Set populated with _the 98  * initial exception_, no matter which thread does the work. This ensures that seenExceptions 99  * always contains not just the current thread's exception but also the initial thread's. 100  */ 101  Set<Throwable> seenExceptionsLocal = seenExceptions; 102  if (seenExceptionsLocal == null) { 103  // TODO(cpovirk): Should we use a simpler (presumably cheaper) data structure? 104  /* 105  * Using weak references here could let us release exceptions earlier, but: 106  * 107  * 1. On Android, querying a WeakReference blocks if the GC is doing an otherwise-concurrent 108  * pass. 109  * 110  * 2. We would probably choose to compare exceptions using == instead of equals() (for 111  * consistency with how weak references are cleared). That's a behavior change -- arguably the 112  * removal of a feature. 113  * 114  * Fortunately, exceptions rarely contain references to expensive resources. 115  */ 116  117  // 118  seenExceptionsLocal = newConcurrentHashSet(); 119  /* 120  * Other handleException() callers may see this as soon as we publish it. We need to populate 121  * it with the initial failure before we do, or else they may think that the initial failure 122  * has never been seen before. 123  */ 124  addInitialException(seenExceptionsLocal); 125  126  ATOMIC_HELPER.compareAndSetSeenExceptions(this, null, seenExceptionsLocal); 127  /* 128  * If another handleException() caller created the set, we need to use that copy in case yet 129  * other callers have added to it. 130  * 131  * This read is guaranteed to get us the right value because we only set this once (here). 132  * 133  * requireNonNull is safe because either our compareAndSet succeeded or it failed because 134  * another thread did it for us. 135  */ 136  seenExceptionsLocal = requireNonNull(seenExceptions); 137  } 138  return seenExceptionsLocal; 139  } 140  141  /** Populates {@code seen} with the exception that was passed to {@code setException}. */ 142  abstract void addInitialException(Set<Throwable> seen); 143  144  final int decrementRemainingAndGet() { 145  return ATOMIC_HELPER.decrementAndGetRemainingCount(this); 146  } 147  148  final void clearSeenExceptions() { 149  seenExceptions = null; 150  } 151  152  private abstract static class AtomicHelper { 153  /** Atomic compare-and-set of the {@link AggregateFutureState#seenExceptions} field. */ 154  abstract void compareAndSetSeenExceptions( 155  AggregateFutureState<?> state, @CheckForNull Set<Throwable> expect, Set<Throwable> update); 156  157  /** Atomic decrement-and-get of the {@link AggregateFutureState#remaining} field. */ 158  abstract int decrementAndGetRemainingCount(AggregateFutureState<?> state); 159  } 160  161  private static final class SafeAtomicHelper extends AtomicHelper { 162  final AtomicReferenceFieldUpdater<AggregateFutureState<?>, Set<Throwable>> 163  seenExceptionsUpdater; 164  165  final AtomicIntegerFieldUpdater<AggregateFutureState<?>> remainingCountUpdater; 166  167  @SuppressWarnings({"rawtypes", "unchecked"}) // Unavoidable with reflection API 168  SafeAtomicHelper( 169  AtomicReferenceFieldUpdater seenExceptionsUpdater, 170  AtomicIntegerFieldUpdater remainingCountUpdater) { 171  this.seenExceptionsUpdater = 172  (AtomicReferenceFieldUpdater<AggregateFutureState<?>, Set<Throwable>>) 173  seenExceptionsUpdater; 174  this.remainingCountUpdater = 175  (AtomicIntegerFieldUpdater<AggregateFutureState<?>>) remainingCountUpdater; 176  } 177  178  @Override 179  void compareAndSetSeenExceptions( 180  AggregateFutureState<?> state, @CheckForNull Set<Throwable> expect, Set<Throwable> update) { 181  seenExceptionsUpdater.compareAndSet(state, expect, update); 182  } 183  184  @Override 185  int decrementAndGetRemainingCount(AggregateFutureState<?> state) { 186  return remainingCountUpdater.decrementAndGet(state); 187  } 188  } 189  190  private static final class SynchronizedAtomicHelper extends AtomicHelper { 191  @Override 192  void compareAndSetSeenExceptions( 193  AggregateFutureState<?> state, @CheckForNull Set<Throwable> expect, Set<Throwable> update) { 194  synchronized (state) { 195  if (state.seenExceptions == expect) { 196  state.seenExceptions = update; 197  } 198  } 199  } 200  201  @Override 202  int decrementAndGetRemainingCount(AggregateFutureState<?> state) { 203  synchronized (state) { 204  return --state.remaining; 205  } 206  } 207  } 208 }