fix(core): join running embedded Postgres correctly
Read the live port from PostgreSQL's actual postmaster.pid field so extension TaskStore boot reuses the existing server instead of wedging on a colliding start.
This commit is contained in:
7
.changeset/fix-extension-taskstore-postmaster-port.md
Normal file
7
.changeset/fix-extension-taskstore-postmaster-port.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
summary: Prevent fn_task_show timeouts when another Fusion process already owns embedded PostgreSQL.
|
||||||
|
category: fix
|
||||||
|
dev: Reads the port from PostgreSQL's actual postmaster.pid line 4 field before joining the running instance.
|
||||||
@@ -808,7 +808,7 @@ describe("embedded-lifecycle: startup race (cross-process)", () => {
|
|||||||
async start() {
|
async start() {
|
||||||
writeFileSync(
|
writeFileSync(
|
||||||
join(dataDir, "postmaster.pid"),
|
join(dataDir, "postmaster.pid"),
|
||||||
["12345", dataDir, "/tmp", "localhost", "55440", "5432101", String(Date.now())].join("\n") + "\n",
|
["12345", dataDir, String(Date.now()), "55440", "/tmp", "localhost", "5432101", "ready"].join("\n") + "\n",
|
||||||
);
|
);
|
||||||
throw new Error('lock file "postmaster.pid" already exists');
|
throw new Error('lock file "postmaster.pid" already exists');
|
||||||
}
|
}
|
||||||
@@ -864,7 +864,7 @@ describe("embedded-lifecycle: startup race only joins on a lock collision", () =
|
|||||||
// A postmaster.pid exists (ours, or a racer's) but the failure is NOT a lock collision.
|
// A postmaster.pid exists (ours, or a racer's) but the failure is NOT a lock collision.
|
||||||
writeFileSync(
|
writeFileSync(
|
||||||
join(dataDir, "postmaster.pid"),
|
join(dataDir, "postmaster.pid"),
|
||||||
["12345", dataDir, "/tmp", "localhost", "55442", "5432101", String(Date.now())].join("\n") + "\n",
|
["12345", dataDir, String(Date.now()), "55442", "/tmp", "localhost", "5432101", "ready"].join("\n") + "\n",
|
||||||
);
|
);
|
||||||
throw new Error("could not start postgres: readiness poll timed out");
|
throw new Error("could not start postgres: readiness poll timed out");
|
||||||
}
|
}
|
||||||
@@ -891,7 +891,7 @@ describe("embedded-lifecycle: startup race only joins on a lock collision", () =
|
|||||||
async start() {
|
async start() {
|
||||||
writeFileSync(
|
writeFileSync(
|
||||||
join(dataDir, "postmaster.pid"),
|
join(dataDir, "postmaster.pid"),
|
||||||
["12345", dataDir, "/tmp", "localhost", "55444", "5432101", String(Date.now())].join("\n") + "\n",
|
["12345", dataDir, String(Date.now()), "55444", "/tmp", "localhost", "5432101", "ready"].join("\n") + "\n",
|
||||||
);
|
);
|
||||||
throw new Error('lock file "postmaster.pid" already exists');
|
throw new Error('lock file "postmaster.pid" already exists');
|
||||||
}
|
}
|
||||||
@@ -918,7 +918,7 @@ describe("embedded-lifecycle: join-path database verify is best-effort", () => {
|
|||||||
// A port nothing is listening on: the verify's probe cannot succeed.
|
// A port nothing is listening on: the verify's probe cannot succeed.
|
||||||
writeFileSync(
|
writeFileSync(
|
||||||
join(dataDir, "postmaster.pid"),
|
join(dataDir, "postmaster.pid"),
|
||||||
["12345", dataDir, "/tmp", "localhost", "55441", "5432101", String(Date.now())].join("\n") + "\n",
|
["12345", dataDir, String(Date.now()), "55441", "/tmp", "localhost", "5432101", "ready"].join("\n") + "\n",
|
||||||
);
|
);
|
||||||
const logLines: string[] = [];
|
const logLines: string[] = [];
|
||||||
|
|
||||||
@@ -1027,28 +1027,30 @@ describe("embedded-lifecycle: startup timeout (P1 #24)", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("embedded-lifecycle: readPortFromPostmasterPid (P1 code-review fix)", () => {
|
describe("embedded-lifecycle: readPortFromPostmasterPid (P1 code-review fix)", () => {
|
||||||
it("reads the TCP port from line 5 (index 4) of postmaster.pid", () => {
|
it("reads the TCP port from PostgreSQL's real line 4 (index 3) postmaster.pid layout", () => {
|
||||||
const dir = mkdtempSync(join(tmpdir(), "fusion-embedded-pid-"));
|
const dir = mkdtempSync(join(tmpdir(), "fusion-embedded-pid-"));
|
||||||
try {
|
try {
|
||||||
const { writeFileSync } = require("node:fs");
|
const { writeFileSync } = require("node:fs");
|
||||||
// Standard PostgreSQL postmaster.pid format:
|
// Standard PostgreSQL postmaster.pid format:
|
||||||
// Line 1: PID
|
// Line 1: PID
|
||||||
// Line 2: data directory
|
// Line 2: data directory
|
||||||
// Line 3: unix socket directory
|
// Line 3: postmaster start timestamp
|
||||||
// Line 4: listen address
|
// Line 4: port number
|
||||||
// Line 5: port number
|
// Line 5: unix socket directory
|
||||||
// Line 6: shared memory key
|
// Line 6: listen address
|
||||||
// Line 7: postmaster start timestamp
|
// Line 7: shared memory key and id
|
||||||
|
// Line 8: status
|
||||||
writeFileSync(
|
writeFileSync(
|
||||||
join(dir, "postmaster.pid"),
|
join(dir, "postmaster.pid"),
|
||||||
[
|
[
|
||||||
"12345",
|
"12345",
|
||||||
"/home/user/.fusion/embedded-postgres/default",
|
"/home/user/.fusion/embedded-postgres/default",
|
||||||
|
"1784361395",
|
||||||
|
"55432",
|
||||||
"/tmp",
|
"/tmp",
|
||||||
"localhost",
|
"localhost",
|
||||||
"55432",
|
"18446744071752735336 19857409",
|
||||||
"5432101",
|
"ready",
|
||||||
String(Date.now()),
|
|
||||||
].join("\n") + "\n",
|
].join("\n") + "\n",
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1065,7 +1067,7 @@ describe("embedded-lifecycle: readPortFromPostmasterPid (P1 code-review fix)", (
|
|||||||
const { writeFileSync } = require("node:fs");
|
const { writeFileSync } = require("node:fs");
|
||||||
writeFileSync(
|
writeFileSync(
|
||||||
join(dir, "postmaster.pid"),
|
join(dir, "postmaster.pid"),
|
||||||
["12345", "/data", "/tmp", "localhost", "not-a-port", "5432101"].join("\n") + "\n",
|
["12345", "/data", "1784361395", "not-a-port", "/tmp", "localhost", "5432101", "ready"].join("\n") + "\n",
|
||||||
);
|
);
|
||||||
expect(readPortFromPostmasterPid(dir)).toBeNull();
|
expect(readPortFromPostmasterPid(dir)).toBeNull();
|
||||||
} finally {
|
} finally {
|
||||||
@@ -1082,16 +1084,15 @@ describe("embedded-lifecycle: readPortFromPostmasterPid (P1 code-review fix)", (
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does NOT read line 3 (index 2, socket dir) as the port", () => {
|
it("does NOT read line 3 (index 2, start timestamp) as the port", () => {
|
||||||
// Regression: the bug read lines[2] (socket dir) which is never the port.
|
// The start timestamp is numeric, so parsing the wrong adjacent line would
|
||||||
// If the socket dir happened to contain digits, parseInt would produce
|
// produce a plausible-looking but unusable port.
|
||||||
// a wrong port. This test ensures we skip past it.
|
|
||||||
const dir = mkdtempSync(join(tmpdir(), "fusion-embedded-pid-"));
|
const dir = mkdtempSync(join(tmpdir(), "fusion-embedded-pid-"));
|
||||||
try {
|
try {
|
||||||
const { writeFileSync } = require("node:fs");
|
const { writeFileSync } = require("node:fs");
|
||||||
writeFileSync(
|
writeFileSync(
|
||||||
join(dir, "postmaster.pid"),
|
join(dir, "postmaster.pid"),
|
||||||
["12345", "/data", "/var/run/postgresql", "localhost", "5433", "5432101"].join("\n") + "\n",
|
["12345", "/data", "1784361395", "5433", "/var/run/postgresql", "localhost", "5432101", "ready"].join("\n") + "\n",
|
||||||
);
|
);
|
||||||
const port = readPortFromPostmasterPid(dir);
|
const port = readPortFromPostmasterPid(dir);
|
||||||
expect(port).toBe(5433);
|
expect(port).toBe(5433);
|
||||||
|
|||||||
@@ -927,16 +927,18 @@ const runningInstances = new Map<string, { port: number; database: string }>();
|
|||||||
* Read the port from a postmaster.pid file. The standard PostgreSQL format is:
|
* Read the port from a postmaster.pid file. The standard PostgreSQL format is:
|
||||||
* Line 1 (index 0): PID
|
* Line 1 (index 0): PID
|
||||||
* Line 2 (index 1): Data directory path
|
* Line 2 (index 1): Data directory path
|
||||||
* Line 3 (index 2): Unix socket directory
|
* Line 3 (index 2): Postmaster start timestamp
|
||||||
* Line 4 (index 3): Listen address (e.g. localhost or *)
|
* Line 4 (index 3): Port number
|
||||||
* Line 5 (index 4): Port number
|
* Line 5 (index 4): Unix socket directory
|
||||||
* Line 6 (index 5): Shared memory key
|
* Line 6 (index 5): Listen address (e.g. localhost or *)
|
||||||
* Line 7 (index 6): Postmaster start timestamp
|
* Line 7 (index 6): Shared memory key and id
|
||||||
|
* Line 8 (index 7): Status
|
||||||
*
|
*
|
||||||
* FNXC:PostgresCutover 2026-06-27-14:30 (fix code-review P1):
|
* FNXC:PostgresCutover 2026-06-27-14:30 (fix code-review P1):
|
||||||
* Previously read line 3 (index 2, the socket dir) which is never a port
|
* The earlier correction still encoded the wrong field order and read line 5
|
||||||
* number, so singleton detection via postmaster.pid ALWAYS failed. Fixed to
|
* (the socket directory). Read PostgreSQL's actual line 4 port field so a
|
||||||
* read line 5 (index 4, the TCP port).
|
* second Fusion process joins the running postmaster instead of attempting a
|
||||||
|
* colliding start that can wedge extension TaskStore boot.
|
||||||
*
|
*
|
||||||
* Returns null if the file cannot be read or parsed.
|
* Returns null if the file cannot be read or parsed.
|
||||||
*/
|
*/
|
||||||
@@ -944,8 +946,8 @@ export function readPortFromPostmasterPid(dataDir: string): number | null {
|
|||||||
try {
|
try {
|
||||||
const content = readFileSync(join(dataDir, "postmaster.pid"), "utf-8");
|
const content = readFileSync(join(dataDir, "postmaster.pid"), "utf-8");
|
||||||
const lines = content.split("\n");
|
const lines = content.split("\n");
|
||||||
// Line 5 (index 4) is the TCP port in standard PostgreSQL postmaster.pid
|
// Line 4 (index 3) is the TCP port in standard PostgreSQL postmaster.pid.
|
||||||
const portStr = lines[4]?.trim();
|
const portStr = lines[3]?.trim();
|
||||||
if (portStr) {
|
if (portStr) {
|
||||||
const port = parseInt(portStr, 10);
|
const port = parseInt(portStr, 10);
|
||||||
if (!isNaN(port) && port > 0) return port;
|
if (!isNaN(port) && port > 0) return port;
|
||||||
|
|||||||
Reference in New Issue
Block a user