当前系统订单跟踪存在哪些严重的bug3
订单跟踪严重 Bug 分析
一、架构与数据流
订单跟踪涉及三条线:
- 下单侧:
executor._track_limit_order用order_result.order_id调用WebSocketOrderManager.track_order(oid, coin, timeout),并阻塞在wait_for_order(tracking)。 - WS 推送:同一 WebSocket 在
RealtimeKlineServiceBase.on_message中根据channel in ("orderUpdates", "userFills")路由到_ws_order_manager.handle_message(msg)。 - 超时兜底:每个被追踪订单启动一个 daemon 线程
_timeout_then_verify,超时后调用_resolve_via_http(oid)做 HTTP 状态查询并结算。
sequenceDiagram
participant Exec as Executor
participant WSM as WebSocketOrderManager
participant WS as WebSocket
participant Kline as KlineService.on_message
Exec->>WSM: track_order(oid, coin, timeout)
WSM->>WSM: _timeout_then_verify(oid) [daemon thread]
Exec->>WSM: wait_for_order(tracking)
WS->>Kline: orderUpdates / userFills
Kline->>WSM: handle_message(msg)
WSM->>WSM: _on_order_update / _on_user_fill
WSM->>Exec: result_event.set()
Note over WSM: 若超时未 set
WSM->>WSM: _resolve_via_http(oid)
WSM->>Exec: result_event.set()
二、严重 Bug 列表
1. HTTP 失败时订单追踪永不结束且泄漏(严重)
位置:websocket_order_manager._resolve_via_http 第 245–272 行。
问题:在第一次 with self._lock 之后调用 self._executor.query_order_status(oid)(第 252 行)。若此处抛异常(网络错误、超时、API 异常等),则:
- 永远不会进入第二次
with self._lock; - 不会执行
tracking.result_event.set(); - 不会执行
self._tracking.pop(oid, None)。
后果:
wait_for_order会一直阻塞到tracking.timeout_seconds + 30才因 wait 超时返回,且返回值为“未成交”;- 该订单的
OrderTracking会一直留在_tracking中,造成内存泄漏; - 若同一 oid 再次被追踪(理论上可能),会与旧条目冲突或行为混乱。
修复方向:在 _resolve_via_http 中用 try/except/finally 保证:无论 HTTP 是否成功,只要进入了“对该 oid 做 HTTP 解析”的分支,最终都要在适当条件下 result_event.set()、将状态设为 TIMEOUT(或保持 PENDING 并标记失败)、并从 _tracking 中移除该 oid。
2. oid 类型不一致导致 WS 订单更新被忽略(严重)
位置:
- 写入 key:
track_order使用传入的oid(来自order_result.order_id,API 解析后可能是int或str); - 读取 key:
_on_order_update中oid = order.get("oid"),_on_user_fill中oid = fill.get("oid")(JSON 解析后同样可能是int或str)。
问题:若一端为 int、另一端为 str(例如 123 vs "123"),则 self._tracking.get(oid) 为 None,该条 orderUpdates/userFills 完全不会更新任何追踪。
后果:
- 订单实际已成交/取消,但系统一直认为 PENDING;
- 只能依赖超时后的 HTTP 兜底才能结束等待,且若 HTTP 也失败则叠加 Bug 1 的问题;
- 用户可能误判为“未成交”并触发撤单等逻辑,而交易所端可能已成交。
修复方向:在所有使用 oid 作为 _tracking / _fill_prices key 的地方统一规范为同一类型(建议 int):在 track_order、_on_order_update、_on_user_fill、_resolve_via_http、以及 verify_pending_orders 的迭代中,对 oid 做 int(oid)(或安全转换),并保证 API 响应解析处(如 executor 的 _parse_order_response 中 filled.get("oid") / resting.get("oid"))也统一为同一类型再写入 result.order_id。
3. 订单消息在依赖未就绪时被静默丢弃(严重)
位置:RealtimeKlineServiceBase.on_message 第 614–623 行。
问题:当 channel in ("orderUpdates", "userFills") 时,仅当以下全部为真才转发到订单管理器:
hasattr(self, '_trading_orchestrator')且非 None;hasattr(self._trading_orchestrator, '_executor')且非 None;hasattr(self._trading_orchestrator._executor, '_ws_order_manager')且非 None。
若启动顺序或依赖注入导致某一环为 None 或未设置,则:
- 订单消息直接 return,不调用
handle_message; - 没有任何日志,难以发现“订单推送没被处理”。
后果:
- 已下的限价单在交易所已成交/取消,但本系统永远收不到 WS 更新;
- 表现与 Bug 2 类似:一直 PENDING,只能等超时 HTTP;若再叠加 Bug 1,则可能长时间阻塞甚至泄漏。
修复方向:
- 当
channel in ("orderUpdates", "userFills")但上述依赖链不完整时,至少打 warning 日志(例如 “订单消息已收到但未路由:orchestrator/executor/ws_order_manager 未就绪”),便于排查; - 在架构上保证 K 线服务在开始接收 WS 前,订单管理器的依赖链已就绪,或对“未就绪”阶段做明确降级与告警。
4. 其他观察(非致命但值得注意)
- 重连后补查:重连时调用
verify_pending_orders会与同一订单的超时线程可能同时调用_resolve_via_http(oid),会产生重复 HTTP 请求,但先完成者会 pop 并 set,后完成者会因get(oid)为 None 而 return,不会破坏一致性,仅多一次请求。 - 消息去重:
MessageDeduplicator已对orderUpdates、userFills做_SKIP_DEDUP_CHANNELS,订单消息不会被去重掉,此处无问题。 - API 响应中 oid 类型:
executor._parse_order_response中result.order_id = filled.get("oid")/resting.get("oid")未强制转为 int,若交易所返回 str,会与 Bug 2 的修复联动,建议在解析处统一为 int 再赋给result.order_id。
三、修复优先级建议
| 优先级 | Bug | 影响 | 建议 |
|---|---|---|---|
| P0 | HTTP 失败未清理(Bug 1) | 永久阻塞 + 内存泄漏 | 在 _resolve_via_http 中加 try/except/finally,保证 set + pop + 状态 |
| P0 | oid 类型不一致(Bug 2) | WS 更新全部丢失 | 全链路 oid 规范为 int(track、WS 处理、HTTP、API 解析) |
| P1 | 订单消息静默丢弃(Bug 3) | 难以排查的“收不到更新” | 依赖未就绪时打 warning 日志;保证启动顺序/依赖注入 |
按上述顺序修复后,订单跟踪的健壮性和可观测性会有明显提升。