Search code examples
scalaakkaakka-httpakka-actor

Why is actor become/unbecome not working as expected?


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?


Solution

  • 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.