1 /*
2  * Copyright (C) 2014 Square, Inc.
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 package okio;
17
18 import java.io.IOException;
19 import java.io.InterruptedIOException;
20 import java.util.concurrent.TimeUnit;
21
22 /**
23  * A policy on how much time to spend on a task before giving up. When a task
24  * times out, it is left in an unspecified state and should be abandoned. For
25  * example, if reading from a source times out, that source should be closed and
26  * the read should be retried later. If writing to a sink times out, the same
27  * rules apply: close the sink and retry later.
28  *
29  * <h3>Timeouts and Deadlines</h3>
30  * This class offers two complementary controls to define a timeout policy.
31  *
32  * <p><strong>Timeouts</strong> specify the maximum time to wait for a single
33  * operation to complete. Timeouts are typically used to detect problems like
34  * network partitions. For example, if a remote peer doesn't return <i>any</i>
35  * data for ten seconds, we may assume that the peer is unavailable.
36  *
37  * <p><strong>Deadlines</strong> specify the maximum time to spend on a job,
38  * composed of one or more operations. Use deadlines to set an upper bound on
39  * the time invested on a job. For example, a battery-conscious app may limit
40  * how much time it spends pre-loading content.
41  */

42 public class Timeout {
43   /**
44    * An empty timeout that neither tracks nor detects timeouts. Use this when
45    * timeouts aren't necessary, such as in implementations whose operations
46    * do not block.
47    */

48   public static final Timeout NONE = new Timeout() {
49     @Override public Timeout timeout(long timeout, TimeUnit unit) {
50       return this;
51     }
52
53     @Override public Timeout deadlineNanoTime(long deadlineNanoTime) {
54       return this;
55     }
56
57     @Override public void throwIfReached() throws IOException {
58     }
59   };
60
61   /**
62    * True if {@code deadlineNanoTime} is defined. There is no equivalent to null
63    * or 0 for {@link System#nanoTime}.
64    */

65   private boolean hasDeadline;
66   private long deadlineNanoTime;
67   private long timeoutNanos;
68
69   public Timeout() {
70   }
71
72   /**
73    * Wait at most {@code timeout} time before aborting an operation. Using a
74    * per-operation timeout means that as long as forward progress is being made,
75    * no sequence of operations will fail.
76    *
77    * <p>If {@code timeout == 0}, operations will run indefinitely. (Operating
78    * system timeouts may still apply.)
79    */

80   public Timeout timeout(long timeout, TimeUnit unit) {
81     if (timeout < 0) throw new IllegalArgumentException("timeout < 0: " + timeout);
82     if (unit == nullthrow new IllegalArgumentException("unit == null");
83     this.timeoutNanos = unit.toNanos(timeout);
84     return this;
85   }
86
87   /** Returns the timeout in nanoseconds, or {@code 0} for no timeout. */
88   public long timeoutNanos() {
89     return timeoutNanos;
90   }
91
92   /** Returns true if a deadline is enabled. */
93   public boolean hasDeadline() {
94     return hasDeadline;
95   }
96
97   /**
98    * Returns the {@linkplain System#nanoTime() nano time} when the deadline will
99    * be reached.
100    *
101    * @throws IllegalStateException if no deadline is set.
102    */

103   public long deadlineNanoTime() {
104     if (!hasDeadline) throw new IllegalStateException("No deadline");
105     return deadlineNanoTime;
106   }
107
108   /**
109    * Sets the {@linkplain System#nanoTime() nano time} when the deadline will be
110    * reached. All operations must complete before this time. Use a deadline to
111    * set a maximum bound on the time spent on a sequence of operations.
112    */

113   public Timeout deadlineNanoTime(long deadlineNanoTime) {
114     this.hasDeadline = true;
115     this.deadlineNanoTime = deadlineNanoTime;
116     return this;
117   }
118
119   /** Set a deadline of now plus {@code duration} time. */
120   public final Timeout deadline(long duration, TimeUnit unit) {
121     if (duration <= 0) throw new IllegalArgumentException("duration <= 0: " + duration);
122     if (unit == nullthrow new IllegalArgumentException("unit == null");
123     return deadlineNanoTime(System.nanoTime() + unit.toNanos(duration));
124   }
125
126   /** Clears the timeout. Operating system timeouts may still apply. */
127   public Timeout clearTimeout() {
128     this.timeoutNanos = 0;
129     return this;
130   }
131
132   /** Clears the deadline. */
133   public Timeout clearDeadline() {
134     this.hasDeadline = false;
135     return this;
136   }
137
138   /**
139    * Throws an {@link InterruptedIOException} if the deadline has been reached or if the current
140    * thread has been interrupted. This method doesn't detect timeouts; that should be implemented to
141    * asynchronously abort an in-progress operation.
142    */

143   public void throwIfReached() throws IOException {
144     if (Thread.interrupted()) {
145       Thread.currentThread().interrupt(); // Retain interrupted status.
146       throw new InterruptedIOException("interrupted");
147     }
148
149     if (hasDeadline && deadlineNanoTime - System.nanoTime() <= 0) {
150       throw new InterruptedIOException("deadline reached");
151     }
152   }
153
154   /**
155    * Waits on {@code monitor} until it is notified. Throws {@link InterruptedIOException} if either
156    * the thread is interrupted or if this timeout elapses before {@code monitor} is notified. The
157    * caller must be synchronized on {@code monitor}.
158    *
159    * <p>Here's a sample class that uses {@code waitUntilNotified()} to await a specific state. Note
160    * that the call is made within a loop to avoid unnecessary waiting and to mitigate spurious
161    * notifications. <pre>{@code
162    *
163    *   class Dice {
164    *     Random random = new Random();
165    *     int latestTotal;
166    *
167    *     public synchronized void roll() {
168    *       latestTotal = 2 + random.nextInt(6) + random.nextInt(6);
169    *       System.out.println("Rolled " + latestTotal);
170    *       notifyAll();
171    *     }
172    *
173    *     public void rollAtFixedRate(int period, TimeUnit timeUnit) {
174    *       Executors.newScheduledThreadPool(0).scheduleAtFixedRate(new Runnable() {
175    *         public void run() {
176    *           roll();
177    *          }
178    *       }, 0, period, timeUnit);
179    *     }
180    *
181    *     public synchronized void awaitTotal(Timeout timeout, int total)
182    *         throws InterruptedIOException {
183    *       while (latestTotal != total) {
184    *         timeout.waitUntilNotified(this);
185    *       }
186    *     }
187    *   }
188    * }</pre>
189    */

190   public final void waitUntilNotified(Object monitor) throws InterruptedIOException {
191     try {
192       boolean hasDeadline = hasDeadline();
193       long timeoutNanos = timeoutNanos();
194
195       if (!hasDeadline && timeoutNanos == 0L) {
196         monitor.wait(); // There is no timeout: wait forever.
197         return;
198       }
199
200       // Compute how long we'll wait.
201       long waitNanos;
202       long start = System.nanoTime();
203       if (hasDeadline && timeoutNanos != 0) {
204         long deadlineNanos = deadlineNanoTime() - start;
205         waitNanos = Math.min(timeoutNanos, deadlineNanos);
206       } else if (hasDeadline) {
207         waitNanos = deadlineNanoTime() - start;
208       } else {
209         waitNanos = timeoutNanos;
210       }
211
212       // Attempt to wait that long. This will break out early if the monitor is notified.
213       long elapsedNanos = 0L;
214       if (waitNanos > 0L) {
215         long waitMillis = waitNanos / 1000000L;
216         monitor.wait(waitMillis, (int) (waitNanos - waitMillis * 1000000L));
217         elapsedNanos = System.nanoTime() - start;
218       }
219
220       // Throw if the timeout elapsed before the monitor was notified.
221       if (elapsedNanos >= waitNanos) {
222         throw new InterruptedIOException("timeout");
223       }
224     } catch (InterruptedException e) {
225       Thread.currentThread().interrupt(); // Retain interrupted status.
226       throw new InterruptedIOException("interrupted");
227     }
228   }
229
230   static long minTimeout(long aNanos, long bNanos) {
231     if (aNanos == 0L) return bNanos;
232     if (bNanos == 0L) return aNanos;
233     if (aNanos < bNanos) return aNanos;
234     return bNanos;
235   }
236 }
237