Files
ttstd-infinite-canvas/backend/internal/database/postgres_connection_linux_test.go
Catlaxy AI 6dedc5973c fix(database): 数据库连接 - 消费 libpq keepalive 参数并默认限制闲置事务 (#737)
pgx 不识别 DSN 中 libpq 的 keepalives* 参数,会作为服务端运行参数发送导致连接被拒;改为在建连前消费并应用到 TCP 拨号器。
未显式配置时为每条连接默认设置 idle_in_transaction_session_timeout=5min,避免卡住的事务长期持锁;DSN 显式配置时以 DSN 为准。
建连钩子先复制共享的 RuntimeParams,避免多条连接之间累积修改。

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 22:04:27 +08:00

82 lines
2.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package database
import (
"net"
"os"
"strconv"
"sync"
"syscall"
"testing"
)
// clientSocketKeepalive 在当前进程中按端口找到客户端 socket,读取其 keepalive 选项。
func clientSocketKeepalive(port int) ([4]int, bool) {
entries, err := os.ReadDir("/proc/self/fd")
if err != nil {
return [4]int{}, false
}
for _, entry := range entries {
fd, err := strconv.Atoi(entry.Name())
if err != nil {
continue
}
addr, err := syscall.Getsockname(fd)
inet, ok := addr.(*syscall.SockaddrInet4)
if err != nil || !ok || inet.Port != port {
continue
}
var values [4]int
for i, option := range [][2]int{
{syscall.SOL_SOCKET, syscall.SO_KEEPALIVE},
{syscall.IPPROTO_TCP, syscall.TCP_KEEPIDLE},
{syscall.IPPROTO_TCP, syscall.TCP_KEEPINTVL},
{syscall.IPPROTO_TCP, syscall.TCP_KEEPCNT},
} {
if values[i], err = syscall.GetsockoptInt(fd, option[0], option[1]); err != nil {
return [4]int{}, false
}
}
return values, true
}
return [4]int{}, false
}
func TestPostgresOpenAppliesLibpqKeepaliveToSocket(t *testing.T) {
for _, tc := range []struct {
name, params string
want [4]int
}{
{"explicit", "keepalives_idle=90 keepalives_interval=20 keepalives_count=6", [4]int{1, 90, 20, 6}},
{"partial keeps Go defaults", "keepalives_idle=45", [4]int{1, 45, 15, 9}},
{"no params keeps Go defaults", "", [4]int{1, 15, 15, 9}},
{"disabled", "keepalives=0", [4]int{0, -1, -1, -1}},
} {
t.Run(tc.name, func(t *testing.T) {
server := newFakePostgresServer(t)
var mu sync.Mutex
var observed [][4]int
server.onStartup = func(conn net.Conn) {
// 已收到启动消息,说明客户端拨号已完成且 socket 选项已设置。
values, ok := clientSocketKeepalive(conn.RemoteAddr().(*net.TCPAddr).Port)
if !ok {
values = [4]int{-2, -2, -2, -2}
}
mu.Lock()
observed = append(observed, values)
mu.Unlock()
}
openFakePostgres(t, server, tc.params, 0)
mu.Lock()
defer mu.Unlock()
for _, values := range observed {
if values[0] == -2 {
t.Fatal("client socket not found")
}
if values[0] != tc.want[0] || (tc.want[0] == 1 && values != tc.want) {
t.Fatalf("keepalive (enabled, idle, interval, count) = %v, want %v", values, tc.want)
}
}
})
}
}