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 */
16package okio;
17
18import java.io.IOException;
19import java.io.InterruptedIOException;
20import 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 preloading content.
41 */
42public 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 == null) throw 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 == null) throw 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      throw new InterruptedIOException("thread interrupted");
146    }
147
148    if (hasDeadline && deadlineNanoTime - System.nanoTime() <= 0) {
149      throw new InterruptedIOException("deadline reached");
150    }
151  }
152}
153