#async #future #non-blocking #wasm-bindgen

troupe

用于用演员模型建模 Rust 应用的库

1 个不稳定版本

0.1.0 2023年11月18日

#1107 in 异步

LGPL-2.1

64KB
874

Crates.io Documentation GitHub Workflows Coverage Status Maintenance

关于

Troupe 是一个实验性的高级库,用于使用演员模型来建模您的应用程序。演员模型通过隔离组件和消息传递来并发地建模可变状态。这种方法可以使您在程序的不同组件之间有清晰的职责分离,并明确地建模组件如何变化。当以下条件成立时,演员方法效果最佳:

  • 您的应用程序中有多个独立的变更源
  • 您想对系统的一部分或全部进行数据流建模
  • 您想构建由一系列可以独立演化的不同组件组成的系统

Troupe 是在构建全栈 Rust 应用时诞生的。在那里,前端客户端进行身份验证,打开 WebSocket 连接,从服务器获取数据,并向用户呈现 UI。在单线程环境 WASM 中,很难管理所有的变更源:UI 交互、服务器更新等。在那里,可以使用演员来管理 WebSocket 连接,处理服务器数据,并将其传播到应用程序的其余部分(包括非演员部分如 UI)。然后 UI 可以与演员通信以呈现 UI 和处理交互。在服务器端,可以使用演员来管理 WebSocket、缓冲数据库和缓存之间的通信,以及跟踪用户会话信息的变化。

为了实现隔离,Troupe 中的演员被包含在现有的异步运行时中的异步进程内。因此,Troupe 能够支持多个异步运行时,包括 tokioasync-stdwasm-bindgen-futures(用于 WASM 目标),这使得您可以逐渐将其集成到项目的架构中。请注意,无论异步运行时如何,演员通信都使用 tokio 的通道。

模型

troupe 演员的中心是一个状态类型和一个消息类型。让我们以一个简单的缓存为例

pub struct Cache(HashMap<Uuid, YourData>);

pub enum CacheCommand {
    Insert(Uuid, YourData),
    Get(Uuid, OneshotSender<Option<YourData>>),
    Delete(Uuid),
}

消息类型封装了状态如何变化以及演员需要处理的数据。这些消息从各种来源发送以供状态处理。为了完成演员,Cache 只需要实现 ActorState 特性。

impl ActorState for Cache {
    type Permanence = Permanent;
    type ActorType = SinkActor;

    type Message = CacheCommand;
    type Output = ();

    async fn process(&mut self, _: &mut Scheduler<Self>, msg: CacheCommand) {
        match msg {
            CacheCommand::Insert(key, val) => {
                self.inner.insert(key, val);
            }
            CacheCommand::Get(key, send) => {
                let _ = send.send(self.inner.get(&key).cloned());
						}
            CacheCommand::Delete(key) => {
                self.inner.remove(&key);
            }
        }
    }
}

ActorState 除了消息类型外,还关联了几个其他类型。这些类型大多是标记类型,用于指示程序的其他部分如何与actor交互。这种通信是通过客户端完成的,该客户端在actor启动时创建。关联的 ActorType 类型告诉客户端actor是否期望接收消息或从actor广播消息(或两者都是)。关联的 Permanence 类型告诉客户端是否应该期望actor在任何时候关闭。最后,关联的 Output 类型仅用于广播消息的actor,在这种情况下,actor将广播 Output 类型的消息。

运行后,troupe 将每个actor状态与一个调度器配对。调度器负责管理状态队列和附加的消息流中的未来。队列中的未来将在调度器等待传入消息的同时被轮询。大多数actor默认有一个附加流,即客户端和actor之间通信的通道。客户端消息流使用tokio MPSC风格的通道,但actor可以添加任何可以转换为actor消息类型的消息流(如套接字连接)。从概念上讲,调度器-actor关系可以建模为

 _____________________________________________
|                    Actor                    |
|  ___________                   ___________  |
| | Scheduler | --- message --> |   State   | |
| | [streams] | <-- streams --- |           | |
| | [futures] | <-- futures --- |           | |
| |___________|                 |___________| |
|_____________________________________________|

如前所述,每个actor都返回一个客户端。每个actor定义了其客户端的行为。有三种类型的客户端:SinkClientStreamClientJointClient

SinkClient 是您最可能使用的客户端类型。它允许您直接向actor发送消息。这些消息可以是“发送后即忘”或“请求-响应”风格的。请注意,SinkClient 并没有实际实现 Sink 特性,而是实现了相同的概念性目的。一个 StreamClient 监听来自actor的广播消息。与 SinkClient 不同,StreamClient 并不支持将任何类型的消息传递给actor。然而,它实现了 Stream 特性。最后,JointClientSinkClientStreamClient 的组合。一个 JointClient 可以分解为一个或两个其他客户端。请注意,actor类型定义了可以构建的客户端类型,因此您不能为永远不会广播消息的actor构建 StreamClient

troupe actor模型最后的重大组件是持久性。一些actor被设计为永远运行。这些被称为 Permanent actor。其他actor被设计为运行一段时间(可能很长)然后关闭。这些被称为 Transient actor。这种区别在很大程度上是为了帮助与actor交互的便利性。如果一个actor被设计成永远不会关闭,那么用于请求-响应风格消息的单次使用通道可以安全地展开。对于 Transient actor,则不成立。

无论actor的持久性如何,所有actor都可能耗尽其消息来源。这发生在所有流都干涸,没有队列中包含任何消息生成future时。当发生这种情况时,troupe actor有内置的“垃圾收集”。调度器将actor标记为“死亡”,然后关闭actor进程,如果它达到这种状态。对于 SinkActors,这只能发生在所有消息发送客户端都已断开连接的情况下。

向后兼容性

“Troupe”目前处于实验阶段,可能存在潜在的破坏性变更(经过适当考虑)。破坏性变更可能发生以提高API的易用性、微调actor模型或使用新的Rust语言特性。特别是,有几个语言特性将在稳定后用于改进此crate。

前两个特性的稳定将确保此crate存在破坏性变更,但将极大地提高可用性和易用性。专业化将使 ActorBuilder 能够提供同名方法以启动actor,同时返回适当的客户端类型。在特性中稳定 async fn 将允许在WASM上下文中放宽对 ActorState 的约束,允许它们只是 'static,而不是 'static + Send

未来工作

目前,“troupe”仅提供构建actor的框架。如果有某些模式很常见,该模式的通用版本可能会进入此crate或类似的crate。一个例子是类似于本地首次缓冲状态的某些内容。在这里,你有一些数据存在于两个位置,例如客户端和服务器,你想要在其中一个位置更新状态,然后将其更改传播到其他地方。

在“轮询”actor中,另一种可能的actor模式。这将基于广播actor,但广播消息将包含用于与actor通信“投票”的单次通道。

使用和许可

本项目目前使用LGPL-2.1许可。这允许您使用此crate以任何许可(开源或其他)构建其他crate(库或应用程序)。使用此许可的主要目的是要求修改此crate源码的crate必须是开源许可(LGPL或更强)。

贡献

如果您希望看到项目的某个方面发生变化、改进甚至删除,请打开一个工单或PR。如果您有一个使用actor的有趣设计模式,并希望看到它在这个crate中,请打开一个工单!!

依赖关系

~4–19MB
~213K SLoC