• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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