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

Class Method, % Line, %
TimeoutFuture 0% (0/5) 0% (0/24)
TimeoutFuture$Fire 0% (0/2) 0% (0/21)
TimeoutFuture$TimeoutFutureException 0% (0/3) 0% (0/4)
Total 0% (0/10) 0% (0/49)


1 /* 2  * Copyright (C) 2006 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.util.concurrent.MoreExecutors.directExecutor; 18  19 import com.google.common.annotations.GwtIncompatible; 20 import com.google.common.base.Preconditions; 21 import java.util.concurrent.ExecutionException; 22 import java.util.concurrent.Future; 23 import java.util.concurrent.ScheduledExecutorService; 24 import java.util.concurrent.ScheduledFuture; 25 import java.util.concurrent.TimeUnit; 26 import java.util.concurrent.TimeoutException; 27 import javax.annotation.CheckForNull; 28 import org.checkerframework.checker.nullness.qual.Nullable; 29  30 /** 31  * Implementation of {@code Futures#withTimeout}. 32  * 33  * <p>Future that delegates to another but will finish early (via a {@link TimeoutException} wrapped 34  * in an {@link ExecutionException}) if the specified duration expires. The delegate future is 35  * interrupted and cancelled if it times out. 36  */ 37 @GwtIncompatible 38 @ElementTypesAreNonnullByDefault 39 final class TimeoutFuture<V extends @Nullable Object> extends FluentFuture.TrustedFuture<V> { 40  static <V extends @Nullable Object> ListenableFuture<V> create( 41  ListenableFuture<V> delegate, 42  long time, 43  TimeUnit unit, 44  ScheduledExecutorService scheduledExecutor) { 45  TimeoutFuture<V> result = new TimeoutFuture<>(delegate); 46  Fire<V> fire = new Fire<>(result); 47  result.timer = scheduledExecutor.schedule(fire, time, unit); 48  delegate.addListener(fire, directExecutor()); 49  return result; 50  } 51  52  /* 53  * Memory visibility of these fields. There are two cases to consider. 54  * 55  * 1. visibility of the writes to these fields to Fire.run: 56  * 57  * The initial write to delegateRef is made definitely visible via the semantics of 58  * addListener/SES.schedule. The later racy write in cancel() is not guaranteed to be observed, 59  * however that is fine since the correctness is based on the atomic state in our base class. The 60  * initial write to timer is never definitely visible to Fire.run since it is assigned after 61  * SES.schedule is called. Therefore Fire.run has to check for null. However, it should be visible 62  * if Fire.run is called by delegate.addListener since addListener is called after the assignment 63  * to timer, and importantly this is the main situation in which we need to be able to see the 64  * write. 65  * 66  * 2. visibility of the writes to an afterDone() call triggered by cancel(): 67  * 68  * Since these fields are non-final that means that TimeoutFuture is not being 'safely published', 69  * thus a motivated caller may be able to expose the reference to another thread that would then 70  * call cancel() and be unable to cancel the delegate. 71  * There are a number of ways to solve this, none of which are very pretty, and it is currently 72  * believed to be a purely theoretical problem (since the other actions should supply sufficient 73  * write-barriers). 74  */ 75  76  @CheckForNull private ListenableFuture<V> delegateRef; 77  @CheckForNull private ScheduledFuture<?> timer; 78  79  private TimeoutFuture(ListenableFuture<V> delegate) { 80  this.delegateRef = Preconditions.checkNotNull(delegate); 81  } 82  83  /** A runnable that is called when the delegate or the timer completes. */ 84  private static final class Fire<V extends @Nullable Object> implements Runnable { 85  @CheckForNull TimeoutFuture<V> timeoutFutureRef; 86  87  Fire(TimeoutFuture<V> timeoutFuture) { 88  this.timeoutFutureRef = timeoutFuture; 89  } 90  91  @Override 92  public void run() { 93  // If either of these reads return null then we must be after a successful cancel or another 94  // call to this method. 95  TimeoutFuture<V> timeoutFuture = timeoutFutureRef; 96  if (timeoutFuture == null) { 97  return; 98  } 99  ListenableFuture<V> delegate = timeoutFuture.delegateRef; 100  if (delegate == null) { 101  return; 102  } 103  104  /* 105  * If we're about to complete the TimeoutFuture, we want to release our reference to it. 106  * Otherwise, we'll pin it (and its result) in memory until the timeout task is GCed. (The 107  * need to clear our reference to the TimeoutFuture is the reason we use a *static* nested 108  * class with a manual reference back to the "containing" class.) 109  * 110  * This has the nice-ish side effect of limiting reentrancy: run() calls 111  * timeoutFuture.setException() calls run(). That reentrancy would already be harmless, since 112  * timeoutFuture can be set (and delegate cancelled) only once. (And "set only once" is 113  * important for other reasons: run() can still be invoked concurrently in different threads, 114  * even with the above null checks.) 115  */ 116  timeoutFutureRef = null; 117  if (delegate.isDone()) { 118  timeoutFuture.setFuture(delegate); 119  } else { 120  try { 121  ScheduledFuture<?> timer = timeoutFuture.timer; 122  timeoutFuture.timer = null; // Don't include already elapsed delay in delegate.toString() 123  String message = "Timed out"; 124  // This try-finally block ensures that we complete the timeout future, even if attempting 125  // to produce the message throws (probably StackOverflowError from delegate.toString()) 126  try { 127  if (timer != null) { 128  long overDelayMs = Math.abs(timer.getDelay(TimeUnit.MILLISECONDS)); 129  if (overDelayMs > 10) { // Not all timing drift is worth reporting 130  message += " (timeout delayed by " + overDelayMs + " ms after scheduled time)"; 131  } 132  } 133  message += ": " + delegate; 134  } finally { 135  timeoutFuture.setException(new TimeoutFutureException(message)); 136  } 137  } finally { 138  delegate.cancel(true); 139  } 140  } 141  } 142  } 143  144  private static final class TimeoutFutureException extends TimeoutException { 145  private TimeoutFutureException(String message) { 146  super(message); 147  } 148  149  @Override 150  public synchronized Throwable fillInStackTrace() { 151  setStackTrace(new StackTraceElement[0]); 152  return this; // no stack trace, wouldn't be useful anyway 153  } 154  } 155  156  @Override 157  @CheckForNull 158  protected String pendingToString() { 159  ListenableFuture<? extends V> localInputFuture = delegateRef; 160  ScheduledFuture<?> localTimer = timer; 161  if (localInputFuture != null) { 162  String message = "inputFuture=[" + localInputFuture + "]"; 163  if (localTimer != null) { 164  final long delay = localTimer.getDelay(TimeUnit.MILLISECONDS); 165  // Negative delays look confusing in an error message 166  if (delay > 0) { 167  message += ", remaining delay=[" + delay + " ms]"; 168  } 169  } 170  return message; 171  } 172  return null; 173  } 174  175  @Override 176  protected void afterDone() { 177  maybePropagateCancellationTo(delegateRef); 178  179  Future<?> localTimer = timer; 180  // Try to cancel the timer as an optimization. 181  // timer may be null if this call to run was by the timer task since there is no happens-before 182  // edge between the assignment to timer and an execution of the timer task. 183  if (localTimer != null) { 184  localTimer.cancel(false); 185  } 186  187  delegateRef = null; 188  timer = null; 189  } 190 }