Search code examples
akkaakka-actor

akka actor current receive function name


i have a simple actor with 2 behavior

package com.hello

import akka.actor.{Actor, ActorLogging}

case object Ping

class Hello extends Actor with ActorLogging {

  import context._

  def receive: Receive = behaviorFoo

  self ! Ping

  def behaviorFoo: Receive = {
    case _ =>
      log.info(this.receive.getClass.getSimpleName)
      become(behaviorBar)
      self ! Ping
  }

  def behaviorBar: Receive = {
    case _ =>
      log.info(this.receive.getClass.getSimpleName)
  }
}

this thing do those:

ping self

logging current receive function

change behavior to behaviorBar

ping self

logging current receive function

in both cases it logs "$anonfun$behaviorFoo$1"

why it is not "$anonfun$behaviorBar$1" in second log?

if i change code to

  self ! Ping

  def receive: Receive = {
    case _ =>
      log.info(this.receive.getClass.getSimpleName)
      become(behaviorFoo)
      self ! Ping
  }

  def behaviorFoo: Receive = {
    case _ =>
      log.info(this.receive.getClass.getSimpleName)
      become(behaviorBar)
      self ! Ping
  }

  def behaviorBar: Receive = {
    case _ =>
      log.info(this.receive.getClass.getSimpleName)
  }

it logs 3 times "$anonfun$receive$1"

is exist any way to get current behavior (Receive) function name? or i need to hardwrite like log.info("behaviorFoo") any time?

update:

for logging issues, i added

trait MyLogging extends ActorLogging {
  this: Actor ⇒

  private[this] val actorClassName = this.getClass.getSimpleName

  private[this] var receiveName: String = {
    val receiveClassName = s"${this.receive.getClass.getSimpleName}"
    val left = receiveClassName.substring(0, receiveClassName.lastIndexOf("$"))
    left.substring(left.lastIndexOf("$") + 1)
  }

  def become(behavior: Actor.Receive): Unit = {
    val behaviorClassName = behavior.getClass.getSimpleName
    val left = behaviorClassName.substring(0, behaviorClassName.lastIndexOf("$"))

    receiveName = left.substring(left.lastIndexOf("$") + 1)

    context.become(behavior)
  }

  def info(message: Any): Unit = log.info(s"$actorClassName : $receiveName got $message")
}

then, my actor code become

class Hello extends Actor with MyLogging {

  def receive: Receive = behaviorFoo

  self ! Ping

  def behaviorFoo: Receive = {
    case any =>
      info(any)
      become(behaviorBar)
      self ! Ping
  }

  def behaviorBar: Receive = {
    case any => info(any)
  }
}

now logs looks like

... Hello : behaviorFoo got Ping

... Hello : behaviorBar got Ping


Solution

  • Maybe this is why those are called anonymous functions. The behaviorFoo, behaviorBar, ... names have no influence on those anonymous functions. To illustrate this point, consider this:

    // this is a val!
    val behaviorFoo: Receive = {
      case _ =>
        log.info(this.receive.getClass.getSimpleName)
        become(behaviorBar)
        self ! Ping
    }
    // this returns the same anonymous function
    def behaviorFoo2 = behaviorFoo
    

    With the above, you should see that the name under which you store the anonymous function has nothing to do with the anonymous function itself...

    Now, if you realize what those anonymous functions are (they are partial functions, aliased with type Actor.Receive) you could do something like below:

    // not an anonymous function anymore
    class BehaviourFoo extends Actor.Receive {
      val anonymousFun: Actor.Receive = {
        case _ =>
          log.info(this.receive.getClass.getSimpleName)
          become(behaviorBar)
          self ! Ping
      }
      override def isDefinedAt(x: Any) = anonymousFun.isDefinedAt(x)
    
      override def apply(x: Any) = anonymousFun(x)
    }
    // again, the name behaviorFoo doesn't matter
    def behaviorFoo: Receive = new BehaviourFoo
    

    It is certainly NOT worth the hassle, but it should help you understand what is happening.