Files
easyai-ai-gateway/apps/api/internal/store/leadership.go
T
wangbo e05922b0f4 feat(acceptance): 建立同构验收与弹性容量体系
实现本地三节点 K3s 同构环境、脱敏生产快照、Gemini 图片和多参考图视频协议模拟、统一验收报告及故障注入。\n\n新增 Worker 容量控制器、资源与连接预算、任务恢复保护,并将生产验收拆分为 validation 执行和人工 CAS 放量。\n\n验证包括 Go 全量测试、PostgreSQL HTTP 集成测试、go vet、OpenAPI、ShellCheck、前端检查、迁移及发布脚本测试。
2026-07-31 18:02:24 +08:00

99 lines
2.8 KiB
Go

package store
import (
"context"
"errors"
"sync"
"time"
"github.com/jackc/pgx/v5/pgxpool"
)
const leadershipReleaseTimeout = 5 * time.Second
const (
identityCoordinatorLeadershipKey = "easyai-gateway-identity-coordinator"
securityEventHeartbeatLeadershipKey = "easyai-gateway-security-event-heartbeat"
capacityControllerLeadershipKey = "easyai-gateway-capacity-controller"
)
// Leadership holds a PostgreSQL session advisory lock. Callers must keep the
// lease alive and release it when leadership is no longer needed.
type Leadership interface {
KeepAlive(context.Context) error
Release()
}
type postgresLeadership struct {
conn *pgxpool.Conn
key string
once sync.Once
}
func (s *Store) TryAcquireIdentityCoordinatorLeadership(ctx context.Context) (Leadership, bool, error) {
return s.tryAcquireLeadership(ctx, identityCoordinatorLeadershipKey)
}
func (s *Store) TryAcquireSecurityEventHeartbeatLeadership(ctx context.Context) (Leadership, bool, error) {
return s.tryAcquireLeadership(ctx, securityEventHeartbeatLeadershipKey)
}
func (s *Store) TryAcquireCapacityControllerLeadership(ctx context.Context) (Leadership, bool, error) {
return s.tryAcquireLeadership(ctx, capacityControllerLeadershipKey)
}
func (s *Store) tryAcquireLeadership(ctx context.Context, key string) (Leadership, bool, error) {
conn, err := s.pool.Acquire(ctx)
if err != nil {
return nil, false, err
}
var acquired bool
if err := conn.QueryRow(ctx,
`SELECT pg_try_advisory_lock(hashtextextended($1, 0))`,
key,
).Scan(&acquired); err != nil {
conn.Release()
return nil, false, err
}
if !acquired {
conn.Release()
return nil, false, nil
}
return &postgresLeadership{conn: conn, key: key}, true, nil
}
func (leadership *postgresLeadership) KeepAlive(ctx context.Context) error {
if leadership == nil || leadership.conn == nil {
return errors.New("leadership lease is closed")
}
var alive int
return leadership.conn.QueryRow(ctx, `SELECT 1`).Scan(&alive)
}
func (leadership *postgresLeadership) Release() {
if leadership == nil {
return
}
leadership.once.Do(func() {
unlockCtx, unlockCancel := context.WithTimeout(context.Background(), leadershipReleaseTimeout)
var unlocked bool
err := leadership.conn.QueryRow(unlockCtx,
`SELECT pg_advisory_unlock(hashtextextended($1, 0))`,
leadership.key,
).Scan(&unlocked)
unlockCancel()
if err != nil || !unlocked {
// A session-scoped advisory lock must never return to the pool when
// unlock is uncertain. Closing the connection lets PostgreSQL
// release the lock and forces the pool to replace the session.
conn := leadership.conn.Hijack()
closeCtx, closeCancel := context.WithTimeout(context.Background(), leadershipReleaseTimeout)
_ = conn.Close(closeCtx)
closeCancel()
} else {
leadership.conn.Release()
}
leadership.conn = nil
})
}