From 322ba5969ba5738d712061288b8a99921950a68d Mon Sep 17 00:00:00 2001 From: ThearchyHelios Date: Mon, 19 Oct 2026 14:06:20 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=88=E5=85=A5=20nr8/b=20=E4=B9=8B=E5=90=8E?= =?UTF-8?q?=E7=9A=84=E9=85=8D=E5=90=88=EF=BC=9A=E5=90=AF=E5=8A=A8=E6=97=B6?= =?UTF-8?q?=E5=85=88=E7=AD=89=E5=BA=93=E5=86=8D=E5=81=9A=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E6=A3=80=E6=9F=A5=EF=BC=88cluster/version.preflight=20?= =?UTF-8?q?=E6=98=AF=E7=AC=AC=E4=B8=80=E6=AC=A1=E8=BF=9E=E5=BA=93=EF=BC=8C?= =?UTF-8?q?=E6=94=BE=E5=9C=A8=E7=AD=89=E5=BA=93=E4=B9=8B=E5=89=8D=E7=9A=84?= =?UTF-8?q?=E8=AF=9D=E5=BA=93=E7=9A=84=E5=AE=B9=E5=99=A8=E6=99=9A=E4=B8=80?= =?UTF-8?q?=E6=AD=A5=E8=B5=B7=E6=9D=A5=E6=9C=8D=E5=8A=A1=E5=B0=B1=E7=9B=B4?= =?UTF-8?q?=E6=8E=A5=E5=B4=A9=EF=BC=89=EF=BC=8C=E5=8D=95=E6=B5=8B=E6=A0=B8?= =?UTF-8?q?=E5=AF=B9=E5=85=88=E5=90=8E=EF=BC=9Bdeploy/README=20=E5=A4=9A?= =?UTF-8?q?=E8=BF=9B=E7=A8=8B=E4=B8=80=E6=AE=B5=E5=86=99=E6=98=8E=E5=86=85?= =?UTF-8?q?=E7=BD=AE=20PostgreSQL=20=E6=98=AF=E7=9B=B4=E8=BF=9E=E3=80=81?= =?UTF-8?q?=E4=B8=8D=E7=94=A8=20AGENTLAB=5FDATABASE=5FDIRECT=5FURL?= =?UTF-8?q?=EF=BC=8Cupgrade=20=E5=85=88=E5=81=9C=E6=8E=89=E6=97=A7?= =?UTF-8?q?=E5=AE=B9=E5=99=A8=EF=BC=88=E5=85=A8=E9=83=A8=E5=B7=A5=E4=BD=9C?= =?UTF-8?q?=E8=BF=9B=E7=A8=8B=EF=BC=89=E3=80=81=E5=A4=87=E4=BB=BD=E3=80=81?= =?UTF-8?q?=E5=86=8D=E8=B5=B7=E6=96=B0=E7=89=88=E6=9C=AC=EF=BC=8C=E6=AD=A3?= =?UTF-8?q?=E5=90=88=E4=B8=8D=E8=AE=B8=E6=96=B0=E6=97=A7=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E5=90=8C=E6=97=B6=E8=BF=90=E8=A1=8C=E7=9A=84=E6=8B=A6=E6=88=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/main.py | 4 +++- backend/tests/test_db_startup.py | 24 ++++++++++++++++++++++++ deploy/README.md | 5 +++++ 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/backend/app/main.py b/backend/app/main.py index 5aa95a8e..026a85a3 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -59,9 +59,11 @@ async def lifespan(_: FastAPI) -> AsyncIterator[None]: # (app/cluster/guard.py)。PostgreSQL 可以多进程,什么都不做 guard.acquire() # PgBouncer 事务模式(AGENTLAB_DB_PGBOUNCER=1)要另给直连地址,没给就拒绝启动(db/base.check_pgbouncer) - from app.db.base import check_pgbouncer + from app.db.base import check_pgbouncer, wait_for_database check_pgbouncer() + # 库还没起来(和 PostgreSQL 容器一起起、库还在初始化)时先按退避等它:下面的版本检查就要连库,不等的话一上来就崩 + await wait_for_database() # 不许新旧版本混跑(app/cluster/version.py):库里还有活着的、版本不同的进程就不启动——在补列之前看 from app.cluster import version diff --git a/backend/tests/test_db_startup.py b/backend/tests/test_db_startup.py index c18f0bb1..1ff06ffb 100644 --- a/backend/tests/test_db_startup.py +++ b/backend/tests/test_db_startup.py @@ -135,3 +135,27 @@ async def test_a_missing_database_on_a_real_server_is_raised_at_once(): await engine.dispose() assert clock.slept == [], caught.value assert "agentlab_no_such_database" in str(caught.value) + + +async def test_startup_waits_for_the_database_before_the_version_check(monkeypatch): + """lifespan 里第一次连库的是版本检查(cluster/version.preflight):等库要在它前面,库的容器晚一步起来时服务不至于直接崩。""" + from app.cluster import guard, version + from app.db import base + from app.main import app, lifespan + + calls: list[str] = [] + + async def waited(*_a, **_k) -> None: + calls.append("wait") + + async def checked(*_a, **_k) -> None: + calls.append("preflight") + raise RuntimeError("到这里就够了") + + monkeypatch.setattr(guard, "acquire", lambda: None) + monkeypatch.setattr(base, "wait_for_database", waited) + monkeypatch.setattr(version, "preflight", checked) + with pytest.raises(RuntimeError, match="到这里就够了"): + async with lifespan(app): + pass + assert calls == ["wait", "preflight"] diff --git a/deploy/README.md b/deploy/README.md index 51dd6598..5a8b453f 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -241,6 +241,11 @@ docker run --rm --user 10001:10001 -v "$(pwd)/data:/data" --env-file agentlab.en 问数据和后台任务(规矩见仓库 README「多进程」)。SQLite 时设大于 1 会启动失败(照实报错:SQLite 只支持单进程)。 几台机器、几个容器指着同一个库也行,但数据目录要放在共享存储上、每台挂在同一个路径,限制见仓库 README「多进程」。 +内置 PostgreSQL 是直连(中间没有 PgBouncer),多进程时什么都不用另设(不用 `AGENTLAB_DATABASE_DIRECT_URL`)。新旧版本的进程不能同时 +连着一个库(代码里拦着:新版本发现库里还有活着的旧版本进程就拒绝启动)。`./deploy.sh upgrade` 就是按这个顺序做的:先停掉旧容器 +(里面的全部工作进程一起停)、备份,再起新版本;旧进程要是被强杀、登记还没过期,新版本启动时会等它过期(最多半分钟左右)。 +同一个库还接着别处的 AgentLab 进程(另一台机器、另一个容器)的,升级前先把它们也停掉。 + ## 升级 把新版本的离线包拷到机器上,解到一个新目录,然后: