Наследование актера Akka с context.become

У меня есть сложная проблема при попытке изменить состояние дочернего актера с помощью метода become. Я реализовал наследование актера в соответствии с чертой Receiving, предложенной здесь:

trait Mcma extends Receiving with ActorLogging {
  val actorName: String

  /**
    * Simple method to be overridden when necessary
    * @param entity the response entity
    */
  protected def handleHttpOK(entity: ResponseEntity): Unit = log.info(s"$actorName got unhandled OK response")

  // Add http responses match if necessary
  receiver {
    case HttpResponse(StatusCodes.OK, _, entity, _) => handleHttpOK(entity)

    case resp @ HttpResponse(code, _, _, _) =>
      log.error(s"$actorName got response code: {}", code)
      // Discard the flow to avoid backpressure
      resp.discardEntityBytes()

    case e: Status.Failure => log.error(s"$actorName got failure: {}", e.cause.getMessage)

    case _ => log.warning(s"Unexpected message in $actorName")
  }
}

Проблема возникает, когда у меня есть актор, который реализует этот признак Mcma и меняет свое состояние с помощью шаблона become:

class Reseau(url: String, optSender: Option[ActorRef]) extends Mcma with Receiving {
  override val actorName: String = "ReseauActor"

  /**
    * The active method used to handle actor state change with the become helper
    * @param queryDataStr the query string for each call to ReseauActor
    * @return
    */
  def active(queryDataStr: String): Receive = {
    case s: String => context become active(s)
  }

  // Init the actor with empty query
  receiver(
    active("")
  )
}

После изменения контекста унаследованные случаи сопоставления по умолчанию, такие как e или _, больше не сопоставляются. Скорее всего проблема очевидна...

[Обновленная рабочая версия с ответом от Евгения] С упомянутой чертой Receiving актер Reseau становится

class Reseau(url: String, optSender: Option[ActorRef]) extends Mcma {
  override val actorName: String = "ReseauActor"

  override def preStart(): Unit = {
    super.preStart()
    // Init the actor with empty query
    context become receiver(active(""))
  }

  /**
    * The active method used to handle actor state change with the become helper
    *
    * @param queryDataStr the query string for each call to ReseauActor
    * @return
    */
  def active(queryDataStr: String): Receive = {

    case s: String => context become receiver(active(s))
  }
}

и черта Mcma:

trait Mcma extends Receiving with ActorLogging {
  val actorName: String

  /**
    * Simple method to be overridden when necessary
    * @param entity the response entity
    */
  protected def handleHttpOK(entity: ResponseEntity): Unit = log.info(s"$actorName got unhandled OK response")

  // For http response handling
  final implicit val materializer: ActorMaterializer = ActorMaterializer(ActorMaterializerSettings(context.system))

  override def preStart(): Unit = log.info(s"$actorName started")

  override def postStop(): Unit = log.info(s"$actorName stopped")

  // Add http responses match if necessary
  addReceiver {
    case HttpResponse(StatusCodes.OK, _, entity, _) => handleHttpOK(entity)

    case resp @ HttpResponse(code, _, _, _) =>
      log.error(s"$actorName got response code: {}", code)
      // Discard the flow to avoid backpressure
      resp.discardEntityBytes()

    case e: Status.Failure => log.error(s"$actorName got failure: {}", e.cause.getMessage)

    case _ => log.warning(s"Unexpected message in $actorName")
  }
}

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

class TLRVJob1() extends Mcma {
  override val actorName: String = "TLRVJob1Actor"

  addReceiver {
    case t: Get => ???
  }
}

person Bill'o    schedule 15.02.2018    source источник


Ответы (2)


Я бы предложил изменить начальную черту Receiving:

trait Receiving extends Actor {
  var receivers: Receive = Actor.emptyBehavior
  def addReceiver(next: Actor.Receive): Unit = {receivers = receiver(next)}
  def receiver(next: Actor.Receive): Receive = {receivers orElse next}

  def receive: Receive = receivers
}

Теперь у вас есть два случая: добавить постоянную часть получения и иметь зависимую от класса часть для становления. Измените receiver на addReceiver в иерархии и инициализируйте свой класс Reseau: вместо

receiver(
  active("")
)

переместите это в preStart в форме:

override def preStart(): Unit = {
  super.preStart()
  context become receiver(active(""))
}

и измените тело метода active на

context become receiver(active(s))
person Evgeny    schedule 16.02.2018

Вероятно, самое простое решение в этом случае — отказаться от использования become. То есть вместо кодирования состояния queryDataStr в качестве параметра измените queryDataStr на var:

class Reseau(url: String, optSender: Option[ActorRef]) extends Mcma with Receiving {
  override val actorName: String = "ReseauActor"

  var queryDataStr: Option[String] = None

  receiver {
    case s: String => queryDataStr = Option(s)
  }
}
person Jeffrey Chung    schedule 15.02.2018
comment
Да, действительно, я думал об этом решении, но предпочел попытаться сохранить подход к изменению функционального состояния. - person Bill'o; 16.02.2018