diff --git a/pkg/ai/config.go b/pkg/ai/config.go index ea5c20dc..4dd21c9b 100644 --- a/pkg/ai/config.go +++ b/pkg/ai/config.go @@ -38,7 +38,7 @@ func providerLabel(provider string) string { } func LoadRuntimeConfig() (*RuntimeConfig, error) { - setting, err := model.GetGeneralSetting() + setting, err := model.GetGeneralSettingCached() if err != nil { return nil, err } diff --git a/pkg/ai/handler.go b/pkg/ai/handler.go index 1cd0b417..49852d7c 100644 --- a/pkg/ai/handler.go +++ b/pkg/ai/handler.go @@ -133,7 +133,7 @@ func HandleExecuteContinue(c *gin.Context) { } func HandleGetGeneralSetting(c *gin.Context) { - setting, err := model.GetGeneralSetting() + setting, err := model.GetGeneralSettingCached() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to load general setting: %v", err)}) return diff --git a/pkg/handlers/kubectl_terminal_handler.go b/pkg/handlers/kubectl_terminal_handler.go index fb51b0a6..66b3b225 100644 --- a/pkg/handlers/kubectl_terminal_handler.go +++ b/pkg/handlers/kubectl_terminal_handler.go @@ -50,7 +50,7 @@ func (h *KubectlTerminalHandler) HandleKubectlTerminalWebSocket(c *gin.Context) return } - setting, err := model.GetGeneralSetting() + setting, err := model.GetGeneralSettingCached() if err != nil { h.sendErrorMessage(conn, fmt.Sprintf("Failed to load settings: %v", err)) return diff --git a/pkg/handlers/node_terminal_handler.go b/pkg/handlers/node_terminal_handler.go index fbf22bca..7df3a225 100644 --- a/pkg/handlers/node_terminal_handler.go +++ b/pkg/handlers/node_terminal_handler.go @@ -61,7 +61,7 @@ func (h *NodeTerminalHandler) HandleNodeTerminalWebSocket(c *gin.Context) { h.sendErrorMessage(conn, fmt.Sprintf("Node %s not found", nodeName)) return } - setting, err := model.GetGeneralSetting() + setting, err := model.GetGeneralSettingCached() if err != nil { log.Printf("Failed to load general setting: %v", err) h.sendErrorMessage(conn, fmt.Sprintf("Failed to load settings: %v", err)) diff --git a/pkg/model/general_setting.go b/pkg/model/general_setting.go index 36ede60b..62dcf775 100644 --- a/pkg/model/general_setting.go +++ b/pkg/model/general_setting.go @@ -3,6 +3,7 @@ package model import ( "errors" "strings" + "sync" "github.com/zxh326/kite/pkg/common" "gorm.io/gorm" @@ -63,6 +64,46 @@ func DefaultGeneralAIModelByProvider(provider string) string { } } +// gsMu + gsCache implement a singleton cache for the GeneralSetting row. +// Read-path uses RLock (~10-25 ns); miss falls through to DB with double-check. +var ( + gsMu sync.RWMutex + gsCache *GeneralSetting +) + +// GetGeneralSettingCached returns the cached GeneralSetting singleton. +// On cache miss it falls back to GetGeneralSetting (DB + normalisation) +// and stores the result. Thread-safe via double-checked locking. +func GetGeneralSettingCached() (*GeneralSetting, error) { + gsMu.RLock() + if c := gsCache; c != nil { + gsMu.RUnlock() + return c, nil + } + gsMu.RUnlock() + + // Cache miss — acquire write lock and double-check. + gsMu.Lock() + defer gsMu.Unlock() + if gsCache != nil { + return gsCache, nil + } + s, err := GetGeneralSetting() + if err != nil { + return nil, err + } + gsCache = s + return s, nil +} + +// InvalidateGeneralSettingCache clears the cached singleton so the next +// read reloads from the database. Called from UpdateGeneralSetting. +func InvalidateGeneralSettingCache() { + gsMu.Lock() + gsCache = nil + gsMu.Unlock() +} + func GetGeneralSetting() (*GeneralSetting, error) { var setting GeneralSetting err := DB.First(&setting, 1).Error @@ -132,6 +173,7 @@ func UpdateGeneralSetting(updates map[string]interface{}) (*GeneralSetting, erro return nil, err } applyRuntimeGeneralSetting(setting) + InvalidateGeneralSettingCache() return setting, nil }