#subsystem #orchestra #pattern #global #partial #orchestrator #actor

orchestra-malus

使orchestra子系统可替换

1个不稳定版本

0.0.0 2022年10月5日

#31#orchestrator

MIT/Apache

2KB

orchestra

orchestra模式是一种部分actor模式,全局orchestrator负责相关工作项。

proc-macro

该proc宏提供了一个使用构建器模式的便利生成器,其核心是创建和启动一组子系统,这些子系统完全是声明性的。

    #[orchestra(signal=SigSigSig, event=Event, gen=AllMessages, error=OrchestraError)]
    pub struct Opera {
        #[subsystem(MsgA, sends: [MsgB])]
        sub_a: AwesomeSubSysA,

        #[cfg(any(feature = "feature1", feature = "feature2"))]
        #[subsystem(MsgB, sends: [MsgA])]
        sub_b: AwesomeSubSysB,
    }
  • 每个子系统都使用 #[subsystem(_)] 进行注解,其中 MsgAMsgB 是该特定子系统消费的消息。这些子系统中的每一个都必须实现具有正确特质的子系统特质。通常,这是通过使用 #[subsystem]#[contextbounds] 宏来实现的。
    • #[contextbounds(Foo, error=Yikes, prefix=wherethetraitsat)] 可应用于 impl 块和 fn 块。它将为泛型 Context 添加额外的特质界限,包括 Context: FooContextTrait 以及 <Context as FooContextTrait>::Sender: FooSenderTrait 等。请注意,这里的 Foo 指的是在 #[orchestra(..)] 宏中声明的子系统名称。
    • #[subsystem(Foo, error=Yikes, prefix=wherethetraitsat)] 是对上述内容的扩展,实现了 trait Subsystem<Context, Yikes>
  • error= 告诉乐队使用用户提供的错误类型,如果没有提供,则使用内置类型。请注意,这是在整个调用中使用的错误类型之一,因此请确保为所有相关的应用错误类型 E 实现了 From<E>
  • event= 声明一个外部事件类型,该类型会将某些事件注入乐队,而不参与子系统模式。
  • signal= 定义一个用于乐队的信号类型。这是所有子系统的共享“tick”或“时钟”。
  • gen= 定义一个封装的 enum 类型,用于封装所有可以被 任何 子系统消费的消息。
  • 可以通过 #[cfg(feature = "feature")] 属性宏表达式来对功能进行功能门控。目前支持 anyallnotfeature
    /// Execution context, always required.
    pub struct DummyCtx;

    /// Task spawner, always required
    /// and must implement `trait orchestra::Spawner`.
    pub struct DummySpawner;

    fn main() {
        let _orchestra = Opera::builder()
            .sub_a(AwesomeSubSysA::default())
            .sub_b(AwesomeSubSysB::default())
            .spawner(DummySpawner)
            .build();
    }

在显示的 main 中,乐队是通过生成、编译时错误生成器模式创建的。

生成器需要在调用 build 方法之前通过相应的设置方法设置所有子系统、行李字段(附加结构数据)和启动器。未能进行此类初始化会导致编译错误。这是通过将每个生成器字段编码为所谓的 状态泛型 来实现的,这意味着每个字段可以是 Init<T>Missing<T>,因此每个设置器将状态从 Missing 转换为特定结构字段的 Init 状态。因此,如果您看到编译时错误,错误信息指向期望 Init 而不是 Missing,通常意味着在调用 build 之前没有设置某些子系统或行李字段。

要排除子系统,可以在尚未准备好包含在 orchestra 中的某些子系统上设置 wip 属性。

    #[orchestra(signal=SigSigSig, event=Event, gen=AllMessages, error=OrchestraError)]
    pub struct Opera {
        #[subsystem(MsgA, sends: MsgB)]
        sub_a: AwesomeSubSysA,

        #[subsystem(MsgB, sends: MsgA), wip]
        sub_b: AwesomeSubSysB, // This subsystem will not be required nor allowed to be set
    }

行李字段可以初始化多次,但对于子系统来说并非如此:子系统必须只初始化一次(另一个编译时检查)或通过特殊设置器(如方法 replace_<subsystem>)来替换。

需要定义一个任务生成器和子系统上下文,分别使用 SpawnerSubsystemContext 实现。

调试

就像往常一样,调试带错误的 proc-macros 是一件非常令人烦恼的事情,请参阅 功能 "expand"

特性

特性 "expand"

expander 用于生成更好的错误信息。通过 --features=orchestra/expand 启用。

特性 "dotgraph"

根据声明的要发送和消费的消息生成一个有向图,显示连接性。通过 --features=orchestra/dotgraph 启用。生成的文件路径将显示出来,格式为 ${OUT_DIR}/${orchestra|lowercase}-subsystem-messaging.dot。使用 dot -Tpng ${OUT_DIR}/${orchestra|lowercase}-subsystem-messaging.dot > connectivity.dot 转换为例如 png 图像或使用您喜欢的 dot 文件查看器。它还在 .dot 图旁边创建了一个 .svg,直接从 .dot 图生成。

注意事项

没有任何工具是没有注意事项的,orchestra 也不例外。

大型消息类型

不建议使用通过通道发送的大型消息,就像在其他通道实现中一样。如果您需要传输大于几个百字节的数据,可以使用 Box<_> 来包装它,或者根据使用情况使用全局标识符来访问持久状态,如数据库。

响应通道

将响应通道作为消息的一部分似乎非常有吸引力,并且在许多情况下,这些都是维护结构化数据流的非常方便的方式,但如果不谨慎使用,它们也会给你带来麻烦。所需的小心包括三个方面

  1. 循环消息依赖导致单线程子系统死锁
  2. 许多 子系统太深的消息依赖
  3. 由于响应通道导致的延迟

每个问题都有多种解决方案,例如使用本地缓存来修复频繁查找相同信息的操作,或将子系统分割成多个以避免循环依赖,或在某些异常情况下将拓扑上与一个子系统紧密相关的微小部分合并,但具体取决于 orchestra 使用的具体上下文。

要找到这些问题,特性 dotgraph 提供了子系统间所有交互的可视化(目前还不是在消息级别),以研究循环。在生成阶段要注意警告。

许可

许可协议为以下之一

根据您的选择。

无运行时依赖