From 3f6b681d0fc3df750b944df4e87a3a683fce954e Mon Sep 17 00:00:00 2001 From: claude_dev Date: Sun, 16 Aug 2026 18:51:07 +0800 Subject: [PATCH] =?UTF-8?q?fix(shadow):=20=E5=A4=9A=E8=B4=A6=E6=88=B7worke?= =?UTF-8?q?r=E4=B8=8D=E6=8A=A2=E5=85=A8=E5=B1=80solo=E9=94=81=E2=80=94?= =?UTF-8?q?=E2=80=94=E6=A0=B9=E5=9B=A0=E5=AE=9E=E9=94=A4:=5F=5Fmain=5F=5F?= =?UTF-8?q?=E5=AF=B9--account=20N=E4=B9=9F=E8=B5=B0~/.sanguo=5Fshadow=5Fde?= =?UTF-8?q?sk.lock=E5=85=A8=E5=B1=80=E9=94=81,=E5=85=A8=E6=9C=BA=E5=8F=AA?= =?UTF-8?q?=E6=94=BE=E8=A1=8C=E4=B8=80=E4=B8=AA=E5=BD=B1=E5=AD=90worker(?= =?UTF-8?q?=E8=B0=81=E5=85=88=E6=8A=A2=E5=88=B0=E8=B0=81=E6=B4=BB),supervi?= =?UTF-8?q?sor=E5=A4=9A=E8=B4=A6=E6=88=B7=E5=B9=B6=E5=AD=98=E6=97=B6?= =?UTF-8?q?=E5=85=B6=E4=BD=99=E5=85=A8=E9=83=A8'=E9=94=81=E5=8D=A0?= =?UTF-8?q?=E7=94=A8'rc=3D0=E4=BC=98=E9=9B=85=E9=80=80=E5=87=BA=E2=86=92?= =?UTF-8?q?=E6=AF=8F60s=E9=87=8D=E6=8B=89=E7=A7=92=E9=80=80=E5=BE=AA?= =?UTF-8?q?=E7=8E=AF;VPS=E5=AE=9E=E5=BD=9540/41=E5=8F=8D=E5=A4=8D=E9=80=80?= =?UTF-8?q?=E5=87=BA+42=E6=8A=A2=E5=88=B0=E9=94=81=E7=8B=AC=E6=B4=BB(shado?= =?UTF-8?q?w=5F41.log=E4=B8=80=E5=87=BB=E5=AE=9A=E4=BD=8D);=E5=8D=95?= =?UTF-8?q?=E8=B4=A6=E6=88=B7=E6=97=B6=E4=BB=A3=E4=BB=8E=E6=9C=AA=E6=9A=B4?= =?UTF-8?q?=E9=9C=B2,2026-08-16=E7=94=A8=E6=88=B7=E5=BB=BA=E4=B8=89?= =?UTF-8?q?=E5=BD=B1=E5=AD=90=E8=B4=A6=E6=88=B7=E5=8D=B3=E7=82=B8=E3=80=82?= =?UTF-8?q?=E4=BF=AE:--account=E5=88=86=E6=94=AF=E8=B7=B3=E8=BF=87?= =?UTF-8?q?=E5=85=A8=E5=B1=80=E9=94=81=E7=9B=B4=E8=BF=9Brun=5Fshadow(?= =?UTF-8?q?=E5=90=8C=E8=B4=A6=E6=88=B7=E9=98=B2=E9=87=8D=E5=A4=8D=E7=94=B1?= =?UTF-8?q?bullet=5Ftrade=20runtime/shadow=5F{aid}=E5=AE=9E=E4=BE=8B?= =?UTF-8?q?=E9=94=81+supervisor=20children=E8=A1=A8=E5=85=9C=E5=BA=95);sol?= =?UTF-8?q?o=E6=A8=A1=E5=BC=8F(=E6=97=A0--account)=E7=BB=B4=E6=8C=81?= =?UTF-8?q?=E5=85=A8=E5=B1=80=E9=94=81=E9=98=B2=E5=8F=8C=E5=BC=80;+3?= =?UTF-8?q?=E8=B7=AF=E7=94=B1=E6=B5=8B=E8=AF=95(account=E8=B7=B3=E9=94=81/?= =?UTF-8?q?solo=E6=8C=81=E9=94=81/auto=E4=B8=8D=E7=A2=B0=E6=9F=9C=E5=8F=B0?= =?UTF-8?q?);936=E7=BB=BF=20[vps]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sanguo_trader/shadow/__main__.py | 9 ++++ tests/trader/test_shadow_lock_routing.py | 56 ++++++++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 tests/trader/test_shadow_lock_routing.py diff --git a/sanguo_trader/shadow/__main__.py b/sanguo_trader/shadow/__main__.py index 8c4a139..11d0d96 100644 --- a/sanguo_trader/shadow/__main__.py +++ b/sanguo_trader/shadow/__main__.py @@ -60,6 +60,15 @@ def main() -> int: run_auto_supervisor() return 0 + if "--account" in sys.argv: + # 多账户 worker 不抢全局 solo 锁:全局锁全机只放行一个 worker,supervisor + # 多账户并存时其余全部"锁占用"优雅退出(VPS 2026-08-16 实录 40/41 反复 + # rc=0,单账户时代从未暴露)。同账户防重复由 bullet_trade + # runtime/shadow_{aid} 实例锁兜底,supervisor children 表保证一账户一进程。 + from .runner import run_shadow + + run_shadow() + return 0 lock = _acquire_lock() if lock is None: print("影子柜台已在运行(锁占用),本次启动退出。", flush=True) diff --git a/tests/trader/test_shadow_lock_routing.py b/tests/trader/test_shadow_lock_routing.py new file mode 100644 index 0000000..861298a --- /dev/null +++ b/tests/trader/test_shadow_lock_routing.py @@ -0,0 +1,56 @@ +"""影子柜台 CLI 路由:--account worker 不抢全局 solo 锁(多账户并存的前提)。""" +import sys + +import pytest + + +def _run_main(monkeypatch, argv): + from sanguo_trader.shadow import __main__ as m + + calls = {"lock": 0, "shadow": 0, "auto": 0} + + def _no_lock(): + calls["lock"] += 1 + return object() + + def _shadow(*a, **k): + calls["shadow"] += 1 + + def _auto(*a, **k): + calls["auto"] += 1 + + import sanguo_trader.shadow.runner as runner_mod + import sanguo_trader.shadow.supervisor as sup + + monkeypatch.setattr(m, "_acquire_lock", _no_lock) + # main() 内 lazy import:patch 源模块而非 __main__ + monkeypatch.setattr(runner_mod, "run_shadow", _shadow) + monkeypatch.setattr(sup, "run_auto_supervisor", _auto) + monkeypatch.setattr(sys, "argv", ["prog", *argv]) + rc = m.main() + return rc, calls + + +def test_account_worker_skips_global_lock(monkeypatch): + """--account N:多账户 worker 不碰全局锁(VPS 实录 40/41「锁占用」循环退出)。""" + rc, calls = _run_main(monkeypatch, ["--account", "41"]) + assert rc == 0 + assert calls["shadow"] == 1 + assert calls["lock"] == 0 # 关键:不抢全局 solo 锁 + + +def test_solo_mode_still_locks(monkeypatch): + """无 --account 的 solo 模式维持全局锁(防双开)。""" + rc, calls = _run_main(monkeypatch, []) + assert rc == 0 + assert calls["shadow"] == 1 + assert calls["lock"] == 1 + + +def test_auto_mode_no_lock_no_shadow(monkeypatch): + """--auto 主管:既不抢锁也不起柜台。""" + rc, calls = _run_main(monkeypatch, ["--auto"]) + assert rc == 0 + assert calls["auto"] == 1 + assert calls["lock"] == 0 + assert calls["shadow"] == 0