diff --git a/modules/gitea/files/server.env b/modules/gitea/files/server.env index 59c2a9686..164b0c40b 100644 --- a/modules/gitea/files/server.env +++ b/modules/gitea/files/server.env @@ -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} diff --git a/modules/gitea/module.json b/modules/gitea/module.json index ba21ac17c..7d803c79c 100644 --- a/modules/gitea/module.json +++ b/modules/gitea/module.json @@ -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", diff --git a/modules/gitea/test/pool-cap.test.ts b/modules/gitea/test/pool-cap.test.ts index fc19701c5..0634dabed 100644 --- a/modules/gitea/test/pool-cap.test.ts +++ b/modules/gitea/test/pool-cap.test.ts @@ -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 = { + 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"]); });