Forge database pool caps are config values and a change recreates the forge (issue 1966, recovery)
mesh/delivery merged: source.main moved to novox/mesh-catalog@53d173aa7de5
mesh/check checking 4becc7bd8312 on 53d173aa7de5

The caps were literals in server.env and the container named no restart-on, so a changed env-file never reached the running forge. They are now three gitea config values placed with ${config:...}, and the server container restarts on server-env.

Claude-Session: https://claude.ai/code/session_01NcbgfFfQ4Z9UE5jK32PmE5
This commit is contained in:
2026-11-08 03:33:16 +01:00
parent 61737962fc
commit 4becc7bd83
3 changed files with 42 additions and 6 deletions
+3 -3
View File
@@ -7,6 +7,6 @@ GITEA__database__PASSWD=${secret:postgres-database}
GITEA__webhook__ALLOWED_HOST_LIST=172.17.0.1
# Issue 1966: cap the forge's own pool; the store's 100 connections are shared by every module on this machine.
# SSH git operations go through the forge's private API, so this pool carries them.
GITEA__database__MAX_OPEN_CONNS=40
GITEA__database__MAX_IDLE_CONNS=10
GITEA__database__CONN_MAX_LIFETIME=5m
GITEA__database__MAX_OPEN_CONNS=${config:database-max-open-connections}
GITEA__database__MAX_IDLE_CONNS=${config:database-max-idle-connections}
GITEA__database__CONN_MAX_LIFETIME=${config:database-connection-lifetime}
+23
View File
@@ -187,6 +187,26 @@
]
},
"config": {
"database-max-open-connections": {
"type": "number",
"description": "The most connections the forge's own database pool holds open to the store. The store's 100 connections are shared by every module on its machine, and the forge's git operations over ssh go through the forge's private API, which this pool carries: 40 leaves room for the others.",
"trusted": false,
"minimum": 1,
"default": 40
},
"database-max-idle-connections": {
"type": "number",
"description": "The connections the forge's database pool keeps idle, ready for the next request. They count against the store's 100 connections, shared on its machine, so keep this well under the open-connection cap.",
"trusted": false,
"minimum": 0,
"default": 10
},
"database-connection-lifetime": {
"type": "string",
"description": "How long the forge's database pool keeps one connection before it closes and reopens it, as a duration such as 5m. A shorter lifetime gives back connections of the store's 100 connections, shared on its machine, sooner.",
"trusted": false,
"default": "5m"
},
"url": {
"type": "string",
"description": "The address the module's tools reach the forge at, as a URL; unset, the code falls back to its environment, else this machine's gitea on its port.",
@@ -286,6 +306,9 @@
{
"id": "server",
"type": "container",
"restart-on": [
"server-env"
],
"health": {
"kind": "http",
"endpoint": "web",
+16 -3
View File
@@ -3,11 +3,24 @@ import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
// The forge's own database pool is capped (issue 1966): the store's connections are shared by every module on the machine.
// The caps are config values placed into the env-file, and a changed env-file recreates the container.
const env = readFileSync(new URL("../files/server.env", import.meta.url), "utf8");
const manifest = JSON.parse(readFileSync(new URL("../module.json", import.meta.url), "utf8"));
test("the forge caps its database pool", () => {
test("the forge's pool caps are config values, placed in server.env, and a change recreates the container", () => {
const lines = env.split("\n");
for (const line of ["GITEA__database__MAX_OPEN_CONNS=40", "GITEA__database__MAX_IDLE_CONNS=10", "GITEA__database__CONN_MAX_LIFETIME=5m"]) {
assert.ok(lines.includes(line), `server.env lacks ${line}`);
const placed: Record<string, [string, unknown]> = {
GITEA__database__MAX_OPEN_CONNS: ["database-max-open-connections", 40],
GITEA__database__MAX_IDLE_CONNS: ["database-max-idle-connections", 10],
GITEA__database__CONN_MAX_LIFETIME: ["database-connection-lifetime", "5m"],
};
for (const [name, [key, def]] of Object.entries(placed)) {
assert.ok(lines.includes(`${name}=\${config:${key}}`), `server.env does not place ${key} at ${name}`);
assert.equal(manifest.config[key].default, def);
assert.equal(manifest.config[key].trusted, false);
assert.ok(manifest.config[key].description.includes("100 connections"));
}
assert.equal(manifest.config["database-max-open-connections"].minimum, 1);
const server = manifest.resources.find((r: { id: string }) => r.id === "server");
assert.deepEqual(server["restart-on"], ["server-env"]);
});