Akka 2.6下可注入Actor生成的通用抽象设计及报错问题咨询
核心结论
定义ActorSpawner抽象本身完全合理,不属于反模式。这一抽象方向和Akka Typed官方内置的akka.actor.typed.ActorSpawner几乎完全一致,核心目的就是把“创建Actor的能力”和“持有Actor上下文/系统的环境”解耦,让业务类不需要感知底层是依托ActorContext还是ActorSystem完成spawn操作。和自定义的接口定义对比如下:
trait ActorSpawner { def spawn[T](behavior: Behavior[T]): ActorRef[T] def spawn[T](behavior: Behavior[T], name: String): ActorRef[T] }
触发的报错不是接口设计的问题,是2.6版本对ActorContext的访问规则收紧后,旧实现方式触发的边界校验。
升级到Akka 2.6后触发报错的原因
报错本质是Akka从2.6版本开始,对ActorContext的访问增加了强制线程上下文校验,触发的典型报错信息如下:
ERROR akka.actor.SupervisorStrategy - Unsupported access to ActorContext from the outside of Actor[akka://...]. No message is currently processed by the actor, but ActorContext was called from Thread[...]
规则变化的具体逻辑:
- 从设计初衷上,
ActorContext是单个Actor的私有上下文对象,仅允许在该Actor处理消息的执行线程上、处于消息处理生命周期内调用其spawn、watch等方法。官方不支持将ActorContext的引用透传到Actor边界之外,在其他线程、非消息处理流程中调用上下文方法。 - 2.6版本之前,“将ActorContext作为通用Spawner传给外部类”的写法没有硬校验,可正常运行,但本质是兼容版本留存的规则漏洞。2.6版本堵上这个漏洞后,只要在Actor外部线程上调用ActorContext的spawn方法,就会直接触发上述错误日志。
合法的Spawner实现方式,可完全适配现有自定义接口
不需要修改已定义的ActorSpawner接口,只需要按照spawn的实际场景选择对应合法的实现,即可彻底规避报错:
- 场景1:待创建的Actor是某一父Actor的子节点,生命周期归属于父Actor管理。
- 禁止写法:直接将父Actor的
ActorContext引用透传给外部业务类充当Spawner,这种写法必然触发2.6版本的校验报错。 - 正确写法:在持有ActorContext的父Actor的消息协议中,增加“创建指定Behavior的子Actor”的消息类型。外部业务类将spawn请求以消息形式发送给该父Actor,由父Actor在自身的消息处理线程上调用Context.spawn完成创建,再将生成的
ActorRef回传给外部业务类。可将该逻辑封装为实现ActorSpawner接口的代理类,保证所有spawn操作都在合法的上下文线程上执行。
- 禁止写法:直接将父Actor的
- 场景2:待创建的Actor是系统级顶级Actor,生命周期归属于ActorSystem管理。
- 可直接使用
ActorSystem本身作为ActorSpawner的实现。ActorSystem的spawn方法本身是线程安全的,不绑定单个Actor的私有上下文,无论从哪个外部线程调用都不会触发校验报错,直接注入到业务类中即可满足需求。
- 可直接使用
注意常见踩坑点:不要将经典Actor系统转Typed后得到的上下文、或某个Typed Actor的Context透传出去当Spawner使用,这类上下文依然绑定在具体Actor的私有边界上,同样会触发2.6版本的访问校验。
内容的提问来源于stack exchange,提问作者lex82

