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.concurrent.ScheduledExecutorService; 20 import java.util.concurrent.ScheduledFuture; 21 import java.util.concurrent.TimeUnit; 22 23 /** 24 * An absolute point in time, generally for tracking when a task should be completed. A deadline is 25 * immutable except for the passage of time causing it to expire. 26 * 27 * <p>Many systems use timeouts, which are relative to the start of the operation. However, being 28 * relative causes them to be poorly suited for managing higher-level tasks where there are many 29 * components and sub-operations that may not know the time of the initial "start of the operation." 30 * However, a timeout can be converted to a {@code Deadline} at the start of the operation and then 31 * passed to the various components unambiguously. 32 */ 33 public final class Deadline implements Comparable<Deadline> { 34 private static final SystemTicker SYSTEM_TICKER = new SystemTicker(); 35 // nanoTime has a range of just under 300 years. Only allow up to 100 years in the past or future 36 // to prevent wraparound as long as process runs for less than ~100 years. 37 private static final long MAX_OFFSET = TimeUnit.DAYS.toNanos(100 * 365); 38 private static final long MIN_OFFSET = -MAX_OFFSET; 39 40 /** 41 * Create a deadline that will expire at the specified offset from the current system clock. 42 * @param duration A non-negative duration. 43 * @param units The time unit for the duration. 44 * @return A new deadline. 45 */ after(long duration, TimeUnit units)46 public static Deadline after(long duration, TimeUnit units) { 47 return after(duration, units, SYSTEM_TICKER); 48 } 49 50 // For testing after(long duration, TimeUnit units, Ticker ticker)51 static Deadline after(long duration, TimeUnit units, Ticker ticker) { 52 checkNotNull(units, "units"); 53 return new Deadline(ticker, units.toNanos(duration), true); 54 } 55 56 private final Ticker ticker; 57 private final long deadlineNanos; 58 private volatile boolean expired; 59 Deadline(Ticker ticker, long offset, boolean baseInstantAlreadyExpired)60 private Deadline(Ticker ticker, long offset, boolean baseInstantAlreadyExpired) { 61 this(ticker, ticker.read(), offset, baseInstantAlreadyExpired); 62 } 63 Deadline(Ticker ticker, long baseInstant, long offset, boolean baseInstantAlreadyExpired)64 private Deadline(Ticker ticker, long baseInstant, long offset, 65 boolean baseInstantAlreadyExpired) { 66 this.ticker = ticker; 67 // Clamp to range [MIN_OFFSET, MAX_OFFSET] 68 offset = Math.min(MAX_OFFSET, Math.max(MIN_OFFSET, offset)); 69 deadlineNanos = baseInstant + offset; 70 expired = baseInstantAlreadyExpired && offset <= 0; 71 } 72 73 /** 74 * Has this deadline expired 75 * @return {@code true} if it has, otherwise {@code false}. 76 */ isExpired()77 public boolean isExpired() { 78 if (!expired) { 79 if (deadlineNanos - ticker.read() <= 0) { 80 expired = true; 81 } else { 82 return false; 83 } 84 } 85 return true; 86 } 87 88 /** 89 * Is {@code this} deadline before another. 90 */ isBefore(Deadline other)91 public boolean isBefore(Deadline other) { 92 return this.deadlineNanos - other.deadlineNanos < 0; 93 } 94 95 /** 96 * Return the minimum deadline of {@code this} or an other deadline. 97 * @param other deadline to compare with {@code this}. 98 */ minimum(Deadline other)99 public Deadline minimum(Deadline other) { 100 return isBefore(other) ? this : other; 101 } 102 103 /** 104 * Create a new deadline that is offset from {@code this}. 105 */ 106 // TODO(ejona): This method can cause deadlines to grow too far apart. For example: 107 // Deadline.after(100 * 365, DAYS).offset(100 * 365, DAYS) would be less than 108 // Deadline.after(-100 * 365, DAYS) offset(long offset, TimeUnit units)109 public Deadline offset(long offset, TimeUnit units) { 110 // May already be expired 111 if (offset == 0) { 112 return this; 113 } 114 return new Deadline(ticker, deadlineNanos, units.toNanos(offset), isExpired()); 115 } 116 117 /** 118 * How much time is remaining in the specified time unit. Internal units are maintained as 119 * nanoseconds and conversions are subject to the constraints documented for 120 * {@link TimeUnit#convert}. If there is no time remaining, the returned duration is how 121 * long ago the deadline expired. 122 */ timeRemaining(TimeUnit unit)123 public long timeRemaining(TimeUnit unit) { 124 final long nowNanos = ticker.read(); 125 if (!expired && deadlineNanos - nowNanos <= 0) { 126 expired = true; 127 } 128 return unit.convert(deadlineNanos - nowNanos, TimeUnit.NANOSECONDS); 129 } 130 131 /** 132 * Schedule a task to be run when the deadline expires. 133 * @param task to run on expiration 134 * @param scheduler used to execute the task 135 * @return {@link ScheduledFuture} which can be used to cancel execution of the task 136 */ runOnExpiration(Runnable task, ScheduledExecutorService scheduler)137 public ScheduledFuture<?> runOnExpiration(Runnable task, ScheduledExecutorService scheduler) { 138 checkNotNull(task, "task"); 139 checkNotNull(scheduler, "scheduler"); 140 return scheduler.schedule(task, deadlineNanos - ticker.read(), TimeUnit.NANOSECONDS); 141 } 142 143 @Override toString()144 public String toString() { 145 return timeRemaining(TimeUnit.NANOSECONDS) + " ns from now"; 146 } 147 148 @Override compareTo(Deadline that)149 public int compareTo(Deadline that) { 150 long diff = this.deadlineNanos - that.deadlineNanos; 151 if (diff < 0) { 152 return -1; 153 } else if (diff > 0) { 154 return 1; 155 } 156 return 0; 157 } 158 159 /** Time source representing nanoseconds since fixed but arbitrary point in time. */ 160 abstract static class Ticker { 161 /** Returns the number of nanoseconds since this source's epoch. */ read()162 public abstract long read(); 163 } 164 165 private static class SystemTicker extends Ticker { 166 @Override read()167 public long read() { 168 return System.nanoTime(); 169 } 170 } 171 checkNotNull(T reference, Object errorMessage)172 private static <T> T checkNotNull(T reference, Object errorMessage) { 173 if (reference == null) { 174 throw new NullPointerException(String.valueOf(errorMessage)); 175 } 176 return reference; 177 } 178 } 179