diff --git a/akka-actor-tests/src/test/scala/akka/actor/SchedulerSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/SchedulerSpec.scala index ba34987c9c..0710be6ad5 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/SchedulerSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/SchedulerSpec.scala @@ -2,6 +2,7 @@ package akka.actor import language.postfixOps +import com.typesafe.config.ConfigFactory import org.scalatest.BeforeAndAfterEach import scala.concurrent.duration._ import java.util.concurrent.{ CountDownLatch, ConcurrentLinkedQueue, TimeUnit } @@ -9,9 +10,16 @@ import akka.testkit._ import scala.concurrent.Await import akka.pattern.ask import java.util.concurrent.atomic.AtomicInteger +import java.util.concurrent.TimeoutException + +object SchedulerSpec { + val testConf = ConfigFactory.parseString(""" + akka.scheduler.ticks-per-wheel = 32 + """).withFallback(AkkaSpec.testConf) +} @org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) -class SchedulerSpec extends AkkaSpec with BeforeAndAfterEach with DefaultTimeout with ImplicitSender { +class SchedulerSpec extends AkkaSpec(SchedulerSpec.testConf) with BeforeAndAfterEach with DefaultTimeout with ImplicitSender { private val cancellables = new ConcurrentLinkedQueue[Cancellable]() import system.dispatcher @@ -231,4 +239,31 @@ class SchedulerSpec extends AkkaSpec with BeforeAndAfterEach with DefaultTimeout n * 1000.0 / (System.nanoTime - startTime).nanos.toMillis must be(4.4 plusOrMinus 0.3) } } + + "A HashedWheelTimer" must { + + "not mess up long timeouts" taggedAs LongRunningTest in { + val longish = Long.MaxValue.nanos + val barrier = TestLatch() + import system.dispatcher + val job = system.scheduler.scheduleOnce(longish)(barrier.countDown()) + intercept[TimeoutException] { + // this used to fire after 46 seconds due to wrap-around + Await.ready(barrier, 90 seconds) + } + job.cancel() + } + + "handle timeouts equal to multiple of wheel period" taggedAs TimingTest in { + val timeout = 3200 milliseconds + val barrier = TestLatch() + import system.dispatcher + val job = system.scheduler.scheduleOnce(timeout)(barrier.countDown()) + try { + Await.ready(barrier, 5000 milliseconds) + } finally { + job.cancel() + } + } + } } diff --git a/akka-actor-tests/src/test/scala/akka/util/DurationSpec.scala b/akka-actor-tests/src/test/scala/akka/util/DurationSpec.scala index ca285274aa..de8d8ab99b 100644 --- a/akka-actor-tests/src/test/scala/akka/util/DurationSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/util/DurationSpec.scala @@ -5,35 +5,12 @@ package akka.util import language.postfixOps -import org.scalatest.WordSpec -import org.scalatest.matchers.MustMatchers import scala.concurrent.duration._ -import scala.concurrent.Await -import java.util.concurrent.TimeUnit._ import akka.testkit.AkkaSpec -import akka.testkit.TestLatch -import java.util.concurrent.TimeoutException -import akka.testkit.LongRunningTest class DurationSpec extends AkkaSpec { - "A HashedWheelTimer" must { - - "not mess up long timeouts" taggedAs LongRunningTest in { - val longish = Long.MaxValue.nanos - val barrier = TestLatch() - import system.dispatcher - val job = system.scheduler.scheduleOnce(longish)(barrier.countDown()) - intercept[TimeoutException] { - // this used to fire after 46 seconds due to wrap-around - Await.ready(barrier, 90 seconds) - } - job.cancel() - } - - } - "Duration" must { "form a one-dimensional vector field" in { diff --git a/akka-actor/src/main/java/akka/util/internal/HashedWheelTimer.java b/akka-actor/src/main/java/akka/util/internal/HashedWheelTimer.java index 73b3cf143d..fcdee4866f 100644 --- a/akka-actor/src/main/java/akka/util/internal/HashedWheelTimer.java +++ b/akka-actor/src/main/java/akka/util/internal/HashedWheelTimer.java @@ -268,6 +268,8 @@ public class HashedWheelTimer implements Timer { // one tick early; that shouldn’t matter since we’re talking 270 years here if (relativeIndex < 0) relativeIndex = delay / tickDuration; if (relativeIndex == 0) relativeIndex = 1; + // if an integral number of wheel rotations, schedule one tick earlier + if ((relativeIndex & mask) == 0) relativeIndex--; final long remainingRounds = relativeIndex / wheel.length; // Add the timeout to the wheel.