функция sender() приводит к мертвым буквам для реализации, но не к модульным тестам

#scala #akka

Вопрос:

Я работаю с актерами Akka classic (работающими в игровой среде) и сталкиваюсь с проблемой с этим sender() методом. Когда я создаю и запускаю код (с помощью sbt run ), я вижу, что sender() это решает deadLetters проблему. Однако при запуске модульных тестов для этого субъекта sender() выполняется разрешение на правильную ссылку ActorRef (хотя это не должно быть так).

Он sender() вызывается из закрывающегося будущего, поэтому я вижу deadLetters его при запуске фактической реализации. Однако по какой-то причине я не вижу deadLetters этого в модульных тестах. Есть какие-нибудь идеи о том, почему существует разница в поведении между unittests и запущенным экземпляром?

Несколько примеров кода, дающих представление о структуре кода

 class MyActor extends Actor {
  private def throwError() = {
    // THIS IS WHERE sender() BEHAVIOR DIFFERENTIATES BETWEEN IMPLEMENTATION AND UNITTESTS
    sender() ! MyErrorMessage()

    throw new Exception()
  }

  private def myFutureMethod(args: SomeType): Future[Done] = {
    for {
       Some logic here...
    } yield {
      if (some logic check...)
        throwError()
      else
        Done
    }
  }

  override def stepReceive = {
    case MyMessage(args) => 
      myFutureMethod(args)
  }
}

 

Некоторые примеры кода, чтобы дать представление о структуре unittest

   "My failure test case" in {
    val testProbe = TestProbe()

    myActorImpl.tell(MyMessage(args), testProbe)

    // testProbe SHOULD BE NOT BE RECEIVING A MESSAGE BASED ON THE ABOVE
    // IMPLEMENTATION, BUT IT DOES.
    testProbe.expectNoMessage()
  }
 

Если у кого-нибудь есть какие-то советы или идеи для отладки, это было бы здорово. Спасибо.

Ответ №1:

Вызов sender кода, выполняемого как часть Future внутри субъекта, не является детерминированным (а именно, условие гонки), потому что вы MyActor можете перейти к обработке другого сообщения до того, как код в Future нем будет выполнен, и если с тех sender пор он изменился, возможно deadLetters , изменится.

В вашем тесте, если myActorImpl сообщение не обработано, sender оно все равно будет указывать на тестовый зонд.

Предполагая, что вы хотите , чтобы сообщение действительно было отправлено отправителю, вам нужно захватить его, прежде чем передавать его в Future соответствии с этими строками

 class MyActor extends Actor {
  private def throwError(target: ActorRef) = {
    // uses the captured sender, no race condition
    target ! MyErrorMessage()

    throw new Exception()
  }

  private def myFutureMethod(args: SomeType, sender: ActorRef): Future[Done] = {
    for {
       Some logic here...
    } yield {
      if (some logic check...)
        throwError(sender)
      else
        Done
    }
  }

  override def stepReceive = {
    case MyMessage(args) => 
      myFutureMethod(args, sender)  // captures the sender reference
  }
}
 

Комментарии:

1. Потрясающе, спасибо за ваш ответ, он многое проясняет.