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 进程(另一台机器、另一个容器)的,升级前先把它们也停掉。 + ## 升级 把新版本的离线包拷到机器上,解到一个新目录,然后: