Akka FSM: переход состояния в то же состояние происходит не так, как ожидалось

В реализации FSM (старая история: моделирование лифта/лифта) я пытаюсь посмотреть, смогу ли я проверить, следует ли FSM переходу из одного состояния и в одно и то же. Кажется, этого не происходит, но это меня удивляет, потому что согласно the-same-state?rq=1">это обсуждение на StackOverFlow, исправление доступно в Akka 2.4.x.

Вот соответствующая часть кода:

  class LiftCarriageWithMovingState extends Actor
  with FSM[LiftState,LiftData]
  with ActorLogging
{

  import context._

  val mxTimeToWaitStopping = 500 milliseconds
  private var pendingPassengerRequests: Vector[NextStop] = Vector.empty
  private var currentFloorID = 0 // Always start at Ground Floor



  val timeToReachNextFloor = 1000 millis  // up  or down, same time taken

  private val dontCare: StateFunction = { /* ... */ }

  private val powerYourselfOff: StateFunction = { /* ... */ }

  private val powerYourselfOn: StateFunction = { /* ... */ }

  private val beReady: StateFunction = { /* ... */ }

  private val moveToWaitingPassenger: StateFunction = { /* ... */ }

  private val transportPassengerToDest: StateFunction = {/* ... */ }

  private val actWhenStoppedForLongEnough: StateFunction = {
    case Event(StateTimeout,_)   =>
      if (this.pendingPassengerRequests isEmpty) {
        println("timeout in Stopped, no passenger requests pending")
        goto (Stopped)
      }
      else {
        println("timeout in Stopped, moving to floor(" + this.pendingPassengerRequests.head + ")")
        simulateMovementTo(this.pendingPassengerRequests.head)
        goto(Moving)
      }
  }

  startWith(PoweredOff,InitialData)

  when (PoweredOff) (powerYourselfOn orElse
                     dontCare)

  when (PoweredOn)  (powerYourselfOff orElse
                     beReady orElse
                     dontCare)

  when (Ready)      (moveToWaitingPassenger   orElse
                     transportPassengerToDest )


  when (Stopped)   (actWhenStoppedForLongEnough orElse
                    moveToWaitingPassenger      orElse
                    transportPassengerToDest    )

  when (Moving)              {
    case Event(ReachedFloor(floorID, PurposeOfMovement.ToWelcomeInAnWaitingPassenger),_) =>
      currentFloorID = pendingPassengerRequests.head.floorID
      pendingPassengerRequests = pendingPassengerRequests.tail
      goto (Stopped) forMax(this.mxTimeToWaitStopping)

    case Event(ReachedFloor(floorID,PurposeOfMovement.ToAllowATransportedPassengerAlight),_) =>
      currentFloorID = pendingPassengerRequests.head.floorID
      pendingPassengerRequests = pendingPassengerRequests.tail
      goto (Stopped) forMax(this.mxTimeToWaitStopping)

   // .... Other events
  }

  whenUnhandled {
    // Event handlers...
  }

  onTransition {
    case Stopped -> Stopped => {
      println("Remaining in Stopped ...")
    }
  }

  // Rest of the Actor

Я вырезал ненужные части, потому что суть вот в чем: когда актер получает

Event(ReachedFloor(floorID, PurposeOfMovement.ToWelcomeInAnWaitingPassenger),_)

когда он находится в состоянии «Перемещение», он переключается в состояние «Остановка» с заданным тайм-аутом. Когда этот таймер срабатывает, когда FSM остановлен, обработчик, который должен выполняться, получает имя (также показано выше):

actWhenStoppedForLongEnough

Здесь я проверяю определенное условие выхода, и если я не доволен, я снова переключаюсь обратно в то же состояние Stopped. Сейчас я использую goto(), но исходный код имел stay().

Я также установил блок onTransition (снова показан выше), чтобы доказать, что переход действительно происходит.

onTransition {
    case Stopped -> Stopped => {
      println("Remaining in Stopped ...")
    }
  }

Когда я запускаю код, оператор println вообще не выполняется.

У меня также есть тестовый код; соответствующий тестовый пример таков:

var carriage: ActorRef = _

  override def beforeAll(): Unit = {
    super.beforeAll()

    carriage = TestActorRef(Props(new LiftCarriageWithMovingState))
    carriage ! SubscribeTransitionCallBack(testActor)
  }

  "A LiftCarriage" must {
    "be ready, when it settles down after being PoweredOn" in {

      expectMsg(Duration(100, TimeUnit.MILLISECONDS), new CurrentState(carriage,PoweredOff))
      carriage ! InstructedToPowerOn
      expectMsg (new Transition(carriage,PoweredOff,PoweredOn))
      carriage ! BeReady
      expectMsg((new Transition(carriage,PoweredOn,Ready)))
    }

    "move to where a passenger is waiting, if ready" in {
      carriage ! ReportCurrentFloor
      expectMsg(StoppedAt(0))
      carriage ! PassengerIsWaitingAt(3)
      expectMsg(new Transition(carriage,Ready,Moving))
      expectMsg(Duration(5000, TimeUnit.MILLISECONDS), new Transition(carriage,Moving, Stopped))
      carriage ! ReportCurrentFloor
      expectMsg(StoppedAt(3))

    }
    "let a passenger in and transport her to her destination" in {
      carriage ! ReportCurrentFloor
      expectMsg(StoppedAt(3))
      carriage ! PassengerRequestsATransportTo(Vector(7))
      expectMsg(new Transition(carriage,Stopped,Moving))
      expectMsg(Duration(5000, TimeUnit.MILLISECONDS), new Transition(carriage,Moving,Stopped))
      carriage ! ReportCurrentFloor
      expectMsg(StoppedAt(7))

      // This always times out and fails.
      expectMsg(Duration(5000, TimeUnit.MILLISECONDS), new Transition(carriage,Stopped,Stopped)) 

    }

Я пытаюсь усердно размышлять о том, есть ли пробел в моем понимании и где, но безуспешно. Любые подсказки?

Вот build.sbt:

name := """akka-sample-fsm-scala"""

version := "2.4"

scalaVersion := "2.11.8"

libraryDependencies ++= Seq(
  // https://mvnrepository.com/artifact/com.typesafe.akka/akka-actor_2.11
  "com.typesafe.akka" % "akka-actor_2.11" % "2.4.12",
// https://mvnrepository.com/artifact/org.scalatest/scalatest_2.11
  "org.scalatest" % "scalatest_2.11" % "2.1.3",
  // https://mvnrepository.com/artifact/com.typesafe.akka/akka-testkit_2.11,
  "com.typesafe.akka" % "akka-testkit_2.11" % "2.3.15"
)

Еще один вопрос, очень похожий на мой - уже спрашивали, но ответов не вижу. Может быть, ответ уже найден, но еще не опубликован.


person Nirmalya    schedule 06.11.2016    source источник
comment
Существует тестовое покрытие для перехода в одно и то же состояние, вызывающее onTransition здесь было изменено до выхода Akka 2.4.0 (в тикете 13970) Не вижу ничего явно неправильного в вашем коде, единственное, что странно, насколько я вижу, это то, что вы используете старую версию akka-testkit вместе с текущей akka-actor может это как-то связано с твоей проблемой?   -  person johanandren    schedule 09.11.2016
comment
Не уверен, что разъяснение здесь поможет кому-либо. Я также не ответил на @stackoverflow.com/users/1219125/johanandren (приношу свои искренние извинения, я был невежлив) . Вот что я нашел в документации Akka, и это похоже на мой вопрос (продолжение...)   -  person Nirmalya    schedule 15.06.2017
comment
'' '..Обратите внимание, что изменение состояния включает в себя действие по выполнению перехода (S), уже находясь в состоянии S. В этом случае субъект мониторинга будет уведомлен сообщением Transition(ref,S,S). Это может быть полезно, если ваш FSM должен реагировать на все переходы (также в одном и том же состоянии). Если вы не хотите генерировать события для переходов в одном и том же состоянии, используйте stay() вместо goto(S)...'   -  person Nirmalya    schedule 15.06.2017