问题描述
当一个对象和针对该对象的状态更新通过两个独立 Queue 传递时,两条路径可能因延迟或反压不同而错过交互。
即使每个 Queue 内部保持 FIFO,也无法保证两个 Queue 之间的全局顺序:
Object(X) ── Queue A ──┐
├── Consumer
Update(X) ── Queue B ──┘
可能出现:
发送顺序:Object(X) → Update(X)
到达顺序:Update(X) → Object(X)
如果 Consumer 只将 Update(X) 应用于已经驻留的对象,并且不保存更新状态,那么稍后到达的 Object(X) 将永久错过
该更新。
DavinciOO 示例
DavinciOO 中,ReadyTable 通过两条独立路径向 IssueQueue 发送指令和 wakeup:
ReadyTable ── ready_to_issue_q ──> IssueQueue
│
└────── issue_wakeup_q ─────> IssueQueue
可能发生以下时序:
- 指令 I 进入 ready_to_issue_q。
- IssueQueue 已满,I 因反压滞留在 Queue 中。
- I 等待的 tag 完成,ReadyTable 发出 wakeup。
- wakeup 经 issue_wakeup_q 先到达 IssueQueue。
- IssueQueue 只 wakeup 内部已经驻留的 entries,此时其中没有 I。
- I 随后才从 ready_to_issue_q 进入 IssueQueue。
如果没有额外机制,I 将错过 wakeup,并一直保持 not-ready。
当前实现
当前 DavinciOO 不会真正丢失该 wakeup,因为:
- ReadyTable 将完成 tag 持久记录在 ready_tags_ 中;
- 指令进入 IssueQueue 时调用 RefreshSourceReady();
- IssueQueue 重新查询 ReadyTable,而不是只依赖之前经过的 wakeup 消息。
因此当前协议实际是:
IQ resident 指令:通过 wakeup 消息更新
中间 Queue 中的指令:进入 IQ 时重新查询 ReadyTable
设计要求
对于通过独立 Queue 传递的关联对象和更新,系统必须至少提供一种保证:
- 持久保存最新状态,并在对象到达时重新查询;
- 缓存尚未匹配的更新事件;
- 将关联消息放入同一个有序通道;
- 使用版本号或 epoch 识别新旧状态。
否则,反压或路径延迟可能导致 lost wakeup、lost invalidation 或类似的状态同步错误。
问题描述
当一个对象和针对该对象的状态更新通过两个独立 Queue 传递时,两条路径可能因延迟或反压不同而错过交互。
即使每个 Queue 内部保持 FIFO,也无法保证两个 Queue 之间的全局顺序:
Object(X) ── Queue A ──┐
├── Consumer
Update(X) ── Queue B ──┘
可能出现:
发送顺序:Object(X) → Update(X)
到达顺序:Update(X) → Object(X)
如果 Consumer 只将 Update(X) 应用于已经驻留的对象,并且不保存更新状态,那么稍后到达的 Object(X) 将永久错过
该更新。
DavinciOO 示例
DavinciOO 中,ReadyTable 通过两条独立路径向 IssueQueue 发送指令和 wakeup:
ReadyTable ── ready_to_issue_q ──> IssueQueue
│
└────── issue_wakeup_q ─────> IssueQueue
可能发生以下时序:
如果没有额外机制,I 将错过 wakeup,并一直保持 not-ready。
当前实现
当前 DavinciOO 不会真正丢失该 wakeup,因为:
因此当前协议实际是:
IQ resident 指令:通过 wakeup 消息更新
中间 Queue 中的指令:进入 IQ 时重新查询 ReadyTable
设计要求
对于通过独立 Queue 传递的关联对象和更新,系统必须至少提供一种保证:
否则,反压或路径延迟可能导致 lost wakeup、lost invalidation 或类似的状态同步错误。