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

monix.execution.schedulers.TrampolineExecutionContext.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2014-2019 by The Monix Project Developers.
 * See the project homepage at: https://monix.io
 *
 * 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 monix.execution.schedulers

import monix.execution.internal.Trampoline
import scala.util.control.NonFatal
import scala.concurrent.{BlockContext, CanAwait, ExecutionContext}

/** A `scala.concurrentExecutionContext` implementation
  * that executes runnables immediately, on the current thread,
  * by means of a trampoline implementation.
  *
  * Can be used in some cases to keep the asynchronous execution
  * on the current thread, as an optimization, but be warned,
  * you have to know what you're doing.
  *
  * The `TrampolineExecutionContext` keeps a reference to another
  * `underlying` context, to which it defers for:
  *
  *  - reporting errors
  *  - deferring the rest of the queue in problematic situations
  *
  * Deferring the rest of the queue happens:
  *
  *  - in case we have a runnable throwing an exception, the rest
  *    of the tasks get re-scheduled for execution by using
  *    the `underlying` context
  *  - in case we have a runnable triggering a Scala `blocking`
  *    context, the rest of the tasks get re-scheduled for execution
  *    on the `underlying` context to prevent any deadlocks
  *
  * Thus this implementation is compatible with the
  * `scala.concurrent.BlockContext`, detecting `blocking` blocks and
  * reacting by forking the rest of the queue to prevent deadlocks.
  *
  * @param underlying is the `ExecutionContext` to which the it defers
  *        to in case real asynchronous is needed
  */
final class TrampolineExecutionContext private (underlying: ExecutionContext) extends ExecutionContext {

  private[this] val trampoline =
    new ThreadLocal[Trampoline]() {
      override def initialValue(): Trampoline =
        TrampolineExecutionContext.buildTrampoline(underlying)
    }

  override def execute(runnable: Runnable): Unit =
    trampoline.get().execute(runnable)
  override def reportFailure(t: Throwable): Unit =
    underlying.reportFailure(t)
}

object TrampolineExecutionContext {
  /** Builds a [[TrampolineExecutionContext]] instance.
    *
    * @param underlying is the `ExecutionContext` to which the
    *        it defers to in case asynchronous or time-delayed execution
    *        is needed
    */
  def apply(underlying: ExecutionContext): TrampolineExecutionContext =
    new TrampolineExecutionContext(underlying)

  /** [[TrampolineExecutionContext]] instance that executes everything
    * immediately, on the current thread.
    *
    * Implementation notes:
    *
    *  - if too many `blocking` operations are chained, at some point
    *    the implementation will trigger a stack overflow error
    *  - `reportError` re-throws the exception in the hope that it
    *    will get caught and reported by the underlying thread-pool,
    *    because there's nowhere it could report that error safely
    *    (i.e. `System.err` might be routed to `/dev/null` and we'd
    *    have no way to override it)
    */
  val immediate: TrampolineExecutionContext =
    TrampolineExecutionContext(new ExecutionContext {
      def execute(r: Runnable): Unit = r.run()
      def reportFailure(e: Throwable): Unit = throw e
    })

  /** Returns the `localContext`, allowing us to bypass calling
    * `BlockContext.withBlockContext`, as an optimization trick.
    */
  private val localContext: ThreadLocal[BlockContext] = {
    try {
      val methods = BlockContext.getClass.getDeclaredMethods
        .filter(m => m.getParameterCount == 0 && m.getReturnType == classOf[ThreadLocal[_]])
        .toList

      methods match {
        case m :: Nil =>
          m.setAccessible(true)
          m.invoke(BlockContext).asInstanceOf[ThreadLocal[BlockContext]]
        case _ =>
          throw new NoSuchMethodError("BlockContext.contextLocal")
      }
    } catch {
      case _: NoSuchMethodError => null
      case _: SecurityException => null
      case NonFatal(_) => null
    }
  }

  private def buildTrampoline(underlying: ExecutionContext): Trampoline = {
    if (localContext ne null)
      new JVMOptimalTrampoline(underlying)
    else
      new JVMNormalTrampoline(underlying)
  }

  private final class JVMOptimalTrampoline(underlying: ExecutionContext) extends Trampoline(underlying) {

    private[this] val trampolineContext: BlockContext =
      new BlockContext {
        def blockOn[T](thunk: => T)(implicit permission: CanAwait): T = {
          // In case of blocking, execute all scheduled local tasks on
          // a separate thread, otherwise we could end up with a dead-lock
          forkTheRest()
          thunk
        }
      }

    override def startLoop(runnable: Runnable): Unit = {
      val parentContext = localContext.get()
      localContext.set(trampolineContext)
      try {
        super.startLoop(runnable)
      } finally {
        localContext.set(parentContext)
      }
    }
  }

  private class JVMNormalTrampoline(underlying: ExecutionContext) extends Trampoline(underlying) {

    private[this] val trampolineContext: BlockContext =
      new BlockContext {
        def blockOn[T](thunk: => T)(implicit permission: CanAwait): T = {
          // In case of blocking, execute all scheduled local tasks on
          // a separate thread, otherwise we could end up with a dead-lock
          forkTheRest()
          thunk
        }
      }

    override def startLoop(runnable: Runnable): Unit = {
      BlockContext.withBlockContext(trampolineContext) {
        super.startLoop(runnable)
      }
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy