为 PostgreSQL 连接增加可配置的事务空闲与锁等待超时,并在请求取消或 Worker 退出后使用独立有界上下文回滚事务。\n\n恢复过期任务时按批次使用 SKIP LOCKED,周期重建缺失或终态 River Job;锁等待超时只在确认尚未提交上游时安全重排,避免重复执行和重复结算。\n\n验证:完整 Go 测试、go vet、生产 Kustomize 渲染、gofmt 与 git diff --check 均通过。
110 lines
3.4 KiB
Go
110 lines
3.4 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/jackc/pgx/v5/pgconn"
|
|
)
|
|
|
|
func TestPostgresPoolConfigSetsDiagnosticAndConnectTimeout(t *testing.T) {
|
|
config, err := postgresPoolConfig("postgresql://gateway:password@localhost:5432/gateway?sslmode=disable")
|
|
if err != nil {
|
|
t.Fatalf("parse PostgreSQL pool config: %v", err)
|
|
}
|
|
if config.ConnConfig.ConnectTimeout != 5*time.Second {
|
|
t.Fatalf("connect timeout = %s, want 5s", config.ConnConfig.ConnectTimeout)
|
|
}
|
|
if applicationName := config.ConnConfig.RuntimeParams["application_name"]; applicationName != "easyai-ai-gateway" {
|
|
t.Fatalf("application_name = %q, want easyai-ai-gateway", applicationName)
|
|
}
|
|
}
|
|
|
|
func TestPostgresPoolConfigRejectsMalformedURL(t *testing.T) {
|
|
if _, err := postgresPoolConfig("://malformed"); err == nil {
|
|
t.Fatal("expected malformed PostgreSQL URL to fail")
|
|
}
|
|
}
|
|
|
|
func TestPostgresPoolConfigWarmsBoundedIdleConnections(t *testing.T) {
|
|
config, err := postgresPoolConfig(
|
|
"postgresql://gateway:password@localhost:5432/gateway?sslmode=disable",
|
|
24,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("parse PostgreSQL pool config: %v", err)
|
|
}
|
|
if config.MaxConns != 24 || config.MinIdleConns != 24 {
|
|
t.Fatalf(
|
|
"pool bounds max=%d minIdle=%d, want 24/24",
|
|
config.MaxConns,
|
|
config.MinIdleConns,
|
|
)
|
|
}
|
|
|
|
small, err := postgresPoolConfig(
|
|
"postgresql://gateway:password@localhost:5432/gateway?sslmode=disable",
|
|
4,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("parse small PostgreSQL pool config: %v", err)
|
|
}
|
|
if small.MinIdleConns != 4 {
|
|
t.Fatalf("small pool minIdle=%d, want 4", small.MinIdleConns)
|
|
}
|
|
}
|
|
|
|
func TestPostgresPoolConfigSetsBoundedTransactionTimeouts(t *testing.T) {
|
|
config, err := postgresPoolConfigWithOptions(
|
|
"postgresql://gateway:password@localhost:5432/gateway?sslmode=disable",
|
|
PostgresPoolOptions{
|
|
MaxConns: 32,
|
|
IdleInTransactionTimeout: 60 * time.Second,
|
|
LockTimeout: 30 * time.Second,
|
|
},
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("parse PostgreSQL pool config: %v", err)
|
|
}
|
|
if got := config.ConnConfig.RuntimeParams["idle_in_transaction_session_timeout"]; got != "60000ms" {
|
|
t.Fatalf("idle transaction timeout = %q, want 60000ms", got)
|
|
}
|
|
if got := config.ConnConfig.RuntimeParams["lock_timeout"]; got != "30000ms" {
|
|
t.Fatalf("lock timeout = %q, want 30000ms", got)
|
|
}
|
|
if config.MaxConns != 32 || config.MinIdleConns != 32 {
|
|
t.Fatalf("pool bounds max=%d minIdle=%d, want 32/32", config.MaxConns, config.MinIdleConns)
|
|
}
|
|
}
|
|
|
|
func TestIsPostgresUnavailableClassifiesConnectivityFailures(t *testing.T) {
|
|
for _, testCase := range []struct {
|
|
name string
|
|
err error
|
|
}{
|
|
{name: "deadline", err: context.DeadlineExceeded},
|
|
{name: "connection exception", err: &pgconn.PgError{Code: "08006"}},
|
|
{name: "too many connections", err: &pgconn.PgError{Code: "53300"}},
|
|
{name: "cannot connect now", err: &pgconn.PgError{Code: "57P03"}},
|
|
} {
|
|
t.Run(testCase.name, func(t *testing.T) {
|
|
if !IsPostgresUnavailable(testCase.err) {
|
|
t.Fatalf("error %v was not classified as PostgreSQL unavailable", testCase.err)
|
|
}
|
|
})
|
|
}
|
|
if IsPostgresUnavailable(&pgconn.PgError{Code: "42601"}) {
|
|
t.Fatal("SQL syntax error was incorrectly classified as PostgreSQL unavailable")
|
|
}
|
|
}
|
|
|
|
func TestIsPostgresLockTimeout(t *testing.T) {
|
|
if !IsPostgresLockTimeout(&pgconn.PgError{Code: "55P03"}) {
|
|
t.Fatal("lock timeout was not classified")
|
|
}
|
|
if IsPostgresLockTimeout(&pgconn.PgError{Code: "57014"}) {
|
|
t.Fatal("query cancellation was classified as lock timeout")
|
|
}
|
|
}
|