1 /* 2 * Copyright 2016 The gRPC Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package io.grpc; 18 19 import java.util.Arrays; 20 import java.util.Locale; 21 import java.util.concurrent.ScheduledExecutorService; 22 import java.util.concurrent.ScheduledFuture; 23 import java.util.concurrent.TimeUnit; 24 25 /** 26 * An absolute point in time, generally for tracking when a task should be completed. A deadline is 27 * immutable except for the passage of time causing it to expire. 28 * 29 * <p>Many systems use timeouts, which are relative to the start of the operation. However, being 30 * relative causes them to be poorly suited for managing higher-level tasks where there are many 31 * components and sub-operations that may not know the time of the initial "start of the operation." 32 * However, a timeout can be converted to a {@code Deadline} at the start of the operation and then 33 * passed to the various components unambiguously. 34 */ 35 public final class Deadline implements Comparable<Deadline> { 36 private static final SystemTicker SYSTEM_TICKER = new SystemTicker(); 37 // nanoTime has a range of just under 300 years. Only allow up to 100 years in the past or future 38 // to prevent wraparound as long as process runs for less than ~100 years. 39 private static final long MAX_OFFSET = TimeUnit.DAYS.toNanos(100 * 365); 40 private static final long MIN_OFFSET = -MAX_OFFSET; 41 private static final long NANOS_PER_SECOND = TimeUnit.SECONDS.toNanos(1); 42 43 /** 44 * Returns the ticker that's based on system clock. 45 * 46 * <p>This is <strong>EXPERIMENTAL</strong> API and may subject to change. If you'd like it to be 47 * stabilized or have any feedback, please 48 * <a href="https://github.com/grpc/grpc-java/issues/6030">let us know</a>. 49 * 50 * @since 1.24.0 51 */ getSystemTicker()52 public static Ticker getSystemTicker() { 53 return SYSTEM_TICKER; 54 } 55 56 /** 57 * Create a deadline that will expire at the specified offset based on the {@link #getSystemTicker 58 * system ticker}. 59 * 60 * <p>If the given offset is extraordinarily long, say 100 years, the actual deadline created 61 * might saturate. 62 * 63 * @param duration A non-negative duration. 64 * @param units The time unit for the duration. 65 * @return A new deadline. 66 */ after(long duration, TimeUnit units)67 public static Deadline after(long duration, TimeUnit units) { 68 return after(duration, units, SYSTEM_TICKER); 69 } 70 71 /** 72 * Create a deadline that will expire at the specified offset based on the given {@link Ticker}. 73 * 74 * <p>If the given offset is extraordinarily long, say 100 years, the actual deadline created 75 * might saturate. 76 * 77 * <p><strong>CAUTION</strong>: Only deadlines created with the same {@link Ticker} instance can 78 * be compared by methods like {@link #minimum}, {@link #isBefore} and {@link #compareTo}. Custom 79 * Tickers should only be used in tests where you fake out the clock. Always use the {@link 80 * #getSystemTicker system ticker} in production, or serious errors may occur. 81 * 82 * <p>This is <strong>EXPERIMENTAL</strong> API and may subject to change. If you'd like it to be 83 * stabilized or have any feedback, please 84 * <a href="https://github.com/grpc/grpc-java/issues/6030">let us know</a>. 85 * 86 * @param duration A non-negative duration. 87 * @param units The time unit for the duration. 88 * @param ticker Where this deadline refer the current time 89 * @return A new deadline. 90 * 91 * @since 1.24.0 92 */ after(long duration, TimeUnit units, Ticker ticker)93 public static Deadline after(long duration, TimeUnit units, Ticker ticker) { 94 checkNotNull(units, "units"); 95 return new Deadline(ticker, units.toNanos(duration), true); 96 } 97 98 private final Ticker ticker; 99 private final long deadlineNanos; 100 private volatile boolean expired; 101 Deadline(Ticker ticker, long offset, boolean baseInstantAlreadyExpired)102 private Deadline(Ticker ticker, long offset, boolean baseInstantAlreadyExpired) { 103 this(ticker, ticker.nanoTime(), offset, baseInstantAlreadyExpired); 104 } 105 Deadline(Ticker ticker, long baseInstant, long offset, boolean baseInstantAlreadyExpired)106 private Deadline(Ticker ticker, long baseInstant, long offset, 107 boolean baseInstantAlreadyExpired) { 108 this.ticker = ticker; 109 // Clamp to range [MIN_OFFSET, MAX_OFFSET] 110 offset = Math.min(MAX_OFFSET, Math.max(MIN_OFFSET, offset)); 111 deadlineNanos = baseInstant + offset; 112 expired = baseInstantAlreadyExpired && offset <= 0; 113 } 114 115 /** 116 * Returns whether this has deadline expired. 117 * 118 * @return {@code true} if it has, otherwise {@code false}. 119 */ isExpired()120 public boolean isExpired() { 121 if (!expired) { 122 if (deadlineNanos - ticker.nanoTime() <= 0) { 123 expired = true; 124 } else { 125 return false; 126 } 127 } 128 return true; 129 } 130 131 /** 132 * Is {@code this} deadline before another. Two deadlines must be created using the same {@link 133 * Ticker}. 134 */ isBefore(Deadline other)135 public boolean isBefore(Deadline other) { 136 checkTicker(other); 137 return this.deadlineNanos - other.deadlineNanos < 0; 138 } 139 140 /** 141 * Return the minimum deadline of {@code this} or an other deadline. They must be created using 142 * the same {@link Ticker}. 143 * 144 * @param other deadline to compare with {@code this}. 145 */ minimum(Deadline other)146 public Deadline minimum(Deadline other) { 147 checkTicker(other); 148 return isBefore(other) ? this : other; 149 } 150 151 /** 152 * Create a new deadline that is offset from {@code this}. 153 * 154 * <p>If the given offset is extraordinarily long, say 100 years, the actual deadline created 155 * might saturate. 156 */ 157 // TODO(ejona): This method can cause deadlines to grow too far apart. For example: 158 // Deadline.after(100 * 365, DAYS).offset(100 * 365, DAYS) would be less than 159 // Deadline.after(-100 * 365, DAYS) offset(long offset, TimeUnit units)160 public Deadline offset(long offset, TimeUnit units) { 161 // May already be expired 162 if (offset == 0) { 163 return this; 164 } 165 return new Deadline(ticker, deadlineNanos, units.toNanos(offset), isExpired()); 166 } 167 168 /** 169 * How much time is remaining in the specified time unit. Internal units are maintained as 170 * nanoseconds and conversions are subject to the constraints documented for 171 * {@link TimeUnit#convert}. If there is no time remaining, the returned duration is how 172 * long ago the deadline expired. 173 */ timeRemaining(TimeUnit unit)174 public long timeRemaining(TimeUnit unit) { 175 final long nowNanos = ticker.nanoTime(); 176 if (!expired && deadlineNanos - nowNanos <= 0) { 177 expired = true; 178 } 179 return unit.convert(deadlineNanos - nowNanos, TimeUnit.NANOSECONDS); 180 } 181 182 /** 183 * Schedule a task to be run when the deadline expires. 184 * 185 * <p>Note if this deadline was created with a custom {@link Ticker}, the {@code scheduler}'s 186 * underlying clock should be synchronized with that Ticker. Otherwise the task won't be run at 187 * the expected point of time. 188 * 189 * @param task to run on expiration 190 * @param scheduler used to execute the task 191 * @return {@link ScheduledFuture} which can be used to cancel execution of the task 192 */ runOnExpiration(Runnable task, ScheduledExecutorService scheduler)193 public ScheduledFuture<?> runOnExpiration(Runnable task, ScheduledExecutorService scheduler) { 194 checkNotNull(task, "task"); 195 checkNotNull(scheduler, "scheduler"); 196 return scheduler.schedule(task, deadlineNanos - ticker.nanoTime(), TimeUnit.NANOSECONDS); 197 } 198 199 @Override toString()200 public String toString() { 201 long remainingNanos = timeRemaining(TimeUnit.NANOSECONDS); 202 long seconds = Math.abs(remainingNanos) / NANOS_PER_SECOND; 203 long nanos = Math.abs(remainingNanos) % NANOS_PER_SECOND; 204 205 StringBuilder buf = new StringBuilder(); 206 if (remainingNanos < 0) { 207 buf.append('-'); 208 } 209 buf.append(seconds); 210 if (nanos > 0) { 211 buf.append(String.format(Locale.US, ".%09d", nanos)); 212 } 213 buf.append("s from now"); 214 if (ticker != SYSTEM_TICKER) { 215 buf.append(" (ticker=" + ticker + ")"); 216 } 217 return buf.toString(); 218 } 219 220 /** 221 * {@inheritDoc} 222 * 223 * <p>Both deadlines must be created with the same {@link Ticker}. 224 */ 225 @Override compareTo(Deadline that)226 public int compareTo(Deadline that) { 227 checkTicker(that); 228 long diff = this.deadlineNanos - that.deadlineNanos; 229 if (diff < 0) { 230 return -1; 231 } else if (diff > 0) { 232 return 1; 233 } 234 return 0; 235 } 236 237 @Override hashCode()238 public int hashCode() { 239 return Arrays.asList(this.ticker, this.deadlineNanos).hashCode(); 240 } 241 242 @Override equals(final Object o)243 public boolean equals(final Object o) { 244 if (o == this) { 245 return true; 246 } 247 if (!(o instanceof Deadline)) { 248 return false; 249 } 250 251 final Deadline other = (Deadline) o; 252 if (this.ticker == null ? other.ticker != null : this.ticker != other.ticker) { 253 return false; 254 } 255 if (this.deadlineNanos != other.deadlineNanos) { 256 return false; 257 } 258 return true; 259 } 260 261 /** 262 * Time source representing nanoseconds since fixed but arbitrary point in time. 263 * 264 * <p>DO NOT use custom {@link Ticker} implementations in production, because deadlines created 265 * with custom tickers are incompatible with those created with the system ticker. Always use 266 * the {@link #getSystemTicker system ticker} whenever you need to provide one in production code. 267 * 268 * <p>This is <strong>EXPERIMENTAL</strong> API and may subject to change. If you'd like it to be 269 * stabilized or have any feedback, please 270 * <a href="https://github.com/grpc/grpc-java/issues/6030">let us know</a>. 271 * 272 * <p>In general implementations should be thread-safe, unless it's implemented and used in a 273 * localized environment (like unit tests) where you are sure the usages are synchronized. 274 * 275 * @since 1.24.0 276 */ 277 public abstract static class Ticker { 278 /** Returns the number of nanoseconds since this source's epoch. */ nanoTime()279 public abstract long nanoTime(); 280 } 281 282 private static class SystemTicker extends Ticker { 283 @Override nanoTime()284 public long nanoTime() { 285 return System.nanoTime(); 286 } 287 } 288 checkNotNull(T reference, Object errorMessage)289 private static <T> T checkNotNull(T reference, Object errorMessage) { 290 if (reference == null) { 291 throw new NullPointerException(String.valueOf(errorMessage)); 292 } 293 return reference; 294 } 295 checkTicker(Deadline other)296 private void checkTicker(Deadline other) { 297 if (ticker != other.ticker) { 298 throw new AssertionError( 299 "Tickers (" + ticker + " and " + other.ticker + ") don't match." 300 + " Custom Ticker should only be used in tests!"); 301 } 302 } 303 } 304