ek-event 0.1.2

Self-contained event engine in Rust, a faithful semantic replication of enkeep_trade's event engine. No dependency on enkeep_trade.
Documentation
# ek-event 设计文档

> Rust 复刻 enkeep_trade 的事件引擎(`enkeep_trade/event/engine.py`,148 行,自包含)。
> 本 crate **不依赖 enkeep_trade 任何模块**;业务事件常量、Gateway 双投递、OmsEngine 等生态层由使用方自行在 ek-event 之上实现。

## 1. 范围

| 纳入 | 说明 |
| --- | --- |
| `Event{event_type, data}` | 原版 `Event(type, data)` |
| `EventEngine` | start/stop、put、register/unregister、register_general/unregister_general、is_active |
| `EVENT_TIMER` 定时事件 | 独立线程,默认每 1s put 一个 `"eTimer"` 事件 |
| 分发语义 | 精确匹配:该 type 的 handler 列表先执行,general handler 后执行(注册顺序) |
| handler 注册 | 原版同 handler 同 type 只注册一次(函数相等去重);Rust 侧去重责任在上层(闭包不可比较),引擎保证每次注册产出全局唯一 `HandlerId`,凭 id 可精确注销 |
| stop 语义 | 置 inactive 并 join 两线程;**队列中未处理的事件被丢弃**(对齐原版) |
| put-on-stopped | 报 `Err(PutError::Stopped)`(原版静默丢,Rust 显式化) |

不在范围内:`trader/event.py` 的 ~90 个业务事件常量、BaseGateway 双投递、BaseTradeEngine 过滤器、OmsEngine、UI 桥接、日志引擎。

## 2. 对原版的三个必要修正

1. **handler 表加锁**:原版 `register/unregister` 在任意线程改 `defaultdict`,无锁,靠 GIL 兜底且非完全安全。Rust 用 `RwLock<Handlers>`。
2. **panic 隔离**:原版 handler 抛异常会杀死分发线程(引擎永停)。Rust 分发端用 `catch_unwind` 包住单 handler,panic 只记 `log::warn!`,后续 handler 与后续事件继续。
3. **HandlerId 注册**:函数对象不可比较(原版 `if handler not in list` 依赖对象相等),Rust 注册即分配自增 `HandlerId(u64)`,unregister 按 id。原版"同 handler 重复注册无副作用"的语义等效实现。若需要严格等效"同一闭包体的去重",由上层调用方维护 id 再注册即可(框架不管)。

## 3. 队列选型(结论:P1 用 crossbeam-channel)

| 维度 | std::sync::mpsc | crossbeam-channel(采用) | tokio |
| --- | --- | --- | --- |
| 依赖 | 零 | 1 个轻量三方(0.5.17) | runtime 全家桶 |
| 热路径 | mutex+condvar | MPMC 无锁快路径(单字 CAS) | 需 async runtime |
| 无界队列 | 支持 | `unbounded()` 对应原版 `Queue()` 无界语义 | broadcast 环形有界 |
| 多 worker 扩展 | Receiver 不可克隆 | Receiver 可克隆,tick 热点分流免换型 | broadcast 多消费者破坏串行语义 |
| stop 后 put | SendError→`PutError::Stopped` | 同样映射,`TrySendError` 粒度更细 | 需 runtime |
| 结论 | 零依赖备选(接口不变可随时替换) | **采用**:性能+扩展空间+语义贴合的最小代价 | 仅当选 feature 追加 AsyncEventEngine |

Rust 工具链标准: **Edition 2024,`rust-version = "1.85"`**(2025-02 起稳定)。

tokio 追加形态(暂不做):`feature = "tokio"` 下加 `AsyncEventEngine`(tokio mpsc 单消费 task + `tokio::time::interval`),与本同步引擎并存,不共用代码。

## 4. 载荷类型(已定:参考方案①)

`pub type EventData = Arc<dyn Any + Send + Sync>;` —— ek-event 零业务知识,使用方传任意可序列化/可克隆结构。遍历热点可自行封装 `Vec<TickData>` 批量事件(原版 `_process_tick_event` 支持 list,那是生态层职责)。

## 5. 模块与 API 骨架

```
ek-event/
├── Cargo.toml
├── DESIGN.md           ← 本文档
└── src/
    ├── lib.rs          ← 模块出口 + EVENT_TIMER 常量 + 集成测试
    ├── event.rs        ← Event 结构体
    ├── handler.rs      ← HandlerId + HandlerTable(RwLock<精确表 + general 表>)
    └── engine.rs       ← EventEngine:队列/分发线程/定时线程/stop
```

API 签名见 `src/*.rs` 中的 doc 注释:
- `EventEngine::new()` / `with_interval(Duration)`(默认 1s)
- **`Send + Sync`**:整个引擎可经 `Arc<EventEngine>` 跨线程共享(对齐 enkeep_trade 各组件共享同一引擎的用法);`start()/stop()` 均为 `&self`(内部可变性),可从任意线程调用
- `start()` 幂等;`stop(&self)` 幂等,join 两线程;stop 后引擎不可重启(对齐原版线程已死不可恢复)
- `put()`:`Result<(), PutError>` —— start 前投递合法(start 后处理,对齐 `Queue.put`);stop 后返回 `PutError::Stopped`
- `register(impl Into<String>, handler) -> HandlerId`;`unregister(HandlerId) -> bool`
- `register_general / unregister_general` 同构

## 6. 测试清单

| 编号 | 用例 | 状态 |
| --- | --- | --- |
| T1 | HandlerId 语义:每次注册产出全局唯一 id(防重复由上层负责);两次注册各得不同 id、double-unregister 返回 false | ✅ 已实现(lib.rs) |
| T2 | unregister 后空列表自动从表中移除(对齐原版 `pop(type)`);空表回收后重新注册可正常重建列表 | ✅ 已实现(handler.rs) |
| T3 | 一个事件先按 type 顺序分发给该 type handler,再分给 general handler | ✅ 已实现(lib.rs) |
| T4 | 定时事件:启动后收到周期性 `EVENT_TIMER` | ✅ 已实现(lib.rs) |
| T5 | handler panic:同 type 后续 handler、后续事件均仍被处理 | ✅ 已实现(lib.rs) |
| T6 | stop 后 put → `Err(PutError::Stopped)`;二次 stop 幂等;pending 清零 | ✅ 已实现(lib.rs) |
| T7 | 多线程并发:Arc 共享 + 4 线程×50 并发 put + `Send+Sync` 编译期断言(`loom` 模型检验后验) | ✅ 轻量版已实现(lib.rs) |
| T8 | 顺序性:同一 type 事件按 put 顺序送达(单分发线程) | ✅ lib.rs |

### 生态使用模式集成测试(`tests/` 目录,方案与结果详见 TESTPLAN.md)

| 文件 | 覆盖 | 状态 |
| --- | --- | --- |
| usage_gateway.rs | 聚合+逐品种/逐订单双投、注销停发、先 start 后注册不回放 | ✅ 3 用例 |
| usage_shared.rs | 四组件共享守恒、并发注册风暴、双实例隔离 | ✅ 3 用例 |
| usage_lifecycle.rs | UI open/close/open、general 重启、handler 自注销不死锁 | ✅ 3 用例 |
| usage_rpc.rs | 跨 type 保序转发+过滤/空载荷跳过、type→general 同达 | ✅ 2 用例 |
| usage_timer.rs | 计数节流、timer 与业务交织保序 | ✅ 2 用例 |
| usage_sentinel.rs | 成对事件流水线、无载荷/带载荷哨兵 | ✅ 2 用例 |
| usage_batch.rs | 500 条批量 list 全收保序 | ✅ 1 用例 |
| usage_stress.rs | 10 万双投保序、8 线程 40 万洪峰不丢不重 | ✅ 2 用例 |

全组通过:`cargo test` EXIT 0 + `cargo clippy --all-targets -- -D warnings` EXIT 0 + 10 连跑零失败(TESTPLAN.md §5 有发现与修正记录)。

## 7. 基准(criterion,P1 交付)

- `put + 分发空 handler`:期望 > 100 万事件/秒
- `put + 分发任一业务大小结构体 Arc 克隆`:目标同量级
- 定时器精度:`interval=1s` 下相邻 TIMER 事件时间差分布(P95 ≤ interval*1.05)

## 8. 与原版的已知行为差异汇总

| 原版 | ek-event | 备注 |
| --- | --- | --- |
| stop 后 put 静默丢 | 显式 `Err(PutError::Stopped)` | Rust 显式化,调用方可忽略 |
| register 无锁(GIL) | `RwLock` | 修正 |
| handler 异常杀线程 | catch_unwind + 日志 | 修正 |
| handler 相等去重 | HandlerId 去重 | 上层管 id 即可 |
| `interval: int` 秒 | `Duration` | 更通用 |
| 引擎对象由 MainEngine 单点持有 | `Send + Sync`,可 `Arc` 多线程共享 | 能力增强,`start/stop` 为 `&self` |