All Downloads are FREE. Search and download functionalities are using the official Maven repository.

main.okhttp3.internal.concurrent.TaskRunner.kt Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2019 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 okhttp3.internal.concurrent

import java.util.concurrent.BlockingQueue
import java.util.concurrent.SynchronousQueue
import java.util.concurrent.ThreadFactory
import java.util.concurrent.ThreadPoolExecutor
import java.util.concurrent.TimeUnit
import java.util.concurrent.locks.Condition
import java.util.concurrent.locks.ReentrantLock
import java.util.logging.Logger
import kotlin.concurrent.withLock
import okhttp3.internal.addIfAbsent
import okhttp3.internal.assertHeld
import okhttp3.internal.concurrent.TaskRunner.Companion.INSTANCE
import okhttp3.internal.okHttpName
import okhttp3.internal.threadFactory

/**
 * A set of worker threads that are shared among a set of task queues.
 *
 * Use [INSTANCE] for a task runner that uses daemon threads. There is not currently a shared
 * instance for non-daemon threads.
 *
 * The task runner is also responsible for releasing held threads when the library is unloaded.
 * This is for the benefit of container environments that implement code unloading.
 *
 * Most applications should share a process-wide [TaskRunner] and use queues for per-client work.
 */
class TaskRunner(
  val backend: Backend,
  internal val logger: Logger = TaskRunner.logger,
) {
  val lock: ReentrantLock = ReentrantLock()
  val condition: Condition = lock.newCondition()

  private var nextQueueName = 10000
  private var coordinatorWaiting = false
  private var coordinatorWakeUpAt = 0L

  /** Queues with tasks that are currently executing their [TaskQueue.activeTask]. */
  private val busyQueues = mutableListOf()

  /** Queues not in [busyQueues] that have non-empty [TaskQueue.futureTasks]. */
  private val readyQueues = mutableListOf()

  private val runnable: Runnable =
    object : Runnable {
      override fun run() {
        while (true) {
          val task =
            [email protected] {
              awaitTaskToRun()
            } ?: return

          logger.logElapsed(task, task.queue!!) {
            var completedNormally = false
            try {
              runTask(task)
              completedNormally = true
            } finally {
              // If the task is crashing start another thread to service the queues.
              if (!completedNormally) {
                lock.withLock {
                  backend.execute(this@TaskRunner, this)
                }
              }
            }
          }
        }
      }
    }

  internal fun kickCoordinator(taskQueue: TaskQueue) {
    lock.assertHeld()

    if (taskQueue.activeTask == null) {
      if (taskQueue.futureTasks.isNotEmpty()) {
        readyQueues.addIfAbsent(taskQueue)
      } else {
        readyQueues.remove(taskQueue)
      }
    }

    if (coordinatorWaiting) {
      backend.coordinatorNotify(this@TaskRunner)
    } else {
      backend.execute(this@TaskRunner, runnable)
    }
  }

  private fun beforeRun(task: Task) {
    lock.assertHeld()

    task.nextExecuteNanoTime = -1L
    val queue = task.queue!!
    queue.futureTasks.remove(task)
    readyQueues.remove(queue)
    queue.activeTask = task
    busyQueues.add(queue)
  }

  private fun runTask(task: Task) {
    val currentThread = Thread.currentThread()
    val oldName = currentThread.name
    currentThread.name = task.name

    var delayNanos = -1L
    try {
      delayNanos = task.runOnce()
    } finally {
      lock.withLock {
        afterRun(task, delayNanos)
      }
      currentThread.name = oldName
    }
  }

  private fun afterRun(
    task: Task,
    delayNanos: Long,
  ) {
    lock.assertHeld()

    val queue = task.queue!!
    check(queue.activeTask === task)

    val cancelActiveTask = queue.cancelActiveTask
    queue.cancelActiveTask = false
    queue.activeTask = null
    busyQueues.remove(queue)

    if (delayNanos != -1L && !cancelActiveTask && !queue.shutdown) {
      queue.scheduleAndDecide(task, delayNanos, recurrence = true)
    }

    if (queue.futureTasks.isNotEmpty()) {
      readyQueues.add(queue)
    }
  }

  /**
   * Returns an immediately-executable task for the calling thread to execute, sleeping as necessary
   * until one is ready. If there are no ready queues, or if other threads have everything under
   * control this will return null. If there is more than a single task ready to execute immediately
   * this will launch another thread to handle that work.
   */
  fun awaitTaskToRun(): Task? {
    lock.assertHeld()

    while (true) {
      if (readyQueues.isEmpty()) {
        return null // Nothing to do.
      }

      val now = backend.nanoTime()
      var minDelayNanos = Long.MAX_VALUE
      var readyTask: Task? = null
      var multipleReadyTasks = false

      // Decide what to run. This loop's goal wants to:
      //  * Find out what this thread should do (either run a task or sleep)
      //  * Find out if there's enough work to start another thread.
      eachQueue@ for (queue in readyQueues) {
        val candidate = queue.futureTasks[0]
        val candidateDelay = maxOf(0L, candidate.nextExecuteNanoTime - now)

        when {
          // Compute the delay of the soonest-executable task.
          candidateDelay > 0L -> {
            minDelayNanos = minOf(candidateDelay, minDelayNanos)
            continue@eachQueue
          }

          // If we already have more than one task, that's enough work for now. Stop searching.
          readyTask != null -> {
            multipleReadyTasks = true
            break@eachQueue
          }

          // We have a task to execute when we complete the loop.
          else -> {
            readyTask = candidate
          }
        }
      }

      // Implement the decision.
      when {
        // We have a task ready to go. Get ready.
        readyTask != null -> {
          beforeRun(readyTask)

          // Also start another thread if there's more work or scheduling to do.
          if (multipleReadyTasks || !coordinatorWaiting && readyQueues.isNotEmpty()) {
            backend.execute(this@TaskRunner, runnable)
          }

          return readyTask
        }

        // Notify the coordinator of a task that's coming up soon.
        coordinatorWaiting -> {
          if (minDelayNanos < coordinatorWakeUpAt - now) {
            backend.coordinatorNotify(this@TaskRunner)
          }
          return null
        }

        // No other thread is coordinating. Become the coordinator!
        else -> {
          coordinatorWaiting = true
          coordinatorWakeUpAt = now + minDelayNanos
          try {
            backend.coordinatorWait(this@TaskRunner, minDelayNanos)
          } catch (_: InterruptedException) {
            // Will cause all tasks to exit unless more are scheduled!
            cancelAll()
          } finally {
            coordinatorWaiting = false
          }
        }
      }
    }
  }

  fun newQueue(): TaskQueue {
    val name = lock.withLock { nextQueueName++ }
    return TaskQueue(this, "Q$name")
  }

  /**
   * Returns a snapshot of queues that currently have tasks scheduled. The task runner does not
   * necessarily track queues that have no tasks scheduled.
   */
  fun activeQueues(): List {
    lock.withLock {
      return busyQueues + readyQueues
    }
  }

  fun cancelAll() {
    lock.assertHeld()
    for (i in busyQueues.size - 1 downTo 0) {
      busyQueues[i].cancelAllAndDecide()
    }
    for (i in readyQueues.size - 1 downTo 0) {
      val queue = readyQueues[i]
      queue.cancelAllAndDecide()
      if (queue.futureTasks.isEmpty()) {
        readyQueues.removeAt(i)
      }
    }
  }

  interface Backend {
    fun nanoTime(): Long

    fun coordinatorNotify(taskRunner: TaskRunner)

    fun coordinatorWait(
      taskRunner: TaskRunner,
      nanos: Long,
    )

    fun  decorate(queue: BlockingQueue): BlockingQueue

    fun execute(
      taskRunner: TaskRunner,
      runnable: Runnable,
    )
  }

  class RealBackend(threadFactory: ThreadFactory) : Backend {
    val executor =
      ThreadPoolExecutor(
        // corePoolSize:
        0,
        // maximumPoolSize:
        Int.MAX_VALUE,
        // keepAliveTime:
        60L,
        TimeUnit.SECONDS,
        SynchronousQueue(),
        threadFactory,
      )

    override fun nanoTime() = System.nanoTime()

    override fun coordinatorNotify(taskRunner: TaskRunner) {
      taskRunner.condition.signal()
    }

    /**
     * Wait a duration in nanoseconds. Unlike [java.lang.Object.wait] this interprets 0 as
     * "don't wait" instead of "wait forever".
     */
    @Throws(InterruptedException::class)
    @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
    override fun coordinatorWait(
      taskRunner: TaskRunner,
      nanos: Long,
    ) {
      taskRunner.lock.assertHeld()
      if (nanos > 0) {
        taskRunner.condition.awaitNanos(nanos)
      }
    }

    override fun  decorate(queue: BlockingQueue) = queue

    override fun execute(
      taskRunner: TaskRunner,
      runnable: Runnable,
    ) {
      executor.execute(runnable)
    }

    fun shutdown() {
      executor.shutdown()
    }
  }

  companion object {
    val logger: Logger = Logger.getLogger(TaskRunner::class.java.name)

    @JvmField
    val INSTANCE = TaskRunner(RealBackend(threadFactory("$okHttpName TaskRunner", daemon = true)))
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy