futu_cache/trd_cache/order_state.rs
1use std::sync::Arc;
2use std::sync::atomic::{AtomicU64, Ordering};
3
4use super::*;
5
6impl TrdCache {
7 /// v1.4.73 A2 BUG-008 fix: 读当前账户的 cipher state version(用于 unlock idem_key)。
8 ///
9 /// 首次访问 acc_id 会初始化为 0。后续每次 lock 清 cipher 会 `fetch_add(1)`。
10 /// `idem_key` 构造时把这个 version 纳入 hash → cipher 清后 version 递增 →
11 /// 同 body 的 idem_key 不同 → cache miss → 真执行 unlock(或 backend 真校验)。
12 pub fn get_cipher_state_version(&self, acc_id: u64) -> u64 {
13 let entry = self
14 .cipher_state_versions
15 .entry(acc_id)
16 .or_insert_with(|| Arc::new(AtomicU64::new(0)));
17 entry.load(Ordering::SeqCst)
18 }
19
20 /// Conservative invalidation token for unlock-all idempotency keys.
21 #[must_use]
22 pub fn cipher_state_version_sum(&self) -> u64 {
23 self.cipher_state_versions
24 .iter()
25 .map(|entry| entry.value().load(Ordering::SeqCst))
26 .sum()
27 }
28
29 /// v1.4.73 A2 BUG-008 fix: lock 清 cipher 时调,递增 version → 让下次 unlock
30 /// 同 body 得 cache miss。
31 ///
32 /// 返回 new version(递增后值),便于调用方 log。
33 #[must_use]
34 pub fn bump_cipher_state_version(&self, acc_id: u64) -> u64 {
35 let entry = self
36 .cipher_state_versions
37 .entry(acc_id)
38 .or_insert_with(|| Arc::new(AtomicU64::new(0)));
39 entry.fetch_add(1, Ordering::SeqCst) + 1
40 }
41
42 /// v1.4.106 codex 0226 F1+F2: PlaceOrder 解析到 `OrderNewRsp.action.order_confirm`
43 /// 时调用, 保存上下文用于后续 `Trd_ReconfirmOrder` 构造 backend `OrderConfirmReq`.
44 ///
45 /// `now_ms` 由 caller 传入 (便于单测注入固定时钟); 真实路径用
46 /// `SystemTime::now()`.
47 pub fn store_pending_order_confirm(
48 &self,
49 acc_id: u64,
50 ftapi_order_id: u64,
51 mut ctx: OrderConfirmContext,
52 now_ms: u64,
53 ) {
54 ctx.inserted_at_ms = now_ms;
55 let key = OrderConfirmKey::new(acc_id, ftapi_order_id);
56 self.pending_order_confirms.insert(key, ctx);
57 }
58
59 /// v1.4.106 codex 0226 F1+F2: ReconfirmOrder handler 入口 lookup, 取出
60 /// `(acc_id, ftapi_order_id)` 对应 OrderConfirmContext.
61 ///
62 /// 返 `None`: cache miss (PlaceOrder 没存 / TTL 过期 / 已被消费). caller 必须
63 /// 早 reject loud, **不**允许 silent fallback (避免反模式 D / silent-success).
64 ///
65 /// `now_ms` 检查 TTL: `now - ctx.inserted_at_ms > ORDER_CONFIRM_CONTEXT_TTL_MS`
66 /// 视为 stale → return None + remove (proactive GC).
67 pub fn get_pending_order_confirm(
68 &self,
69 acc_id: u64,
70 ftapi_order_id: u64,
71 now_ms: u64,
72 ) -> Option<OrderConfirmContext> {
73 let key = OrderConfirmKey::new(acc_id, ftapi_order_id);
74 let ctx = self.pending_order_confirms.get(&key)?.value().clone();
75 if now_ms.saturating_sub(ctx.inserted_at_ms) > ORDER_CONFIRM_CONTEXT_TTL_MS {
76 // Stale → proactive GC
77 self.pending_order_confirms.remove(&key);
78 return None;
79 }
80 Some(ctx)
81 }
82
83 /// v1.4.106 codex 0226 F1+F2: ReconfirmOrder backend 成功 (`OrderConfirmRsp.result==0`)
84 /// 后调用, 从 cache 删除 (一次性消费, 防重复 confirm).
85 ///
86 /// 返 `true` 表示真有删除发生; `false` = 已被其他路径消费 / 过期 GC.
87 pub fn remove_pending_order_confirm(&self, acc_id: u64, ftapi_order_id: u64) -> bool {
88 let key = OrderConfirmKey::new(acc_id, ftapi_order_id);
89 self.pending_order_confirms.remove(&key).is_some()
90 }
91
92 /// v1.4.106 codex 0226 F1+F2: GC stale OrderConfirmContext entries.
93 ///
94 /// 用于定时清理 (push dispatcher 收到 ORDER 类 push 时顺便扫一次), 防止
95 /// stale ctx 累积. 返回清理掉的条目数.
96 pub fn purge_stale_order_confirms(&self, now_ms: u64) -> usize {
97 let mut purged = Vec::new();
98 for entry in self.pending_order_confirms.iter() {
99 if now_ms.saturating_sub(entry.value().inserted_at_ms) > ORDER_CONFIRM_CONTEXT_TTL_MS {
100 purged.push(*entry.key());
101 }
102 }
103 let n = purged.len();
104 for key in purged {
105 self.pending_order_confirms.remove(&key);
106 }
107 n
108 }
109
110 /// Store backend write `req_id -> order_id_ex` for C++ order op-result
111 /// push parity.
112 ///
113 /// Ref:
114 /// - `NNProto_Trd_OnPush.cpp:22-27`
115 /// - `NNProto_Trd_OrderOpReal.cpp:217,352`
116 ///
117 /// Empty values are ignored because C++ only maps meaningful backend
118 /// request/order pairs. Return value mirrors best-effort insert success.
119 pub fn store_order_op_req_order(&self, acc_id: u64, req_id: &str, order_id_ex: &str) -> bool {
120 let req_id = req_id.trim();
121 let order_id_ex = order_id_ex.trim();
122 if req_id.is_empty() || order_id_ex.is_empty() {
123 return false;
124 }
125
126 self.order_op_req_orders
127 .insert(
128 OrderOpReqKey::new(acc_id, req_id.to_string()),
129 order_id_ex.to_string(),
130 )
131 .is_none()
132 }
133
134 /// Consume backend order id by operation `req_id`.
135 ///
136 /// C++ `FindOrderIDByReqID` erases the pair after lookup and clears the
137 /// helper map once it reaches 512 entries. Keep the same bounded,
138 /// best-effort behavior; a miss only means the push may have come from
139 /// another terminal/client and the dispatcher may fall back to full list.
140 pub fn take_order_id_by_op_req_id(&self, acc_id: u64, req_id: &str) -> Option<String> {
141 let req_id = req_id.trim();
142 if req_id.is_empty() {
143 return None;
144 }
145
146 let key = OrderOpReqKey::new(acc_id, req_id.to_string());
147 let value = self
148 .order_op_req_orders
149 .remove(&key)
150 .map(|(_, value)| value);
151 if self.order_op_req_orders.len() >= ORDER_OP_REQ_ORDER_MAP_CLEAR_LIMIT {
152 self.order_op_req_orders.clear();
153 }
154 value
155 }
156}