fix(server): Unix 默认组合在 rloop 不可用时退回 uvicorn+uvloop
- pick_server(auto) 按平台组合选择:Windows granian+winloop;Unix granian+rloop → uvicorn+uvloop → 已安装项 - 回退 uvicorn 时打印原因(未安装 granian / rloop 不可用) - 测试补充 Unix 回退分支与 Windows auto 选择;README 默认矩阵同步
This commit is contained in:
1 parent
bcfc1022eb
commit
39565a3913
3 files changed
+38
-12
No files matched your search
@@ -58,17 +58,17 @@ Node/TypeScript 版(v1.x,`master` 分支):`pnpm install && pnpm start`
|
||||
| 平台 | 默认服务器 | 默认事件循环 |
|
||||
| --- | --- | --- |
|
||||
| Windows | granian | winloop(未安装则 asyncio) |
|
||||
| Linux / macOS | granian | rloop(未安装则 uvloop → asyncio) |
|
||||
| Linux / macOS | granian;rloop 不可用时退回 uvicorn | rloop;退回 uvicorn 时 uvloop(再退 asyncio) |
|
||||
| Windows + `--server uvicorn` | uvicorn | asyncio(uvicorn 不支持 winloop) |
|
||||
| Linux / macOS + `--server uvicorn` | uvicorn | uvloop(未安装则 asyncio) |
|
||||
|
||||
事件循环可用 `auto / asyncio / rloop / uvloop / winloop`,参数与环境变量等价
|
||||
(`SERVER` / `LOOP` / `HOST` / `PORT` / `WORKERS` / `RELOAD`)。`auto` 会在启动器内解析成上表中的具体循环,
|
||||
启动日志会打印实际使用的组合;显式请求当前平台不可用的循环会直接报错并列出可用项(不会静默降级)。
|
||||
(`SERVER` / `LOOP` / `HOST` / `PORT` / `WORKERS` / `RELOAD`)。`auto` 会在启动器内解析成上表中的具体组合,
|
||||
启动日志会打印实际使用的 server+loop;显式请求当前平台不可用的循环会直接报错并列出可用项(不会静默降级)。
|
||||
|
||||
```bash
|
||||
uv sync # 默认依赖已含 granian[rloop/uvloop/winloop/reload]
|
||||
uv run python scripts/serve.py # Windows → granian+winloop;Unix → granian+rloop
|
||||
uv run python scripts/serve.py # Windows → granian+winloop;Unix → granian+rloop(缺 rloop 退 uvicorn+uvloop)
|
||||
uv run python scripts/serve.py --server uvicorn # Unix → uvicorn+uvloop;Windows → uvicorn+asyncio
|
||||
uv run python scripts/serve.py --loop rloop # 仅 Unix(Rust 事件循环)
|
||||
uv run python scripts/serve.py --loop uvloop # 仅 Unix
|
||||
|
||||
+18
-6
@@ -14,9 +14,10 @@
|
||||
uvloop uvloop 包,仅 Unix
|
||||
winloop winloop 包,仅 Windows(uvloop 的 Windows 实现)
|
||||
|
||||
`auto` 在启动器内解析为具体循环,不交给服务器:
|
||||
granian: Windows → winloop → asyncio;Unix → rloop → uvloop → asyncio
|
||||
uvicorn: Windows → asyncio;Unix → uvloop → asyncio
|
||||
`auto` 在启动器内解析成具体组合,不交给服务器:
|
||||
服务器:Windows → granian;Unix → granian+rloop,rloop 不可用时退回 uvicorn+uvloop
|
||||
循环: granian → Windows winloop / Unix rloop→uvloop→asyncio;
|
||||
uvicorn → Unix uvloop→asyncio / Windows asyncio
|
||||
|
||||
环境变量(参数优先):SERVER / LOOP / HOST / PORT / WORKERS / RELOAD。
|
||||
PORT 默认读取 .env 后的 16688;HOST 默认 0.0.0.0(局域网播放器可直连)。
|
||||
@@ -55,11 +56,21 @@ def has_module(name: str) -> bool:
|
||||
|
||||
|
||||
def pick_server(requested: str) -> str:
|
||||
"""auto:默认 granian(uv sync 已包含);未安装时回退 uvicorn。"""
|
||||
"""auto:平台默认组合。
|
||||
|
||||
Windows:granian(+winloop);Unix:granian+rloop,rloop 不可用时退回 uvicorn+uvloop,
|
||||
最后才回退到已安装的服务器(循环由 default_loop 再降级)。
|
||||
"""
|
||||
if requested not in SERVERS:
|
||||
raise SystemExit(f"不支持的 server: {requested}(可选:{', '.join(SERVERS)})")
|
||||
if requested != "auto":
|
||||
return requested
|
||||
if IS_WINDOWS:
|
||||
return "granian" if has_module("granian") else "uvicorn"
|
||||
if has_module("granian") and has_module("rloop"):
|
||||
return "granian"
|
||||
if has_module("uvicorn") and has_module("uvloop"):
|
||||
return "uvicorn"
|
||||
return "granian" if has_module("granian") else "uvicorn"
|
||||
|
||||
|
||||
@@ -165,8 +176,9 @@ def main(argv: list[str] | None = None) -> int:
|
||||
server = pick_server(requested_server)
|
||||
if not has_module(server):
|
||||
raise SystemExit(f"未安装 {server}:uv sync(或 uv add {server})")
|
||||
if requested_server == "auto" and server == "uvicorn" and not has_module("granian"):
|
||||
print("[serve] 未安装 granian,回退 uvicorn;uv sync 可安装默认的 granian 依赖", flush=True)
|
||||
if requested_server == "auto" and server == "uvicorn":
|
||||
reason = "未安装 granian" if not has_module("granian") else "rloop 不可用"
|
||||
print(f"[serve] 默认组合回退 uvicorn({reason});uv sync 会安装 granian+rloop 默认栈", flush=True)
|
||||
|
||||
loop = resolve_loop(server, str(_arg_or_env(args.loop, "LOOP", "auto")))
|
||||
host = str(_arg_or_env(args.host, "HOST", "0.0.0.0"))
|
||||
|
||||
+16
-2
@@ -12,14 +12,28 @@ sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
from scripts import serve
|
||||
|
||||
|
||||
def test_pick_server_auto_prefers_granian(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setattr(serve, "has_module", lambda name: name == "granian")
|
||||
def test_pick_server_auto_windows_prefers_granian(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setattr(serve, "IS_WINDOWS", True)
|
||||
monkeypatch.setattr(serve, "has_module", lambda name: name in ("granian", "winloop"))
|
||||
assert serve.pick_server("auto") == "granian"
|
||||
|
||||
monkeypatch.setattr(serve, "has_module", lambda name: False)
|
||||
assert serve.pick_server("auto") == "uvicorn"
|
||||
|
||||
|
||||
def test_pick_server_auto_unix_prefers_granian_rloop(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setattr(serve, "IS_WINDOWS", False)
|
||||
monkeypatch.setattr(serve, "has_module", lambda name: name in ("granian", "rloop", "uvloop", "uvicorn"))
|
||||
assert serve.pick_server("auto") == "granian"
|
||||
|
||||
|
||||
def test_pick_server_auto_unix_falls_back_to_uvicorn_uvloop(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
# 例如 granian 已安装但 rloop 不可用:默认改为 uvicorn+uvloop
|
||||
monkeypatch.setattr(serve, "IS_WINDOWS", False)
|
||||
monkeypatch.setattr(serve, "has_module", lambda name: name in ("granian", "uvloop", "uvicorn"))
|
||||
assert serve.pick_server("auto") == "uvicorn"
|
||||
|
||||
|
||||
def test_available_loops_follow_installed_packages(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setattr(serve, "has_module", lambda name: name == "winloop")
|
||||
assert serve.available_loops("granian") == ["auto", "asyncio", "winloop"]
|
||||
|
||||
Reference in new issue
Block a user