Use config snapshots for management reload

This commit is contained in:
LTbinglingfeng
2026-06-17 02:40:34 +08:00
parent 7f026e1aab
commit a4756ab7a9
4 changed files with 43 additions and 25 deletions
@@ -28,7 +28,6 @@ import (
geminiAuth "github.com/router-for-me/CLIProxyAPI/v7/internal/auth/gemini"
"github.com/router-for-me/CLIProxyAPI/v7/internal/auth/kimi"
xaiauth "github.com/router-for-me/CLIProxyAPI/v7/internal/auth/xai"
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
"github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces"
"github.com/router-for-me/CLIProxyAPI/v7/internal/misc"
"github.com/router-for-me/CLIProxyAPI/v7/internal/registry"
@@ -1267,13 +1266,11 @@ func (h *Handler) PatchAuthFileStatus(c *gin.Context) {
c.JSON(http.StatusNotFound, gin.H{"error": "config api key entry not found"})
return
}
if errSave := config.SaveConfigPreserveComments(h.configFilePath, h.cfg); errSave != nil {
h.mu.Unlock()
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to save config: %v", errSave)})
cfgSnapshot, okSnapshot := h.saveConfigAndSnapshotLocked(c)
h.mu.Unlock()
if !okSnapshot {
return
}
cfgSnapshot := h.cfg
h.mu.Unlock()
h.reloadConfigAfterManagementSave(ctx, cfgSnapshot)
if h.tokenStore != nil {
_ = h.tokenStore.Delete(ctx, targetAuth.ID)
+30 -7
View File
@@ -152,8 +152,29 @@ func (h *Handler) SetConfigReloadHook(hook func(context.Context, *config.Config)
h.mu.Unlock()
}
func (h *Handler) reloadConfigAfterManagementSave(ctx context.Context, cfg *config.Config) {
if h == nil || cfg == nil {
// snapshotConfigLocked clones the full runtime config while h.mu is held.
// Callers must hold h.mu.
func (h *Handler) snapshotConfigLocked() *config.Config {
if h == nil || h.cfg == nil {
return nil
}
return h.cfg.CloneForRuntime()
}
// saveConfigAndSnapshotLocked saves h.cfg and returns a full runtime config snapshot.
// Callers must hold h.mu.
func (h *Handler) saveConfigAndSnapshotLocked(c *gin.Context) (*config.Config, bool) {
if errSave := config.SaveConfigPreserveComments(h.configFilePath, h.cfg); errSave != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to save config: %v", errSave)})
return nil, false
}
return h.snapshotConfigLocked(), true
}
// reloadConfigAfterManagementSave reloads from an independent config snapshot.
// Callers must pass a full Config clone captured immediately after a successful save.
func (h *Handler) reloadConfigAfterManagementSave(ctx context.Context, cfgSnapshot *config.Config) {
if h == nil || cfgSnapshot == nil {
return
}
h.mu.Lock()
@@ -161,16 +182,18 @@ func (h *Handler) reloadConfigAfterManagementSave(ctx context.Context, cfg *conf
host := h.pluginHost
h.mu.Unlock()
if hook != nil {
hook(ctx, cfg)
hook(ctx, cfgSnapshot)
return
}
if host != nil {
host.ApplyConfig(ctx, cfg)
host.ApplyConfig(ctx, cfgSnapshot)
}
}
func (h *Handler) reloadConfigAfterManagementSaveAsync(ctx context.Context, cfg *config.Config) {
if h == nil || cfg == nil {
// reloadConfigAfterManagementSaveAsync reloads from an independent config snapshot.
// Callers must pass a full Config clone captured immediately after a successful save.
func (h *Handler) reloadConfigAfterManagementSaveAsync(ctx context.Context, cfgSnapshot *config.Config) {
if h == nil || cfgSnapshot == nil {
return
}
reloadCtx := context.Background()
@@ -183,7 +206,7 @@ func (h *Handler) reloadConfigAfterManagementSaveAsync(ctx context.Context, cfg
log.WithField("panic", recovered).Error("management: async config reload panicked")
}
}()
h.reloadConfigAfterManagementSave(reloadCtx, cfg)
h.reloadConfigAfterManagementSave(reloadCtx, cfgSnapshot)
}()
}
@@ -226,9 +226,9 @@ func (h *Handler) installPluginFromStore(c *gin.Context, goos, goarch string) {
if errInstall != nil {
if unloadedBeforeWrite {
h.mu.Lock()
reloadCfg := h.cfg
cfgSnapshot := h.snapshotConfigLocked()
h.mu.Unlock()
h.reloadConfigAfterManagementSave(c.Request.Context(), reloadCfg)
h.reloadConfigAfterManagementSave(c.Request.Context(), cfgSnapshot)
}
if errors.Is(errInstall, pluginstore.ErrLoadedPluginLocked) {
c.JSON(http.StatusConflict, gin.H{
@@ -271,10 +271,10 @@ func (h *Handler) installPluginFromStore(c *gin.Context, goos, goarch string) {
})
return
}
reloadCfg := h.cfg
cfgSnapshot := h.snapshotConfigLocked()
h.mu.Unlock()
h.reloadConfigAfterManagementSaveAsync(c.Request.Context(), reloadCfg)
h.reloadConfigAfterManagementSaveAsync(c.Request.Context(), cfgSnapshot)
log.WithFields(log.Fields{
"plugin_id": result.ID,
"source_id": source.ID,
+6 -8
View File
@@ -226,15 +226,13 @@ func (h *Handler) PatchPluginEnabled(c *gin.Context) {
return
}
h.cfg.Plugins.Configs[id] = updated
if errSave := config.SaveConfigPreserveComments(h.configFilePath, h.cfg); errSave != nil {
h.mu.Unlock()
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to save config: %v", errSave)})
cfgSnapshot, okSnapshot := h.saveConfigAndSnapshotLocked(c)
h.mu.Unlock()
if !okSnapshot {
return
}
reloadCfg := h.cfg
h.mu.Unlock()
h.reloadConfigAfterManagementSaveAsync(c.Request.Context(), reloadCfg)
h.reloadConfigAfterManagementSaveAsync(c.Request.Context(), cfgSnapshot)
c.JSON(http.StatusOK, gin.H{"status": "ok"})
}
@@ -375,10 +373,10 @@ func (h *Handler) DeletePlugin(c *gin.Context) {
return
}
}
reloadCfg := h.cfg
cfgSnapshot := h.snapshotConfigLocked()
h.mu.Unlock()
h.reloadConfigAfterManagementSaveAsync(c.Request.Context(), reloadCfg)
h.reloadConfigAfterManagementSaveAsync(c.Request.Context(), cfgSnapshot)
c.JSON(http.StatusOK, gin.H{
"status": "deleted",
"id": htmlsanitize.String(id),