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

akka.testkit.ManagedEventLoop.scala Maven / Gradle / Ivy

The newest version!
package akka.testkit

import java.util.concurrent.TimeUnit

import scala.util.{Try, Success, Failure }
import scala.concurrent.{Future, Promise}
import scala.concurrent.duration._
import scala.concurrent.duration.Duration

import scala.scalajs.js
import scala.scalajs.js.Dynamic.global
import scala.scalajs.js.timers._
import scala.collection.mutable.{ Queue, ArrayBuffer }

object ManagedEventLoop {

  private val jsSetTimeout = global.setTimeout
  private val jsSetImmediate = global.setImmediate
  private val jsSetInterval = global.setInterval
  private val jsClearTimeout = global.clearTimeout
  private val jsClearInterval = global.clearInterval

  def timer() = Duration(System.currentTimeMillis, TimeUnit.MILLISECONDS)

  type JSFun = js.Function0[Any]
  private abstract class Event(handler: JSFun) {
    val creationDate = timer()
    var externalQueueH: Option[js.Dynamic] = None
    def clear(): Unit
    def globalize(): Unit
    def hasToRun(now: Duration): Boolean
    val hasToBeRemoved: Boolean

    def run(): Unit = handler()
  }
  private final class TimeoutEvent(handler: => JSFun, time: Double) extends Event(handler) {
    def clear(): Unit = externalQueueH.map(x => jsClearTimeout(x))
    def globalize(): Unit =
      externalQueueH = Some(
        jsSetTimeout(() => {
          handler()
          events -= this
        }, time.asInstanceOf[js.Any])
      )
    def hasToRun(now: Duration): Boolean = {
      (now > creationDate + Duration(time, TimeUnit.MILLISECONDS))
    }
    val hasToBeRemoved: Boolean = true
  }
  private final object TimeoutEvent {
    def apply(handler: => JSFun, time: Double) = new TimeoutEvent(handler, time)
  }
  private final class IntervalEvent(handler: => JSFun, time: Double) extends Event(handler) {
    var lastRan = creationDate
    def clear(): Unit = externalQueueH.map(x => jsClearInterval(x))
    def globalize(): Unit =
      externalQueueH = Some(
        jsSetInterval(() => {
          handler()
          lastRan = timer
        }, time.asInstanceOf[js.Any])
      )
    def hasToRun(now: Duration): Boolean = {
      (now > lastRan + Duration(time, TimeUnit.MILLISECONDS))
    }
    val hasToBeRemoved: Boolean = false
  }
  private final object IntervalEvent {
    def apply(handler: => JSFun, time: Double) = new IntervalEvent(handler, time)
  }

  private val events = new ArrayBuffer[Event]()

  private var isBlocking: Int = 0

  def setBlocking = {
    if (isBlocking == 0)
      events.foreach(_.clear())

    isBlocking += 1
  }

  def resetBlocking = {
    isBlocking -= 1
    if (isBlocking == 0)
      events.foreach(_.globalize())
  }

  def tick(): Duration = {
    val now = timer()

    val toBeProcessed = events.filter(_.hasToRun(now))

    val toBeRemoved = toBeProcessed.filter(_.hasToBeRemoved)
    events --= toBeRemoved

    toBeProcessed.foreach(_.run())

    events --= toBeRemoved

    now
  }

  def reset: Unit = {
    global.setTimeout = jsSetTimeout
    global.setImmediate = jsSetImmediate
    global.setInterval = jsSetInterval
    global.clearTimeout = jsClearTimeout
    global.clearInterval = jsClearInterval
  }

  def manage: Unit = {
    global.setTimeout = { (f: js.Function0[_], delay: Number) =>
      if(f.toString() != "undefined") {
        val event = TimeoutEvent(f, delay.doubleValue())
        if (isBlocking == 0)
          event.globalize
        events += event
        event.asInstanceOf[SetTimeoutHandle]
      }
    }
    global.setImmediate = { (f: js.Function0[_]) =>
      if(f.toString() != "undefined") {
        val event = TimeoutEvent(f, 0)
        if (isBlocking == 0)
          event.globalize
        events += event
        event.asInstanceOf[SetTimeoutHandle]
      }
    }
    global.setInterval = { (f: js.Function0[_], interval: Number) =>
      val event = IntervalEvent(f, interval.doubleValue())
      if (isBlocking == 0)
        event.globalize()
      events += event
      event.asInstanceOf[SetIntervalHandle]
    }
    global.clearTimeout = (event: SetTimeoutHandle) => {
      event.asInstanceOf[TimeoutEvent].clear()
      events -= event.asInstanceOf[TimeoutEvent]
    }
    global.clearInterval = (event: SetIntervalHandle) => {
      event.asInstanceOf[IntervalEvent].clear()
      events -= event.asInstanceOf[IntervalEvent]
    }
  }
}

sealed trait CanAwait

object AwaitPermission extends CanAwait


trait Awaitable[T] {
   def ready(atMost: Duration)(implicit permit: CanAwait): Awaitable.this.type
   def result(atMost: Duration)(implicit permit: CanAwait): T
}

case class LastRan(time: Double)

object Await {
  def ready[T](f: Future[T], atMost: Duration): f.type = {
    result[T](f, atMost)
    f
  }

  def ready[T](awaitable: Awaitable[T], atMost: Duration): awaitable.type =
    awaitable.ready(atMost)(AwaitPermission)

  def result[T](f: Future[T]): T =
    result[T](f, Duration.Inf)

  def result[T](f: Future[T], atMost: Duration): T = {
    val initTime = ManagedEventLoop.timer()
    val endTime = initTime + atMost

    ManagedEventLoop.setBlocking
    @scala.annotation.tailrec
    def loop(f: Future[T]): Try[T] = {
      val execution: Duration = ManagedEventLoop.tick
      if(execution > endTime) Failure(new java.util.concurrent.TimeoutException(s"Futures timed out after [${atMost.toMillis}] milliseconds"))
      else f.value match {
        case None => loop(f)
        case Some(res) => res
      }
    }

    val ret = loop(f)
    ManagedEventLoop.resetBlocking
    ret match {
      case Success(m) => m
      // if it's a wrapped execution (something coming from inside a promise)
      // we need to unwrap it, otherwise just return the regular throwable
      case Failure(e) => throw {
        e match {
          case _: scala.concurrent.ExecutionException => e.getCause()
          case _ => e
        }
      }
    }
  }

  def result[T](awaitable: Awaitable[T], atMost: Duration): T =
    awaitable.result(atMost)(AwaitPermission)

}

class CyclicBarrier(val c: Int) {
  private var counter = c
  private var closed = Promise[Int]

  def countDown() = {
    counter -= 1
    if(counter == 0) closed.success(1)
  }
  def getCount() = counter
  def reset() = counter = c


  def await(timeout: Long, unit: TimeUnit): Boolean = {
    try {
      Await.result(closed.future, Duration.fromNanos(TimeUnit.NANOSECONDS.convert(timeout, unit)))
      true
    } catch {
      case e: Exception => throw e
    }
  }
}

class CountDownLatch(val c: Int) {
  private var counter = c
  private var closed = Promise[Int]

  def countDown() = {
    counter -= 1
    if(counter == 0) closed.success(1)
  }
  def getCount() = counter
  def reset() = counter = c


  def await(): Boolean = {
    try {
      Await.result(closed.future, 100 seconds)
      true
    } catch {
      case e: Exception => throw e
    }
  }

  def await(timeout: Long, unit: TimeUnit): Boolean = {
    try {
      Await.result(closed.future, Duration.fromNanos(TimeUnit.NANOSECONDS.convert(timeout, unit)))
      true
    } catch {
      case e: Exception => throw e
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy