Akkatype with classic actors giving me an error: Unsupported access to ActorContext from outside of Actor

I am getting the following runtime error in my akka application (using both akka typed and classic)

java.lang.UnsupportedOperationException: Unsupported access to ActorContext from the outside of Actor[akka://my-classic-actor-system/user/ChatServer#1583147696]. No message is currently processed by the actor, but ActorContext was called from Thread[my-classic-actor-system-akka.actor.default-dispatcher-5,5,run-main-group-0].

def main(args: Array[String]): Unit = {
    val logger = LoggerFactory.getLogger(getClass)
    logger.debug("Server starting yo...")

    implicit val system = akka.actor.ActorSystem("my-classic-actor-system")

    implicit val typedGuardian: ActorRef[Any] =
      system.spawn(HttpWebsocketServer(), "ChatServer")



     val client = system.actorOf(Client.props(remote), "clientActor")
    
    //..
}

The source of the error is inside of HttpWebsocketServer which is taken from ( https://github.com/johanandren/chat-with-akka-http-websockets/blob/akka-2.6/src/main/scala/chat/Server.scala#L101):

object HttpWebsocketServer {
    def apply(): Behavior[Any] = {
        Behaviors.setup[Any] { context =>
            implicit val ec: ExecutionContext = context.executionContext

            val system = context.system
            val chatRoom = context.spawn(ChatRoom(), "chat")
            val userParent = context.spawn(SpawnProtocol(), "users")

            val maker =
            context.spawn(websock.Maker(), "mmaker")

             val route =
                path("chat") {
                    get {
                        handleWebSocketMessages(
                        newUserFlow(system, chatRoom, userParent)
                        )
                    }
                }

            // needed until Akka HTTP has a 2.6 only release
            implicit val materializer: Materializer =
                SystemMaterializer(context.system).materializer

            implicit val classicSystem: akka.actor.ActorSystem =
                context.system.toClassic

            Http()
                .bindAndHandle(route, "0.0.0.0", 9000)
                // future callback, be careful not to touch actor state from in here
                .onComplete {
                case Success(binding) =>
                    context.log.debug(
                    s"Started server at ${binding.localAddress.getHostString}:${binding.localAddress.getPort}"
                    )
                case Failure(ex) =>
                    ex.printStackTrace()
                    context.log.error("Server failed to start, terminating")
                    context.system.terminate()
                }

            Behaviors.empty

        }
    
    }
}         

build.sbt

val AkkaVersion = "2.6.18"
val AkkaHttpVersion = "10.2.7"      
libraryDependencies ++= Seq(
    "io.netty" % "netty-all" % "4.1.68.Final",
    "com.typesafe.akka" %% "akka-actor" % AkkaVersion,
    "com.typesafe.akka" %% "akka-actor-typed" % AkkaVersion,
    "com.typesafe.akka" %% "akka-stream" % AkkaVersion,
    "com.typesafe.akka" %% "akka-stream-typed" % AkkaVersion,
    "com.typesafe.akka" %% "akka-http" % AkkaHttpVersion,
    "com.typesafe.akka" %% "akka-http-spray-json" % AkkaHttpVersion,
    "ch.qos.logback" % "logback-classic" % "1.2.10",
    scalaTest % Test
) 

If I comment out below in my code I don't get the runtime error, so it looks like that is the source of the issue:

 implicit val materializer: Materializer =
                SystemMaterializer(context.system).materializer

            implicit val classicSystem: akka.actor.ActorSystem =
                context.system.toClassic

            Http()
                .bindAndHandle(route, "0.0.0.0", 9000)
                // future callback, be careful not to touch actor state from in here
                .onComplete {
                case Success(binding) =>
                    context.log.debug(
                    s"Started server at ${binding.localAddress.getHostString}:${binding.localAddress.getPort}"
                    )
                case Failure(ex) =>
                    ex.printStackTrace()
                    context.log.error("Server failed to start, terminating")
                    context.system.terminate()
                }
 

I'm not sure what I can do to solve this issue, I am creating actors from either the system or child actors.


Solution 1:

You are not allowed to use context outside of an actor. And you do it in callback of your future.

case Success(binding) =>
   context.log.debug(
  s"Started server at ${binding.localAddress.getHostString}:${binding.localAddress.getPort}"
   )
case Failure(ex) =>
   ex.printStackTrace()
   context.log.error("Server failed to start, terminating")
   context.system.terminate()

The problem is that actor context is treated as internal state of an actor and must not be accessed outside of the scope. Even your comment says that

// future callback, be careful not to touch actor state from in here

The solution is to pre-reference your log and system (you have this already) in a val outside of the callback. The callback can look like

case Success(binding) =>
   log.debug(
  s"Started server at ${binding.localAddress.getHostString}:${binding.localAddress.getPort}"
   )
case Failure(ex) =>
   ex.printStackTrace()
   log.error("Server failed to start, terminating")
   system.terminate()