From 21f72da7a8ab3b50288f7e6ab694abe45ff40515 Mon Sep 17 00:00:00 2001 From: wangbo Date: Wed, 5 Aug 2026 22:57:33 +0800 Subject: [PATCH] =?UTF-8?q?fix(routing):=20=E4=BF=AE=E6=AD=A3=20Worker=20?= =?UTF-8?q?=E5=BF=83=E8=B7=B3=E6=97=B6=E9=97=B4=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 生产 shadow 发布证明 PostgreSQL 将未显式定型的时间参数推断为 interval,导致 Worker 列表和容量查询返回 SQLSTATE 42883。 改为在 Go 中计算心跳截止时间并以 timestamptz 参数查询,补充真实 PostgreSQL 集成回归。 --- apps/api/internal/store/execution_pools.go | 12 ++++++----- .../store/execution_pools_integration_test.go | 20 +++++++++++++++++++ 2 files changed, 27 insertions(+), 5 deletions(-) create mode 100644 apps/api/internal/store/execution_pools_integration_test.go diff --git a/apps/api/internal/store/execution_pools.go b/apps/api/internal/store/execution_pools.go index 52f79af..65b4f72 100644 --- a/apps/api/internal/store/execution_pools.go +++ b/apps/api/internal/store/execution_pools.go @@ -101,14 +101,15 @@ func (s *Store) ListWorkers(ctx context.Context, now time.Time) ([]executionpool if now.IsZero() { now = time.Now() } + cutoff := now.Add(-workerHeartbeatStaleAfter) rows, err := s.pool.Query(ctx, ` SELECT worker_id, instance_id, pool_id, endpoint, protocol_version, revision, capabilities, allocated_capacity, safe_capacity, heavy_capacity, active_tasks, pressure_state, heartbeat_at, load_sampled_at FROM gateway_worker_instances WHERE status = 'active' - AND heartbeat_at > $1 - $2::interval -ORDER BY pool_id, instance_id`, now, workerHeartbeatStaleAfter.String()) + AND heartbeat_at > $1::timestamptz +ORDER BY pool_id, instance_id`, cutoff) if err != nil { return nil, err } @@ -346,6 +347,7 @@ func (s *Store) ListCapacity(ctx context.Context, now time.Time) ([]executionpoo if now.IsZero() { now = time.Now() } + cutoff := now.Add(-workerHeartbeatStaleAfter) rows, err := s.pool.Query(ctx, ` SELECT pool.pool_id, COUNT(worker.instance_id)::int, @@ -358,12 +360,12 @@ SELECT pool.pool_id, COALESCE(MAX(worker.load_sampled_at), $1) FROM gateway_execution_pools pool LEFT JOIN gateway_worker_instances worker - ON worker.pool_id = pool.pool_id + ON worker.pool_id = pool.pool_id AND worker.status = 'active' - AND worker.heartbeat_at > $1 - $2::interval + AND worker.heartbeat_at > $2::timestamptz WHERE pool.state = 'active' GROUP BY pool.pool_id -ORDER BY pool.pool_id`, now, workerHeartbeatStaleAfter.String()) +ORDER BY pool.pool_id`, now, cutoff) if err != nil { return nil, err } diff --git a/apps/api/internal/store/execution_pools_integration_test.go b/apps/api/internal/store/execution_pools_integration_test.go new file mode 100644 index 0000000..303364d --- /dev/null +++ b/apps/api/internal/store/execution_pools_integration_test.go @@ -0,0 +1,20 @@ +package store + +import ( + "context" + "testing" + "time" +) + +func TestExecutionPoolQueriesUseTimestampCutoff(t *testing.T) { + db := billingV2IntegrationStore(t) + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + if _, err := db.ListWorkers(ctx, time.Now()); err != nil { + t.Fatalf("list workers with timestamp cutoff: %v", err) + } + if _, err := db.ListCapacity(ctx, time.Now()); err != nil { + t.Fatalf("list capacity with timestamp cutoff: %v", err) + } +}