最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

scala - Why is actor becomeunbecome not working as expected? - Stack Overflow

programmeradmin0浏览0评论

Background:

I've simplified my web client actor to test out become and unbecome behaviour.

Here is the simplified actor:

class TestClient extends TimerActor {

  implicit val system: ActorSystem = context.system

  private val ONLINE:AtomicBoolean = AtomicBoolean(false)
  private val HTTP_TOKEN:AtomicBoolean = AtomicBoolean(false)

  timers.startSingleTimer(TickKey, FirstTick, FiniteDuration(1, TimeUnit.SECONDS))

  override def receive: Receive = {
    case FirstTick =>
      timers.startTimerWithFixedDelay(TickKey, Tick, FiniteDuration(1, TimeUnit.SECONDS))
    case Tick =>
      system.log.info("becoming pingService")
      context.become(pingService)
      if (ONLINE.get()) {
        system.log.info("becoming httpTokenFetcher")
        context.become(httpTokenFetcher)
        if (HTTP_TOKEN.get()) {
          system.log.info("HTTP token set.")
        }
      }
  }

  private def httpTokenFetcher: Receive = {
    case Tick =>
      system.log.info("httpTokenFetcher becoming pingService")
      context.become(pingService, false) // always do a ping first
      
      if (ONLINE.get()) {
        system.log.info("Fetching http token")
        HTTP_TOKEN.set(true)
        context.unbecome()
      }
  }

  private def pingService: Receive = {
    case Tick =>
      system.log.info("Doing ping")
      ONLINE.set(true)
      context.unbecome()
  }

}

Expected result:

What I expect to see is that when the httpTokenFetcher behaviour is the actors behaviour, it "becomes" a pingService first, performs a ping (prints "doing ping"), then puts the behaviour back to httpPingService to continue with fetching the token.

So I expect the output:

becoming pingService
Doing ping
becoming pingService
Doing ping                  <- this time, ONLINE is true, so become httpTokenFetcher
becoming httpTokenFetcher
httpTokenFetcher becoming pingService
Doing ping
Fetching http token
httpTokenFetcher becoming pingService
Doing ping
Fetching http token
HTTP token set.             <- This print doesn't even hit
...

Actual results:

becoming pingService
Doing ping
becoming pingService
becoming httpTokenFetcher
httpTokenFetcher becoming pingService
Fetching http token
httpTokenFetcher becoming pingService
Fetching http token
httpTokenFetcher becoming pingService
...

What I Tried:

In the httpTokenFetcher receive, I tried using context.become(pingService, false) vs context.become(pingService)

With context.become(pingService) I get the output:

becoming pingService
Doing ping
becoming pingService
becoming httpTokenFetcher
httpTokenFetcher becoming pingService
Fetching http token
becoming pingService
becoming httpTokenFetcher
HTTP token set.             <- actually completed

But still does not print "Doing ping" where I expect it to.

Any suggestions?

Background:

I've simplified my web client actor to test out become and unbecome behaviour.

Here is the simplified actor:

class TestClient extends TimerActor {

  implicit val system: ActorSystem = context.system

  private val ONLINE:AtomicBoolean = AtomicBoolean(false)
  private val HTTP_TOKEN:AtomicBoolean = AtomicBoolean(false)

  timers.startSingleTimer(TickKey, FirstTick, FiniteDuration(1, TimeUnit.SECONDS))

  override def receive: Receive = {
    case FirstTick =>
      timers.startTimerWithFixedDelay(TickKey, Tick, FiniteDuration(1, TimeUnit.SECONDS))
    case Tick =>
      system.log.info("becoming pingService")
      context.become(pingService)
      if (ONLINE.get()) {
        system.log.info("becoming httpTokenFetcher")
        context.become(httpTokenFetcher)
        if (HTTP_TOKEN.get()) {
          system.log.info("HTTP token set.")
        }
      }
  }

  private def httpTokenFetcher: Receive = {
    case Tick =>
      system.log.info("httpTokenFetcher becoming pingService")
      context.become(pingService, false) // always do a ping first
      
      if (ONLINE.get()) {
        system.log.info("Fetching http token")
        HTTP_TOKEN.set(true)
        context.unbecome()
      }
  }

  private def pingService: Receive = {
    case Tick =>
      system.log.info("Doing ping")
      ONLINE.set(true)
      context.unbecome()
  }

}

Expected result:

What I expect to see is that when the httpTokenFetcher behaviour is the actors behaviour, it "becomes" a pingService first, performs a ping (prints "doing ping"), then puts the behaviour back to httpPingService to continue with fetching the token.

So I expect the output:

becoming pingService
Doing ping
becoming pingService
Doing ping                  <- this time, ONLINE is true, so become httpTokenFetcher
becoming httpTokenFetcher
httpTokenFetcher becoming pingService
Doing ping
Fetching http token
httpTokenFetcher becoming pingService
Doing ping
Fetching http token
HTTP token set.             <- This print doesn't even hit
...

Actual results:

becoming pingService
Doing ping
becoming pingService
becoming httpTokenFetcher
httpTokenFetcher becoming pingService
Fetching http token
httpTokenFetcher becoming pingService
Fetching http token
httpTokenFetcher becoming pingService
...

What I Tried:

In the httpTokenFetcher receive, I tried using context.become(pingService, false) vs context.become(pingService)

With context.become(pingService) I get the output:

becoming pingService
Doing ping
becoming pingService
becoming httpTokenFetcher
httpTokenFetcher becoming pingService
Fetching http token
becoming pingService
becoming httpTokenFetcher
HTTP token set.             <- actually completed

But still does not print "Doing ping" where I expect it to.

Any suggestions?

Share Improve this question edited Feb 3 at 14:07 Kris Rice asked Feb 3 at 14:01 Kris RiceKris Rice 1
Add a comment  | 

2 Answers 2

Reset to default 2

I played around some more and determined I can pass the "next behaviour" to the pingService, which calls become if the ping service was successful. This gives the desired result.

New TestClient class:

class TestClient extends TimerActor {

  implicit val system: ActorSystem = context.system

  private val ONLINE: AtomicBoolean = AtomicBoolean(false)
  private val HTTP_TOKEN: AtomicBoolean = AtomicBoolean(false)

  timers.startSingleTimer(TickKey, FirstTick, FiniteDuration(1, TimeUnit.SECONDS))

  override def receive: Receive = {
    case FirstTick =>
      timers.startTimerWithFixedDelay(TickKey, Tick, FiniteDuration(1, TimeUnit.SECONDS))
    case Tick =>
      val nextBehaviour: Receive = if (HTTP_TOKEN.get()) null else httpTokenFetcher

      context.become(pingService(nextBehaviour))
  }

  private def httpTokenFetcher: Receive = {
    case Tick =>
      system.log.info("Fetching http token")
      HTTP_TOKEN.set(true)
      system.log.info("HTTP Token Set")
      context.unbecome()
  }

  private def pingService(nextBehaviour: Receive = null): Receive = {
    case Tick =>
      system.log.info("Doing ping")
      ONLINE.set(true)

      if (nextBehaviour != null) {
        if (ONLINE.get()) {
          context.become(nextBehaviour)
        }
      }
  }

}

I now get the output:

Doing ping
Fetching http token
HTTP Token Set
Doing ping
Doing ping
Doing ping
Doing ping
...

This will allow me to expand the behaviour too, setting different behaviours to pass to pingService to perform if the server is still online.

Edit updated answer after checking new comment:

Your own answer provides a solution approach that I'd recommend as well. Instead of wrapping one's head around context.unbecome() and context.become(myBehavior, false), one should rather not use those and just use context.become(myBehavior) without stacking old behaviors. The resulting code will usually be much easier to understand.

Nevertheless, I will try to answer what went wrong with your usage of context.unbecome(), i.e. why your code seems to keep stuck in httpTokenFetcher forever:

When you call context.become(pingService, false), you set pingService as the new behavior and place the current one (httpTokenFetcher) onto the stack of previous behaviors - from then on, pingService is the "current behavior". Then, a few lines below, you call context.unbecome(). This will discard the current behavior (which is pingService) and set the most recent one from the stack (httpTokenFetcher) as the new "current behavior":

  private def httpTokenFetcher: Receive = {
    case Tick =>
      system.log.info("httpTokenFetcher becoming pingService")
      context.become(pingService, false) // context becomes pingService
      
      if (ONLINE.get()) { // is true
        system.log.info("Fetching http token")
        HTTP_TOKEN.set(true)
        context.unbecome() // context becomes httpTokenFetcher again
      }
  }

So you effectively remain in the httpTokenFetcher (forever).

Initial answer, left here to keep the context of the comments:

To me, it looks like you expect that if you call context.become(myReceive) from the current behavior, myReceive is executed immediately (called with the message that is currently being handled). That expectation is wrong. context.become(myReceive) only sets the behavior for handling the next message without executing myReceive immediately.

So for example, in your original TestClient, the pingService will be executed the first time when the second Tick is received.

发布评论

评论列表(0)

  1. 暂无评论