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 }