aboutsummaryrefslogtreecommitdiff
path: root/okio/src/jvmMain/kotlin/okio/Timeout.kt
diff options
context:
space:
mode:
Diffstat (limited to 'okio/src/jvmMain/kotlin/okio/Timeout.kt')
-rw-r--r--okio/src/jvmMain/kotlin/okio/Timeout.kt233
1 files changed, 233 insertions, 0 deletions
diff --git a/okio/src/jvmMain/kotlin/okio/Timeout.kt b/okio/src/jvmMain/kotlin/okio/Timeout.kt
new file mode 100644
index 00000000..c522380f
--- /dev/null
+++ b/okio/src/jvmMain/kotlin/okio/Timeout.kt
@@ -0,0 +1,233 @@
+/*
+ * 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 okio
+
+import java.io.IOException
+import java.io.InterruptedIOException
+import java.util.concurrent.TimeUnit
+
+actual open class Timeout {
+ /**
+ * True if `deadlineNanoTime` is defined. There is no equivalent to null or 0 for
+ * [System.nanoTime].
+ */
+ private var hasDeadline = false
+ private var deadlineNanoTime = 0L
+ private var timeoutNanos = 0L
+
+ /**
+ * Wait at most `timeout` time before aborting an operation. Using a per-operation timeout means
+ * that as long as forward progress is being made, no sequence of operations will fail.
+ *
+ * If `timeout == 0`, operations will run indefinitely. (Operating system timeouts may still
+ * apply.)
+ */
+ open fun timeout(timeout: Long, unit: TimeUnit): Timeout {
+ require(timeout >= 0) { "timeout < 0: $timeout" }
+ timeoutNanos = unit.toNanos(timeout)
+ return this
+ }
+
+ /** Returns the timeout in nanoseconds, or `0` for no timeout. */
+ open fun timeoutNanos(): Long = timeoutNanos
+
+ /** Returns true if a deadline is enabled. */
+ open fun hasDeadline(): Boolean = hasDeadline
+
+ /**
+ * Returns the [nano time][System.nanoTime] when the deadline will be reached.
+ *
+ * @throws IllegalStateException if no deadline is set.
+ */
+ open fun deadlineNanoTime(): Long {
+ check(hasDeadline) { "No deadline" }
+ return deadlineNanoTime
+ }
+
+ /**
+ * Sets the [nano time][System.nanoTime] when the deadline will be reached. All operations must
+ * complete before this time. Use a deadline to set a maximum bound on the time spent on a
+ * sequence of operations.
+ */
+ open fun deadlineNanoTime(deadlineNanoTime: Long): Timeout {
+ this.hasDeadline = true
+ this.deadlineNanoTime = deadlineNanoTime
+ return this
+ }
+
+ /** Set a deadline of now plus `duration` time. */
+ fun deadline(duration: Long, unit: TimeUnit): Timeout {
+ require(duration > 0) { "duration <= 0: $duration" }
+ return deadlineNanoTime(System.nanoTime() + unit.toNanos(duration))
+ }
+
+ /** Clears the timeout. Operating system timeouts may still apply. */
+ open fun clearTimeout(): Timeout {
+ timeoutNanos = 0
+ return this
+ }
+
+ /** Clears the deadline. */
+ open fun clearDeadline(): Timeout {
+ hasDeadline = false
+ return this
+ }
+
+ /**
+ * Throws an [InterruptedIOException] if the deadline has been reached or if the current thread
+ * has been interrupted. This method doesn't detect timeouts; that should be implemented to
+ * asynchronously abort an in-progress operation.
+ */
+ @Throws(IOException::class)
+ open fun throwIfReached() {
+ if (Thread.currentThread().isInterrupted) {
+ // If the current thread has been interrupted.
+ throw InterruptedIOException("interrupted")
+ }
+
+ if (hasDeadline && deadlineNanoTime - System.nanoTime() <= 0) {
+ throw InterruptedIOException("deadline reached")
+ }
+ }
+
+ /**
+ * Waits on `monitor` until it is notified. Throws [InterruptedIOException] if either the thread
+ * is interrupted or if this timeout elapses before `monitor` is notified. The caller must be
+ * synchronized on `monitor`.
+ *
+ * Here's a sample class that uses `waitUntilNotified()` to await a specific state. Note that the
+ * call is made within a loop to avoid unnecessary waiting and to mitigate spurious notifications.
+ * ```
+ * class Dice {
+ * Random random = new Random();
+ * int latestTotal;
+ *
+ * public synchronized void roll() {
+ * latestTotal = 2 + random.nextInt(6) + random.nextInt(6);
+ * System.out.println("Rolled " + latestTotal);
+ * notifyAll();
+ * }
+ *
+ * public void rollAtFixedRate(int period, TimeUnit timeUnit) {
+ * Executors.newScheduledThreadPool(0).scheduleAtFixedRate(new Runnable() {
+ * public void run() {
+ * roll();
+ * }
+ * }, 0, period, timeUnit);
+ * }
+ *
+ * public synchronized void awaitTotal(Timeout timeout, int total)
+ * throws InterruptedIOException {
+ * while (latestTotal != total) {
+ * timeout.waitUntilNotified(this);
+ * }
+ * }
+ * }
+ * ```
+ */
+ @Throws(InterruptedIOException::class)
+ fun waitUntilNotified(monitor: Any) {
+ try {
+ val hasDeadline = hasDeadline()
+ val timeoutNanos = timeoutNanos()
+
+ if (!hasDeadline && timeoutNanos == 0L) {
+ (monitor as Object).wait() // There is no timeout: wait forever.
+ return
+ }
+
+ // Compute how long we'll wait.
+ val start = System.nanoTime()
+ val waitNanos = if (hasDeadline && timeoutNanos != 0L) {
+ val deadlineNanos = deadlineNanoTime() - start
+ minOf(timeoutNanos, deadlineNanos)
+ } else if (hasDeadline) {
+ deadlineNanoTime() - start
+ } else {
+ timeoutNanos
+ }
+
+ // Attempt to wait that long. This will break out early if the monitor is notified.
+ var elapsedNanos = 0L
+ if (waitNanos > 0L) {
+ val waitMillis = waitNanos / 1000000L
+ (monitor as Object).wait(waitMillis, (waitNanos - waitMillis * 1000000L).toInt())
+ elapsedNanos = System.nanoTime() - start
+ }
+
+ // Throw if the timeout elapsed before the monitor was notified.
+ if (elapsedNanos >= waitNanos) {
+ throw InterruptedIOException("timeout")
+ }
+ } catch (e: InterruptedException) {
+ Thread.currentThread().interrupt() // Retain interrupted status.
+ throw InterruptedIOException("interrupted")
+ }
+ }
+
+ /**
+ * Applies the minimum intersection between this timeout and `other`, run `block`, then finally
+ * rollback this timeout's values.
+ */
+ inline fun intersectWith(other: Timeout, block: () -> Unit) {
+ val originalTimeout = this.timeoutNanos()
+ this.timeout(minTimeout(other.timeoutNanos(), this.timeoutNanos()), TimeUnit.NANOSECONDS)
+
+ if (this.hasDeadline()) {
+ val originalDeadline = this.deadlineNanoTime()
+ if (other.hasDeadline()) {
+ this.deadlineNanoTime(Math.min(this.deadlineNanoTime(), other.deadlineNanoTime()))
+ }
+ try {
+ block()
+ } finally {
+ this.timeout(originalTimeout, TimeUnit.NANOSECONDS)
+ if (other.hasDeadline()) {
+ this.deadlineNanoTime(originalDeadline)
+ }
+ }
+ } else {
+ if (other.hasDeadline()) {
+ this.deadlineNanoTime(other.deadlineNanoTime())
+ }
+ try {
+ block()
+ } finally {
+ this.timeout(originalTimeout, TimeUnit.NANOSECONDS)
+ if (other.hasDeadline()) {
+ this.clearDeadline()
+ }
+ }
+ }
+ }
+
+ actual companion object {
+ @JvmField actual val NONE: Timeout = object : Timeout() {
+ override fun timeout(timeout: Long, unit: TimeUnit): Timeout = this
+
+ override fun deadlineNanoTime(deadlineNanoTime: Long): Timeout = this
+
+ override fun throwIfReached() {}
+ }
+
+ fun minTimeout(aNanos: Long, bNanos: Long) = when {
+ aNanos == 0L -> bNanos
+ bNanos == 0L -> aNanos
+ aNanos < bNanos -> aNanos
+ else -> bNanos
+ }
+ }
+}