feat: server transfer rotation

This commit is contained in:
naiba
2026-05-25 10:17:34 +00:00
parent 37b6db806f
commit 6b88cdb012
43 changed files with 7072 additions and 134 deletions
+23 -2
View File
@@ -6,6 +6,7 @@ import (
"slices"
"strconv"
"strings"
"sync/atomic"
"time"
"github.com/gin-gonic/gin"
@@ -37,8 +38,21 @@ func (c *Common) GetID() uint64 {
return c.ID
}
// GetUserID 原子读取所属用户 ID。Server.UserID 会在 ServerTransfer 的
// Register/revertTransition 流程里被实时改写以反映新所有者,同时 auth
// 热路径在每次 agent RPC 都会读它。任何并发读必须走 atomic,否则与 SetUserID
// 一起会被 go race detector 识别为 data race(见
// TestServerUserIDConcurrentAccessIsRaceFree)。
func (c *Common) GetUserID() uint64 {
return c.UserID
return atomic.LoadUint64(&c.UserID)
}
// SetUserID 原子改写所属用户 ID。仅在「server 已经在 in-memory cache 里」
// 的写入路径(ServerTransfer.Register / revertTransition)需要用 atomic
// 保证可见性;普通 GORM AfterFind / Create 因为没有并发读所以可以直接赋
// 值。配合 GetUserID 形成 atomic-only 的并发访问协议。
func (c *Common) SetUserID(uid uint64) {
atomic.StoreUint64(&c.UserID, uid)
}
func (c *Common) HasPermission(ctx *gin.Context) bool {
@@ -52,7 +66,14 @@ func (c *Common) HasPermission(ctx *gin.Context) bool {
return true
}
return user.ID == c.UserID
// 必须走 GetUserID 而不是裸读 c.UserID — Server.UserID 在
// ServerTransfer.Register / revertTransition 里会被 atomic.StoreUint64
// 改写,dashboard 各 controller 在 listHandler post-filter 这条热路径上
// 高频对同一 *Server 调 HasPermission。裸读会与 SetUserID 形成 data
// raceTestCommonHasPermissionConcurrentWithSetUserIDIsRaceFree 在
// -race 下钉死该不变量),并且在 transfer 切换瞬间可能给出错误的权限
// 判断。
return user.ID == c.GetUserID()
}
type CommonInterface interface {