335 lines
11 KiB
Java
335 lines
11 KiB
Java
/* GENERATED SOURCE. DO NOT MODIFY. */
|
|
/*
|
|
* Copyright (C) 2014 Square, Inc.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
package com.android.okhttp.okio;
|
|
|
|
import java.io.IOException;
|
|
import java.io.InterruptedIOException;
|
|
|
|
/**
|
|
* This timeout uses a background thread to take action exactly when the timeout
|
|
* occurs. Use this to implement timeouts where they aren't supported natively,
|
|
* such as to sockets that are blocked on writing.
|
|
*
|
|
* <p>Subclasses should override {@link #timedOut} to take action when a timeout
|
|
* occurs. This method will be invoked by the shared watchdog thread so it
|
|
* should not do any long-running operations. Otherwise we risk starving other
|
|
* timeouts from being triggered.
|
|
*
|
|
* <p>Use {@link #sink} and {@link #source} to apply this timeout to a stream.
|
|
* The returned value will apply the timeout to each operation on the wrapped
|
|
* stream.
|
|
*
|
|
* <p>Callers should call {@link #enter} before doing work that is subject to
|
|
* timeouts, and {@link #exit} afterwards. The return value of {@link #exit}
|
|
* indicates whether a timeout was triggered. Note that the call to {@link
|
|
* #timedOut} is asynchronous, and may be called after {@link #exit}.
|
|
* @hide This class is not part of the Android public SDK API
|
|
*/
|
|
public class AsyncTimeout extends Timeout {
|
|
/**
|
|
* The watchdog thread processes a linked list of pending timeouts, sorted in
|
|
* the order to be triggered. This class synchronizes on AsyncTimeout.class.
|
|
* This lock guards the queue.
|
|
*
|
|
* <p>Head's 'next' points to the first element of the linked list. The first
|
|
* element is the next node to time out, or null if the queue is empty. The
|
|
* head is null until the watchdog thread is started.
|
|
*/
|
|
private static AsyncTimeout head;
|
|
|
|
/** True if this node is currently in the queue. */
|
|
private boolean inQueue;
|
|
|
|
/** The next node in the linked list. */
|
|
private AsyncTimeout next;
|
|
|
|
/** If scheduled, this is the time that the watchdog should time this out. */
|
|
private long timeoutAt;
|
|
|
|
public final void enter() {
|
|
if (inQueue) throw new IllegalStateException("Unbalanced enter/exit");
|
|
long timeoutNanos = timeoutNanos();
|
|
boolean hasDeadline = hasDeadline();
|
|
if (timeoutNanos == 0 && !hasDeadline) {
|
|
return; // No timeout and no deadline? Don't bother with the queue.
|
|
}
|
|
inQueue = true;
|
|
scheduleTimeout(this, timeoutNanos, hasDeadline);
|
|
}
|
|
|
|
private static synchronized void scheduleTimeout(
|
|
AsyncTimeout node, long timeoutNanos, boolean hasDeadline) {
|
|
// Start the watchdog thread and create the head node when the first timeout is scheduled.
|
|
if (head == null) {
|
|
head = new AsyncTimeout();
|
|
new Watchdog().start();
|
|
}
|
|
|
|
long now = System.nanoTime();
|
|
if (timeoutNanos != 0 && hasDeadline) {
|
|
// Compute the earliest event; either timeout or deadline. Because nanoTime can wrap around,
|
|
// Math.min() is undefined for absolute values, but meaningful for relative ones.
|
|
node.timeoutAt = now + Math.min(timeoutNanos, node.deadlineNanoTime() - now);
|
|
} else if (timeoutNanos != 0) {
|
|
node.timeoutAt = now + timeoutNanos;
|
|
} else if (hasDeadline) {
|
|
node.timeoutAt = node.deadlineNanoTime();
|
|
} else {
|
|
throw new AssertionError();
|
|
}
|
|
|
|
// Insert the node in sorted order.
|
|
long remainingNanos = node.remainingNanos(now);
|
|
for (AsyncTimeout prev = head; true; prev = prev.next) {
|
|
if (prev.next == null || remainingNanos < prev.next.remainingNanos(now)) {
|
|
node.next = prev.next;
|
|
prev.next = node;
|
|
if (prev == head) {
|
|
AsyncTimeout.class.notify(); // Wake up the watchdog when inserting at the front.
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Returns true if the timeout occurred. */
|
|
public final boolean exit() {
|
|
if (!inQueue) return false;
|
|
inQueue = false;
|
|
return cancelScheduledTimeout(this);
|
|
}
|
|
|
|
/** Returns true if the timeout occurred. */
|
|
private static synchronized boolean cancelScheduledTimeout(AsyncTimeout node) {
|
|
// Remove the node from the linked list.
|
|
for (AsyncTimeout prev = head; prev != null; prev = prev.next) {
|
|
if (prev.next == node) {
|
|
prev.next = node.next;
|
|
node.next = null;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// The node wasn't found in the linked list: it must have timed out!
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Returns the amount of time left until the time out. This will be negative
|
|
* if the timeout has elapsed and the timeout should occur immediately.
|
|
*/
|
|
private long remainingNanos(long now) {
|
|
return timeoutAt - now;
|
|
}
|
|
|
|
/**
|
|
* Invoked by the watchdog thread when the time between calls to {@link
|
|
* #enter()} and {@link #exit()} has exceeded the timeout.
|
|
*/
|
|
protected void timedOut() {
|
|
}
|
|
|
|
/**
|
|
* Returns a new sink that delegates to {@code sink}, using this to implement
|
|
* timeouts. This works best if {@link #timedOut} is overridden to interrupt
|
|
* {@code sink}'s current operation.
|
|
*/
|
|
public final Sink sink(final Sink sink) {
|
|
return new Sink() {
|
|
@Override public void write(Buffer source, long byteCount) throws IOException {
|
|
boolean throwOnTimeout = false;
|
|
enter();
|
|
try {
|
|
sink.write(source, byteCount);
|
|
throwOnTimeout = true;
|
|
} catch (IOException e) {
|
|
throw exit(e);
|
|
} finally {
|
|
exit(throwOnTimeout);
|
|
}
|
|
}
|
|
|
|
@Override public void flush() throws IOException {
|
|
boolean throwOnTimeout = false;
|
|
enter();
|
|
try {
|
|
sink.flush();
|
|
throwOnTimeout = true;
|
|
} catch (IOException e) {
|
|
throw exit(e);
|
|
} finally {
|
|
exit(throwOnTimeout);
|
|
}
|
|
}
|
|
|
|
@Override public void close() throws IOException {
|
|
boolean throwOnTimeout = false;
|
|
enter();
|
|
try {
|
|
sink.close();
|
|
throwOnTimeout = true;
|
|
} catch (IOException e) {
|
|
throw exit(e);
|
|
} finally {
|
|
exit(throwOnTimeout);
|
|
}
|
|
}
|
|
|
|
@Override public Timeout timeout() {
|
|
return AsyncTimeout.this;
|
|
}
|
|
|
|
@Override public String toString() {
|
|
return "AsyncTimeout.sink(" + sink + ")";
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Returns a new source that delegates to {@code source}, using this to
|
|
* implement timeouts. This works best if {@link #timedOut} is overridden to
|
|
* interrupt {@code sink}'s current operation.
|
|
*/
|
|
public final Source source(final Source source) {
|
|
return new Source() {
|
|
@Override public long read(Buffer sink, long byteCount) throws IOException {
|
|
boolean throwOnTimeout = false;
|
|
enter();
|
|
try {
|
|
long result = source.read(sink, byteCount);
|
|
throwOnTimeout = true;
|
|
return result;
|
|
} catch (IOException e) {
|
|
throw exit(e);
|
|
} finally {
|
|
exit(throwOnTimeout);
|
|
}
|
|
}
|
|
|
|
@Override public void close() throws IOException {
|
|
boolean throwOnTimeout = false;
|
|
try {
|
|
source.close();
|
|
throwOnTimeout = true;
|
|
} catch (IOException e) {
|
|
throw exit(e);
|
|
} finally {
|
|
exit(throwOnTimeout);
|
|
}
|
|
}
|
|
|
|
@Override public Timeout timeout() {
|
|
return AsyncTimeout.this;
|
|
}
|
|
|
|
@Override public String toString() {
|
|
return "AsyncTimeout.source(" + source + ")";
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Throws an IOException if {@code throwOnTimeout} is {@code true} and a
|
|
* timeout occurred. See {@link #newTimeoutException(java.io.IOException)}
|
|
* for the type of exception thrown.
|
|
*/
|
|
final void exit(boolean throwOnTimeout) throws IOException {
|
|
boolean timedOut = exit();
|
|
if (timedOut && throwOnTimeout) throw newTimeoutException(null);
|
|
}
|
|
|
|
/**
|
|
* Returns either {@code cause} or an IOException that's caused by
|
|
* {@code cause} if a timeout occurred. See
|
|
* {@link #newTimeoutException(java.io.IOException)} for the type of
|
|
* exception returned.
|
|
*/
|
|
final IOException exit(IOException cause) throws IOException {
|
|
if (!exit()) return cause;
|
|
return newTimeoutException(cause);
|
|
}
|
|
|
|
/**
|
|
* Returns an {@link IOException} to represent a timeout. By default this method returns
|
|
* {@link java.io.InterruptedIOException}. If {@code cause} is non-null it is set as the cause of
|
|
* the returned exception.
|
|
*/
|
|
protected IOException newTimeoutException(IOException cause) {
|
|
InterruptedIOException e = new InterruptedIOException("timeout");
|
|
if (cause != null) {
|
|
e.initCause(cause);
|
|
}
|
|
return e;
|
|
}
|
|
|
|
private static final class Watchdog extends Thread {
|
|
public Watchdog() {
|
|
super("Okio Watchdog");
|
|
setDaemon(true);
|
|
}
|
|
|
|
public void run() {
|
|
while (true) {
|
|
try {
|
|
AsyncTimeout timedOut = awaitTimeout();
|
|
|
|
// Didn't find a node to interrupt. Try again.
|
|
if (timedOut == null) continue;
|
|
|
|
// Close the timed out node.
|
|
timedOut.timedOut();
|
|
} catch (InterruptedException ignored) {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Removes and returns the node at the head of the list, waiting for it to
|
|
* time out if necessary. Returns null if the situation changes while waiting:
|
|
* either a newer node is inserted at the head, or the node being waited on
|
|
* has been removed.
|
|
*/
|
|
private static synchronized AsyncTimeout awaitTimeout() throws InterruptedException {
|
|
// Get the next eligible node.
|
|
AsyncTimeout node = head.next;
|
|
|
|
// The queue is empty. Wait for something to be enqueued.
|
|
if (node == null) {
|
|
AsyncTimeout.class.wait();
|
|
return null;
|
|
}
|
|
|
|
long waitNanos = node.remainingNanos(System.nanoTime());
|
|
|
|
// The head of the queue hasn't timed out yet. Await that.
|
|
if (waitNanos > 0) {
|
|
// Waiting is made complicated by the fact that we work in nanoseconds,
|
|
// but the API wants (millis, nanos) in two arguments.
|
|
long waitMillis = waitNanos / 1000000L;
|
|
waitNanos -= (waitMillis * 1000000L);
|
|
AsyncTimeout.class.wait(waitMillis, (int) waitNanos);
|
|
return null;
|
|
}
|
|
|
|
// The head of the queue has timed out. Remove it.
|
|
head.next = node.next;
|
|
node.next = null;
|
|
return node;
|
|
}
|
|
}
|