pub struct IdempotencyCache { /* private fields */ }Expand description
订单幂等 cache。线程安全(DashMap + Instant)。
v1.4.106 codex 0920 F1 (P1) namespace fix: cache key 必须 namespace 隔离 跨 (endpoint, caller_key_id, acc_id, op). 否则:
- Cross-pollination: PlaceOrder 后 ModifyOrder 用同一
Idempotency-Key会拿回 PlaceOrder response (严重交易 bug —— 用户以为改完了但实际没发). - Cross-account leak: 不同 caller (key_id A vs B) 用同一 key 会跨账户 串 response (隐私 + 资金安全).
- Cross-acc/env leak: 同 caller 在 acc_id=A 下单后立刻在 acc_id=B 用 同 key 改单, 会拿到 acc_id=A 的 PlaceOrder response.
Namespace 设计: endpoint|caller|acc_id|op|raw_key
- endpoint =
place_order/modify_order/cancel_order/reconfirm_order - caller = caller_key_id (Some) 或
<no_key>(TCP/legacy) - acc_id = c2s.header.acc_id (u64 decimal)
- op =
PLACE/MODIFY=N/CANCEL/RECONFIRM=N(etc.) - raw_key = 用户 Idempotency-Key 或
tcp-pkt-{conn_id}-{serial_no}
同时所有 5 轴都不同的请求, key 不会撞.
Implementations§
Source§impl IdempotencyCache
impl IdempotencyCache
pub fn new(ttl_secs: u64) -> Self
Sourcepub fn with_default_ttl() -> Self
pub fn with_default_ttl() -> Self
默认 TTL。
Sourcepub fn get(&self, key: &str) -> Option<Vec<u8>>
pub fn get(&self, key: &str) -> Option<Vec<u8>>
查询 cache:
- Some(cached_response):TTL 内命中,返回缓存结果
- None:未命中 / 已过期(调用方应真执行 +
Self::put)
⚠️ 过期的 entry 不在此清理(避免抢锁),由周期性 Self::purge_expired
清。cache hit 判断只看 inserted_at.elapsed() < ttl。
Sourcepub fn put(&self, key: String, response: Vec<u8>)
pub fn put(&self, key: String, response: Vec<u8>)
插入新结果。相同 key 覆盖(但实际上调用方应在 get=None 之后才 put, 避免并发两次 put 覆盖)。
Sourcepub fn put_finished_response(
&self,
key: &str,
guard: &mut Option<IdempotencyFlightGuard<'_>>,
response: Vec<u8>,
)
pub fn put_finished_response( &self, key: &str, guard: &mut Option<IdempotencyFlightGuard<'_>>, response: Vec<u8>, )
Store the terminal response for a key that was guarded by Self::begin.
Call sites keep the fallback put path because older code may still
construct a cache key without acquiring a guard. New trade-write paths
should normally pass the guard returned by begin.
Sourcepub async fn begin(&self, key: String) -> IdempotencyBegin<'_>
pub async fn begin(&self, key: String) -> IdempotencyBegin<'_>
Per-key singleflight gate for idempotent trade writes.
The old pattern was get -> await backend -> put, so two concurrent
requests with the same key could both miss and both execute the backend
write. This method serializes executions for the same namespaced key:
followers wait until the leader stores a response or drops the guard.
Sourcepub fn purge_expired(&self) -> usize
pub fn purge_expired(&self) -> usize
清理已过期 entry。建议 spawn 一个 tokio::time::interval 每 60s 调一次。 即使不调也无正确性问题(get 自己会判 TTL),只是 cache 内存会累积。