Skip to main content

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}