复核 R-b4(prod.sh、Docker 入口读不到 .env 里的配置):新增 python -m app.cluster workers——照后端自己的配置(环境变量和 .env 一起读)说起几个工作进程,SQLite 而大于 1 时照实报错、退出码 2;prod.sh 只在命令行给了 --workers 时写进环境变量,进程数一律问后端,Docker 的入口同样问它。补测试:SQLite 而大于 1 拒绝、PostgreSQL 照给的数

This commit is contained in:
ThearchyHelios
2026-10-19 13:23:02 +08:00
parent 5f7100906f
commit 5578cf137d
4 changed files with 53 additions and 9 deletions
+2 -1
View File
@@ -102,4 +102,5 @@ LABEL org.opencontainers.image.title="AgentLab" \
# 工作进程数照 AGENTLAB_WORKERS(缺省 1)。大于 1 要应用库是 PostgreSQL(AGENTLAB_DATABASE_URL):SQLite 只支持单进程,
# 服务启动时拒绝第二个进程(app/cluster/guard.py)。exec:uvicorn 直接收 SIGTERM,走完整的优雅退出
CMD ["sh", "-c", "exec uvicorn app.main:app --host 0.0.0.0 --port 8000 --workers \"${AGENTLAB_WORKERS:-1}\""]
# 进程数问后端(python -m app.cluster workers,环境变量和 .env 一起读;SQLite 而大于 1 时照实报错、不起)
CMD ["sh", "-c", "n=\"$(python -m app.cluster workers)\" || exit 2; exec uvicorn app.main:app --host 0.0.0.0 --port 8000 --workers \"$n\""]
+24
View File
@@ -0,0 +1,24 @@
"""python -m app.cluster workers:照后端自己的配置(环境变量和 .env 一起读)说起几个工作进程,scripts/prod.sh 和 Docker 的入口按它给
uvicorn --workers(复核 R-b4:写在 .env 里的 AGENTLAB_DATABASE_URL、AGENTLAB_WORKERS,shell 脚本自己读不到)。应用库是 SQLite 而进程数
大于 1 时照实报错、退出码 2(SQLite 只支持单进程)。"""
from __future__ import annotations
import sys
def main(argv: list[str]) -> int:
if argv[:1] != ["workers"]:
print("用法:python -m app.cluster workers", file=sys.stderr)
return 64
from app.cluster.guard import SINGLE_PROCESS
from app.core.config import settings
if settings.db_dialect == "sqlite" and settings.workers > 1:
print(SINGLE_PROCESS.format(dir=settings.data_dir, hint=f"AGENTLAB_WORKERS={settings.workers}"), file=sys.stderr)
return 2
print(settings.workers)
return 0
if __name__ == "__main__":
raise SystemExit(main(sys.argv[1:]))
+19
View File
@@ -481,3 +481,22 @@ async def test_an_idle_single_process_writes_nothing_for_coordination(monkeypatc
assert cluster.leading(), "只有本进程:直接当班"
finally:
await cluster.stop()
def test_the_worker_count_comes_from_the_backend_settings():
"""复核 R-b4:prod.sh、Docker 的入口按 python -m app.cluster workers 起几个进程(后端自己读环境变量和 .env);SQLite 而大于 1 时
照实报错、退出码 2。"""
backend = Path(__file__).resolve().parents[1]
base = {k: v for k, v in os.environ.items() if not k.startswith("AGENTLAB_")}
base.update(PYTHONPATH=str(backend), AGENTLAB_DATA_DIR=os.environ["AGENTLAB_DATA_DIR"],
AGENTLAB_SECRET_KEY="test-only-not-a-real-key")
def ask(**env: str) -> subprocess.CompletedProcess:
return subprocess.run([sys.executable, "-m", "app.cluster", "workers"], cwd=str(backend), env={**base, **env},
capture_output=True, text=True, timeout=60)
sqlite = ask(AGENTLAB_DATABASE_URL="", AGENTLAB_WORKERS="3")
assert sqlite.returncode == 2 and "只支持单进程" in sqlite.stderr, sqlite.stderr[-1000:]
assert ask(AGENTLAB_DATABASE_URL="", AGENTLAB_WORKERS="1").stdout.strip() == "1"
pg = ask(AGENTLAB_DATABASE_URL="postgresql://u:p@db.internal:5432/agentlab", AGENTLAB_WORKERS="3")
assert pg.returncode == 0 and pg.stdout.strip() == "3", pg.stderr[-1000:]
+8 -8
View File
@@ -26,6 +26,7 @@ USAGE
HOST="127.0.0.1"
PORT="${AGENTLAB_PORT:-8000}"
WORKERS="${AGENTLAB_WORKERS:-1}"
WORKERS_CLI=""
BUILD="auto"
while [ $# -gt 0 ]; do
case "$1" in
@@ -36,8 +37,8 @@ while [ $# -gt 0 ]; do
--port=*) PORT="${1#--port=}" ;;
--workers)
[ $# -ge 2 ] || { echo "--workers 后面要跟进程数"; echo; usage; exit 2; }
WORKERS="$2"; shift ;;
--workers=*) WORKERS="${1#--workers=}" ;;
WORKERS="$2"; WORKERS_CLI=1; shift ;;
--workers=*) WORKERS="${1#--workers=}"; WORKERS_CLI=1 ;;
--build) BUILD="always" ;;
--no-build) BUILD="never" ;;
-h|--help) usage; exit 0 ;;
@@ -57,12 +58,7 @@ esac
if [ "$WORKERS" -lt 1 ]; then
echo "进程数至少是 1:$WORKERS"; exit 2
fi
# SQLite 只支持单进程(服务自己也会拒绝启动第二个进程,app/cluster/guard.py):在这里先说清楚,免得 uvicorn 一遍遍重起被拒的进程
if [ "$WORKERS" -gt 1 ] && [ -z "${AGENTLAB_DATABASE_URL:-}" ]; then
echo "应用库是 SQLite 时只支持单进程:要 --workers $WORKERS,先设 AGENTLAB_DATABASE_URL 换成 PostgreSQL(README「用 PostgreSQL」「多进程」)"
exit 2
fi
export AGENTLAB_WORKERS="$WORKERS"
# 命令行给了 --workers 才写进环境变量(见下面问后端的那一句);没给的话照后端自己的配置(环境变量和 .env)
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
DIST="$ROOT/frontend/dist"
@@ -169,6 +165,10 @@ fi
echo "==> 启动 $URLS"
cd "$ROOT/backend"
# 起几个进程问后端(python -m app.cluster workers):它照环境变量和 .env 一起读应用库、进程数——写在 .env 里的 AGENTLAB_DATABASE_URL、
# AGENTLAB_WORKERS 这里也算数;应用库是 SQLite 而进程数大于 1 时它照实报错(SQLite 只支持单进程),这里不起
[ -n "$WORKERS_CLI" ] && export AGENTLAB_WORKERS="$WORKERS"
WORKERS="$("$PY_BIN" -m app.cluster workers)" || exit 2
# exec:让 uvicorn 直接收 Ctrl-C / SIGTERM,一个信号就走完整的优雅退出(收尾运行、关沙箱)。
# 要是包一层 shell 再转发,终端的 Ctrl-C 会让 uvicorn 收到两次信号,第二次直接强退、跳过收尾
# 多进程(--workers 大于 1,PostgreSQL):uvicorn 起几个工作进程共用这个端口,各自在库里登记、续租约(app/cluster)