From b06ab4f27f05b873cf29f91d9b45ffd6fd2a988c Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Fri, 15 May 2026 14:38:15 +0800 Subject: [PATCH] fix(connection): handle stale connection state --- src/lib/connectionHealth.ts | 25 +++++++++++++++++++++++++ tests/connectionHealth.test.ts | 19 +++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 src/lib/connectionHealth.ts create mode 100644 tests/connectionHealth.test.ts diff --git a/src/lib/connectionHealth.ts b/src/lib/connectionHealth.ts new file mode 100644 index 0000000000..51b75f1492 --- /dev/null +++ b/src/lib/connectionHealth.ts @@ -0,0 +1,25 @@ +const CONNECTION_ERROR_PATTERNS = [ + "connection", + "broken pipe", + "reset by peer", + "timed out", + "closed", + "eof", + "i/o error", +]; + +export function staleConnectionMessage(error: unknown): string { + if (error instanceof Error) return error.message; + return String(error); +} + +export function shouldMarkDisconnected(error: unknown): boolean { + const message = staleConnectionMessage(error).toLowerCase(); + return CONNECTION_ERROR_PATTERNS.some((pattern) => message.includes(pattern)) || hasConnectionOsError(message); +} + +function hasConnectionOsError(message: string): boolean { + const osErrorCodes = new Set(["10053", "10054", "10057", "10058", "10060", "10061"]); + const match = message.match(/os error\s+(\d+)/); + return !!match && osErrorCodes.has(match[1]); +} diff --git a/tests/connectionHealth.test.ts b/tests/connectionHealth.test.ts new file mode 100644 index 0000000000..46948f4e96 --- /dev/null +++ b/tests/connectionHealth.test.ts @@ -0,0 +1,19 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { staleConnectionMessage, shouldMarkDisconnected } from "../src/lib/connectionHealth.ts"; + +test("metadata connection errors should turn off connected state", () => { + assert.equal(shouldMarkDisconnected(new Error("connection closed by server")), true); + assert.equal(shouldMarkDisconnected("broken pipe"), true); + assert.equal(shouldMarkDisconnected("operation timed out"), true); +}); + +test("non-connection metadata errors should not turn off connected state", () => { + assert.equal(shouldMarkDisconnected(new Error("permission denied for table users")), false); + assert.equal(shouldMarkDisconnected("syntax error near SELECT"), false); +}); + +test("stale connection errors keep the original backend message", () => { + assert.equal(staleConnectionMessage(new Error("connection reset by peer")), "connection reset by peer"); + assert.equal(staleConnectionMessage("closed"), "closed"); +});