5

Akka Testkit AutoPilot documentation examples show that we can send messages to a TestProbe right after invoking setAutoPilot:

  probe.setAutoPilot(new TestActor.AutoPilot {
    def run(sender: ActorRef, msg: Any): TestActor.AutoPilot =
      msg match {
        case "stop" ⇒ TestActor.NoAutoPilot
        case x      ⇒ testActor.tell(x, sender); TestActor.KeepRunning
      }
  })
  //#autopilot
  probe.ref ! "hallo"

On the other handsetAutoPilot has been implemented as sending a message to the testActor:

def setAutoPilot(pilot: TestActor.AutoPilot): Unit = testActor ! TestActor.SetAutoPilot(pilot)

According to Akka message receive order guarantees, there is no way for testActor (probe.ref) to receive "hallo" before TestActor.SetAutoPilot(pilot) because both are being sent from the same origin.

However, if we used a third actor (created using system.actorOf(...)) to send a "hello" to probe.ref,

wouldn't it be possible that, under some circumstances, it got received by probe.ref before TestActor.SetAutoPilot(pilot) thus ending up being ignored?

1 Answers1

0

In theory - yes, definitely - and that's basically your own answer to the question. In practice, this is unlikely, since the other message travels the longer path, so something very unusual needs to happen so that it arrives earlier.

Since theorizing around this wouldn't give any actionable answer, I've written a test to have an empirical observation:

Build.sbt:

libraryDependencies ++= Seq(
  "com.typesafe.akka" %% "akka-actor" % "2.5.17"
)

libraryDependencies ++= Seq(
  "com.typesafe.akka" %% "akka-testkit" % "2.5.17",
  "org.scalactic" %% "scalactic" % "3.0.5",
  "org.scalatest" %% "scalatest" % "3.0.5",
  "org.scalacheck" %% "scalacheck" % "1.14.0"
) map (_ % "test")

Test:

import scala.concurrent.duration.DurationInt

import akka.actor.{Actor, ActorRef, ActorSystem, Props}
import akka.pattern.ask
import akka.testkit.{TestActor, TestKit, TestProbe}
import akka.util.Timeout
import org.scalatest.{Matchers, PropSpecLike}
import org.scalatest.concurrent.ScalaFutures
import org.scalatest.prop.PropertyChecks

class AutopilotTest extends TestKit(ActorSystem("test"))
  with PropSpecLike with PropertyChecks with ScalaFutures with Matchers {

  private implicit val askTimeout: Timeout = Timeout(100.millis)

  property("Test probe receives autopilot before any other message from same origin") {
    forAll(minSuccessful(1000)) { msg: String =>
      val probe = TestProbe()
      probe.setAutoPilot((sender: ActorRef, msg: Any) => msg match {
        case x => sender ! x; TestActor.KeepRunning
      })
      whenReady((probe.ref ? msg).mapTo[String]) {_ shouldBe msg}
    }
  }

  private class ProxyActor(target: ActorRef) extends Actor {
    override def receive: Receive = { case msg: Any => target forward msg }
  }
  private object ProxyActor { def props(target: ActorRef): Props = Props(new ProxyActor(target)) }

  property("Test probe receives autopilot before any other message from other origin") {
    // set minSuccessuful to as high as you want, but note that current version takes ~38 seconds on my laptop to run
    forAll(minSuccessful(1000)) { msg: String =>
      val probe = TestProbe()
      val proxy = system.actorOf(ProxyActor.props(probe.ref))
      val result = (proxy ? msg).mapTo[String]
      probe.setAutoPilot((sender: ActorRef, msg: Any) => msg match {
        case x => sender ! x; TestActor.KeepRunning
      })
      whenReady(result) {_ shouldBe msg}
    }
  }
}

Practically, I went up all they way to 10000 repeats for the second test, and the tests always passed - also I've made sure it fails if autopilot is set after sending a message to proxy, or if testProbe does not respond.

So, I would say the potential issue you're describing either does not happen at all, or is highly unlikely. This test is a very simplistic one of course, so under other conditions (i.e. blocking, parallel test execution, CI, etc.) observations might be different, but at least it provides some evidence for a most common/simple case.

J0HN
  • 26,063
  • 5
  • 54
  • 85