订单跟踪系统BUG25
订单跟踪系统严重 Bug 分析报告
分析日期:2026-02-21
主要问题文件:websocket_order_manager.py
BUG 1 — 竞态条件导致定时器内存泄漏 🔴 Critical
位置: websocket_order_manager.py:360-365
代码:
timer = threading.Timer(
self._FILL_GRACE_SEC, self._resolve, [oid]
)
timer.daemon = True
tracking._grace_timer = timer
timer.start()
问题描述:
同一 oid 被重复调用 track_order() 时,旧 tracking 被替换,但旧 _grace_timer 的引用丢失。旧定时器仍在后台运行,最终调用 _resolve(oid) 时发现 identity check 失败(tracking is not old_tracking),订单状态永久悬挂为 PENDING。大量重复追踪会导致定时器线程不断累积,最终引发线程爆炸。
严重程度: Critical
潜在影响:
- 订单状态永久不一致,
wait_for_order无限等待直到超时 - 高频交易场景下线程数量失控,系统资源耗尽
BUG 2 — 成交价丢失(默认值陷阱)🟠 High
位置: websocket_order_manager.py:270-283
代码:
if final_status == OrderStatus.FILLED:
if tracking is not None and status is not None:
if not tracking.has_fill_price:
if px > 0:
tracking.avg_price = px
tracking.has_fill_price = True
if sz > 0:
tracking.filled_size = sz
else:
# WS 模式:使用 fallback 价格
if not tracking.has_fill_price:
tracking.avg_price = tracking._fallback_px # ← 可能为 0.0
tracking.filled_size = tracking._fallback_sz # ← 可能为 0.0
问题描述:
当交易所返回的所有价格字段(avgPx、limitPx、px)均为 None 或 0 时,_fallback_px = 0.0,最终成交记录 avg_price=0.0、filled_size=0.0,后续 PnL 计算完全错误。
严重程度: High
潜在影响:
- 订单显示成交但成交均价和数量为 0
- PnL 计算结果失真,用户看到虚假利润数据
- 清仓逻辑因
size=0而失败
BUG 3 — 订单 ID 类型不一致导致追踪失效 🟠 High
位置: websocket_order_manager.py:127, 392
代码:
# track_order 中 (第127行)
oid = int(oid)
# _on_user_fill 中 (第392行)
oid = _safe_int_oid(fill_data.get("oid"))
if oid is None:
continue
问题描述:
不同来源的 oid(字符串、浮点数、整数)转换时可能因精度问题产生不一致。例如浮点精度误差导致 int(10000000000.1) != int(10000000000.2),使 userFills 消息与 orderUpdate 消息无法正确关联,同一订单被认为是两个不同订单。
严重程度: High
潜在影响:
- WS 终态和真实成交价无法正确关联
- 部分成交的订单价格累计失败
- 多币种系统中可能导致订单追踪到错误币种
BUG 4 — _resolve 在锁外调用的 TOCTOU 竞态 🔴 Critical
位置: websocket_order_manager.py:341-376
代码:
with self._lock:
# ...设置 should_resolve = True
if should_resolve:
self._resolve(oid) # ← 锁已释放!此时另一线程可能已替换 _tracking[oid]
问题描述:
锁内完成状态检查和 should_resolve = True 设置后释放锁,在锁外调用 _resolve(oid) 期间,另一线程若调用 track_order(oid) 替换了 _tracking[oid],identity check 失败导致 _resolve() 返回 False,订单永久阻塞在 PENDING,wait_for_order 无限等待直到超时。
时序:
- 线程 A 持锁,设置
should_resolve=True - 线程 A 释放锁
- 线程 B 调用
track_order(oid),替换_tracking[oid]为新 tracking - 线程 A 调用
_resolve(oid),identity check 失败,返回 False - 订单状态永久 PENDING
严重程度: Critical
潜在影响:
- 高并发下自动交易订单频繁阻塞
- 真实订单已成交但系统误判为 TIMEOUT,头寸计算错误
BUG 5 — _http_busy 集合状态不一致 🟡 Medium
位置: websocket_order_manager.py:529-557
代码:
def _http_check(self, oid: int) -> str:
with self._lock:
if oid in self._http_busy:
return "busy"
self._http_busy.add(oid)
try:
...
settled = self._resolve(oid, tracking, status, avg_px, total_sz)
return "resolved" if settled else "stale"
finally:
with self._lock:
self._http_busy.discard(oid)
问题描述:
虽然 finally 块确保清理,但如果 _resolve() 内部抛出异常后被上层吞掉,后续对相同 oid 的 _http_check() 可能因为 tracking is not current 而返回 "stale",导致该订单的 HTTP 查询被跳过。
严重程度: Medium
潜在影响:
- 网络异常频繁时,某些订单的 HTTP 查询被意外跳过
- 订单最终状态无法确定
BUG 6 — orderUpdates / userFills 顺序依赖导致延迟或错误 🟠 High
位置: websocket_order_manager.py:346-365, 417-421
代码:
# _on_order_update 中
if status_str == "filled":
tracking._ws_status = OrderStatus.FILLED
if tracking.has_fill_price:
should_resolve = True
else:
timer = threading.Timer(self._FILL_GRACE_SEC, self._resolve, [oid])
timer.start()
# _on_user_fill 中
if tracking._ws_status is not None:
should_resolve = True
问题描述:
系统设计假设 orderUpdates 先于 userFills 到达。若顺序颠倒:
userFills先到 →_ws_status is None→ 不触发 resolve,成交数据被丢弃- 之后
orderUpdates到达,启动 grace timer,5 秒后才使用可能不准确的 fallback 价格
严重程度: High
潜在影响:
- 成交确认延迟,快速市场中头寸偏离
userFills推送不稳定的币种,成交价完全依赖不准确的 fallback 值
BUG 7 — verify_pending_orders 使用过期 tracking 对象 🟠 High
位置: websocket_order_manager.py:176-205
代码:
with self._lock:
pending = [
(oid, t) for oid, t in self._tracking.items()
if t.status == OrderStatus.PENDING and t._ws_status is None
]
for i, (oid, tracking) in enumerate(pending):
result = self._http_check(oid)
if result in ("error", "busy"):
retry_list.append((oid, tracking)) # ← tracking 可能已过期
time.sleep(3)
for oid, tracking in retry_list:
result = self._http_check(oid)
if result in ("error", "busy"):
self._resolve(oid, tracking, OrderStatus.TIMEOUT) # ← 使用过期 tracking!
问题描述:
重试时持有的 tracking 引用可能已被其他线程替换。调用 _resolve(oid, tracking, ...) 因 identity check 失败而永久返回 False,WebSocket 重连后的订单补查逻辑形同虚设。
严重程度: High
潜在影响:
- WebSocket 重连后大量 PENDING 订单无法被正确解决
- 订单状态在断线重连后永久悬挂
BUG 8 — _pos_float 无法区分真实 0 值 🟡 Medium
位置: websocket_order_manager.py:69-80
代码:
def _pos_float(raw) -> float:
if raw is None:
return 0.0
try:
val = float(raw)
return val if val > 0 else 0.0
except (ValueError, TypeError):
return 0.0
问题描述:
如果交易所返回字符串 "0.0" 或 "0"(尚未更新成交价时的占位值),_pos_float() 返回 0.0,与真实的 0 成交量无法区分,导致部分成交的订单被误判为未成交。
严重程度: Medium
潜在影响:
- 部分成交订单成交价信息丢失
- 成交价格在暂时不可用时被永久设为 0
BUG 9 — Daemon 线程无异常捕获导致静默崩溃 🟠 High
位置: websocket_order_manager.py:142-144
代码:
threading.Thread(
target=self._monitor_order, args=(oid, tracking), daemon=True
).start()
问题描述:
_monitor_order() 和 grace timer 回调 _resolve() 中的任何未捕获异常都会导致线程/定时器静默死亡,没有告警,没有重试,订单永久卡在 PENDING。
严重程度: High
潜在影响:
- 网络错误或 API 异常时,监控线程崩溃无人感知
- 订单阻塞在
wait_for_order,后续交易逻辑全部串行阻塞
BUG 10 — _grace_timer 重复 cancel 调用逻辑不清晰 🟢 Low
位置: websocket_order_manager.py:262-264, 358-359
问题描述:
_resolve() 和 _on_order_update() 中都有 tracking._grace_timer.cancel() 调用,若定时器已在执行 _resolve() 中途时调用 cancel,Python 内部可以安全处理,但代码逻辑不够清晰,存在潜在的状态不一致风险。
严重程度: Low
优先级汇总
| Bug ID | 位置 | 严重程度 | 修复优先级 | 核心风险 |
|---|---|---|---|---|
| BUG 1 | :130-139, 360-365 |
Critical | 🔴 最高 | 定时器泄漏,线程爆炸 |
| BUG 4 | :341-376 |
Critical | 🔴 最高 | 订单永久 PENDING |
| BUG 2 | :270-283 |
High | 🟠 高 | 成交价丢失,PnL 错误 |
| BUG 3 | :127, 392 |
High | 🟠 高 | 订单 ID 关联失败 |
| BUG 6 | :346-365, 417-421 |
High | 🟠 高 | 成交确认延迟或价格错误 |
| BUG 7 | :176-205 |
High | 🟠 高 | 重连后订单补查失效 |
| BUG 9 | :142-144 |
High | 🟠 高 | 线程静默崩溃无感知 |
| BUG 5 | :529-557 |
Medium | 🟡 中 | HTTP 查询被意外跳过 |
| BUG 8 | :69-80 |
Medium | 🟡 中 | 部分成交价格丢失 |
| BUG 10 | :262-264, 358-359 |
Low | 🟢 低 | 逻辑不清晰 |
核心结论
系统在 并发、状态同步、异常处理 三个维度存在系统性缺陷,在 高频交易 + 网络波动 场景下极易导致订单状态不一致或永久阻塞。建议优先修复 BUG 1 和 BUG 4 的竞态问题,再依次处理高优先级的成交价丢失和异常处理问题。