订单跟踪bug17
订单跟踪系统严重 Bug 分析
1. 订单跟踪流程概览
sequenceDiagram
participant Exec as Executor
participant Mgr as WebSocketOrderManager
participant WS as K线服务/WS消息
participant HTTP as 交易所HTTP
Exec->>Mgr: track_order(oid, coin, timeout)
Mgr->>Mgr: _timeout_loop(oid, tracking) 后台线程
Exec->>Mgr: wait_for_order(tracking) 阻塞
WS->>Mgr: handle_message(orderUpdates/userFills)
Mgr->>Mgr: _on_order_update / _on_user_fill
Mgr->>Mgr: _finish(oid) 或 启动 grace timer
Note over Mgr: 超时后
Mgr->>HTTP: _http_check(oid)
HTTP-->>Mgr: status
Mgr->>Mgr: _finish_direct(oid, tracking, status, ...)
Mgr->>Exec: result_event.set()
核心代码位置:
- 追踪与超时:
src/trading/websocket_order_manager.py(track_order、_timeout_loop、_finish/_finish_direct) - 调用方:
src/trading/executor.py(_track_limit_order) - 消息入口:
src/services/realtime_kline_service_base.py(on_message→_get_ws_order_manager().handle_message)
2. 严重 Bug 与风险
Bug 1:_timeout_loop 早退导致 wait_for_order 可能长期甚至永久阻塞(严重)
位置:src/trading/websocket_order_manager.py 第 394-396 行
现象:超时时刻 result_event.wait(timeout=...) 返回后,若在锁内发现 tracking._ws_status is not None(例如 WS 刚把订单置为 filled 并启动了 5 秒 grace timer),当前实现直接 return,既不调用 _finish(oid) 也不调用 result_event.set()。
后果:
wait_for_order()是无超时的result_event.wait(),依赖其它路径调用_finish/_finish_direct才会result_event.set()。- 正常情况:grace timer 在 5 秒内会调用
_finish(oid),主线程最多多等几秒。 - 异常情况:若 grace timer 未触发(如进程正在退出、daemon timer 被提前清理、或 timer 实现异常),则没有任何路径再会 set 该 tracking 的 result_event,
wait_for_order会永久阻塞。
修复方向:在“发现 _ws_status is not None 则 return”的分支里,不要直接退出;应主动调用 _finish(oid),由 _finish 在锁内完成终态设置并 result_event.set(),保证无论 grace timer 是否执行,等待线程都能被唤醒。
Bug 2:_http_check 返回 "resolved" 但未真正结算(逻辑不一致 / 误判)
位置:src/trading/websocket_order_manager.py 第 453-478 行
现象:_http_check 在锁内取出 tracking 后释放锁再发 HTTP。若在 HTTP 往返期间,同一 oid 被 track_order() 替换(新 tracking 覆盖旧 tracking),则:
- HTTP 返回后调用
_finish_direct(oid, old_tracking, status, ...),identity 检查current is not tracking为真,不执行任何更新; - 但
_http_check仍返回"resolved"。
后果:
- 调用方(
_timeout_loop)认为该 oid 已“已解决”并 return,而新的 tracking 从未被本次 HTTP 结果结算。 - 当前设计下,新 tracking 会有自己的
_timeout_loop,在后续超时或重试中再次_http_check时仍可能被正确结算,因此多数情况下不会永久丢单,但会出现“同一订单在逻辑上被当作 resolved 一次、实际又等下一轮超时再结算”的混乱和延迟。 - 若存在依赖“resolved 即表示该 oid 已从管理器完全结算”的其它逻辑,可能产生误判。
修复方向:在 _finish_direct 因 identity 检查未执行任何操作时,不应让 _http_check 返回 "resolved";可返回如 "replaced" 或保持不结算并让调用方重试(例如仍按 "error"/"busy" 语义处理),避免“已返回 resolved 但当前 oid 的 tracking 仍为 PENDING”的不一致。
Bug 3:重连补查 verify_pending_orders 使用陈旧 pending 列表且不保证补查完成
位置:src/trading/websocket_order_manager.py 第 152-176 行
现象:
- 在锁内快照
pending = [oid for ... PENDING]后,在无锁下逐个_http_check(oid)。 - 若某次
_http_check内部通过_finish_direct把该 oid 结算并 pop,其它 oid 的 tracking 不受影响,但列表本身是旧的;若在补查过程中又有新 track 加入或 WS 已结算某 oid,列表不会反映这些变化(仅影响本轮补查范围,不直接导致错写)。 - 文档写明“首次失败延迟 3 秒重试一次,二次失败交由超时机制兜底”,但未对“重试后仍 error/busy”的 oid 做任何状态更新,既未 set result_event,也未标记为 TIMEOUT,该 oid 的
wait_for_order只能继续等自己的_timeout_loop超时。
后果:断线期间有多笔 PENDING 订单时,补查失败(网络/限流)的订单会长时间处于“既未收到 WS 终态,也未在补查中被结算”的状态,依赖每单 600s 超时才能结束,用户体验差且可能被误认为“卡死”。
修复方向:对重试后仍 error/busy 的 oid,可考虑调用 _finish_direct(..., TIMEOUT) 或至少记录并告警,避免无限期依赖超时;同时可评估是否在补查时对列表加短锁或按批处理以减少与并发 track/finish 的竞态观感。
Bug 4:订单消息缓冲区有界但无持久化,进程重启丢失
位置:src/services/realtime_kline_service_base.py 第 617-638 行
现象:当 _get_ws_order_manager() 为 None(例如交易模块未就绪),orderUpdates/userFills 会进入内存队列 _order_msg_buffer;超过 1000 条会打 error 并仍可继续 append,100 条起每 100 条打 warning。
后果:若服务在“管理器尚未就绪且已缓冲大量订单消息”时重启,缓冲区内容全部丢失,断线前后到达的订单状态更新无法被追踪逻辑看到,只能依赖 HTTP 补查与每单超时,存在漏跟或延迟结算风险。
修复方向:视为设计取舍;若需更高可靠性,可考虑对关键订单消息做持久化或至少限制丢弃策略(例如按 oid 去重、只保留最新状态),并在文档中明确“进程重启会丢失未交付的缓冲订单消息”。
Bug 5:_on_order_update 中 continue 在锁内导致单条消息内持锁过长
位置:src/trading/websocket_order_manager.py 第 307-336 行
现象:在 for item in items 循环内,with self._lock 覆盖从“取 tracking”到“设置 _ws_status / 启动 timer / 设置 should_finish”的整段逻辑;当 not tracking or status != PENDING 或 status_str 未匹配时使用 continue 进入下一轮循环,锁会先释放再进入下一轮(continue 跳出的是内层 with 块),因此单条消息内是“每 item 获取/释放一次锁”,不是整条消息持锁。
结论:此处无严重逻辑错误;若单条推送包含大量 item,会多次短时持锁,仅可能加剧锁竞争,属性能/扩展性考量,可在优化阶段考虑缩小锁粒度或批量处理。
3. 其他注意点(非严重但值得统一)
- HTTP 响应状态未覆盖:
_parse_order_response对非filled/canceled/rejected/open/triggered的raw_status返回(None, 0, 0),导致_http_check返回"error"。若交易所未来返回如"partial"等状态,会一直被当作 error 重试,可考虑显式枚举或日志中区分“未知状态”与“请求失败”。 - oid 类型:代码已通过
_safe_int_oid和track_order内int(oid)统一为 int,与文档一致,未发现类型不一致导致的追踪失效。
4. 修复优先级建议
| 优先级 | Bug | 建议 |
|---|---|---|
| P0 | Bug 1:_timeout_loop 早退不 set result_event | 在“_ws_status is not None”分支改为调用 _finish(oid),确保 wait_for_order 必定被唤醒 |
| P1 | Bug 2:_http_check 在 identity 失败时仍返回 resolved | _finish_direct 未实际更新时,返回“未结算”或让调用方重试,避免误判 resolved |
| P2 | Bug 3:verify_pending_orders 重试后仍失败无兜底 | 对二次仍 error/busy 的 oid 做 TIMEOUT 或告警,避免长期挂起 |
| P3 | Bug 4:订单消息缓冲无持久化 | 按业务需求决定是否持久化或限制丢弃策略并文档化 |
以上为当前代码静态分析得出的订单跟踪严重问题与修复方向,实际修代码时需再跑单测/集成测试验证(尤其是并发替换 tracking 与重连场景)。