@@ -7,18 +7,25 @@ import (
77
88 "github.com/mudler/LocalAI/core/config"
99 "github.com/mudler/LocalAI/core/gallery"
10+ "github.com/mudler/LocalAI/core/services/advisorylock"
1011 "github.com/mudler/LocalAI/pkg/model"
1112 "github.com/mudler/LocalAI/pkg/system"
1213 "github.com/mudler/xlog"
14+ "gorm.io/gorm"
1315)
1416
1517// UpgradeChecker periodically checks for backend upgrades and optionally
1618// auto-upgrades them. It caches the last check results for API queries.
19+ //
20+ // In standalone mode it runs a simple ticker loop.
21+ // In distributed mode it uses a PostgreSQL advisory lock so that only one
22+ // frontend instance performs periodic checks and auto-upgrades at a time.
1723type UpgradeChecker struct {
1824 appConfig * config.ApplicationConfig
1925 modelLoader * model.ModelLoader
2026 galleries []config.Gallery
2127 systemState * system.SystemState
28+ db * gorm.DB // non-nil in distributed mode
2229
2330 checkInterval time.Duration
2431 stop chan struct {}
@@ -31,12 +38,15 @@ type UpgradeChecker struct {
3138}
3239
3340// NewUpgradeChecker creates a new UpgradeChecker service.
34- func NewUpgradeChecker (appConfig * config.ApplicationConfig , ml * model.ModelLoader ) * UpgradeChecker {
41+ // Pass db=nil for standalone mode, or a *gorm.DB for distributed mode
42+ // (uses advisory locks so only one instance runs periodic checks).
43+ func NewUpgradeChecker (appConfig * config.ApplicationConfig , ml * model.ModelLoader , db * gorm.DB ) * UpgradeChecker {
3544 return & UpgradeChecker {
3645 appConfig : appConfig ,
3746 modelLoader : ml ,
3847 galleries : appConfig .BackendGalleries ,
3948 systemState : appConfig .SystemState ,
49+ db : db ,
4050 checkInterval : 6 * time .Hour ,
4151 stop : make (chan struct {}),
4252 done : make (chan struct {}),
@@ -47,6 +57,10 @@ func NewUpgradeChecker(appConfig *config.ApplicationConfig, ml *model.ModelLoade
4757
4858// Run starts the upgrade checker loop. It waits 30 seconds after startup,
4959// performs an initial check, then re-checks every 6 hours.
60+ //
61+ // In distributed mode, periodic checks are guarded by a PostgreSQL advisory
62+ // lock so only one frontend instance runs them. On-demand triggers (TriggerCheck)
63+ // and the initial check always run locally for fast API response cache warming.
5064func (uc * UpgradeChecker ) Run (ctx context.Context ) {
5165 defer close (uc .done )
5266
@@ -59,23 +73,44 @@ func (uc *UpgradeChecker) Run(ctx context.Context) {
5973 case <- time .After (30 * time .Second ):
6074 }
6175
62- // First check
76+ // First check always runs locally (to warm the cache on this instance)
6377 uc .runCheck (ctx )
6478
65- // Periodic loop
66- ticker := time .NewTicker (uc .checkInterval )
67- defer ticker .Stop ()
68-
69- for {
70- select {
71- case <- ctx .Done ():
72- return
73- case <- uc .stop :
74- return
75- case <- ticker .C :
76- uc .runCheck (ctx )
77- case <- uc .triggerCh :
79+ if uc .db != nil {
80+ // Distributed mode: use advisory lock for periodic checks.
81+ // RunLeaderLoop ticks every checkInterval; only the lock holder executes.
82+ go advisorylock .RunLeaderLoop (ctx , uc .db , advisorylock .KeyBackendUpgradeCheck , uc .checkInterval , func () {
7883 uc .runCheck (ctx )
84+ })
85+
86+ // Still listen for on-demand triggers (from API / settings change)
87+ // and stop signal — these run on every instance.
88+ for {
89+ select {
90+ case <- ctx .Done ():
91+ return
92+ case <- uc .stop :
93+ return
94+ case <- uc .triggerCh :
95+ uc .runCheck (ctx )
96+ }
97+ }
98+ } else {
99+ // Standalone mode: simple ticker loop
100+ ticker := time .NewTicker (uc .checkInterval )
101+ defer ticker .Stop ()
102+
103+ for {
104+ select {
105+ case <- ctx .Done ():
106+ return
107+ case <- uc .stop :
108+ return
109+ case <- ticker .C :
110+ uc .runCheck (ctx )
111+ case <- uc .triggerCh :
112+ uc .runCheck (ctx )
113+ }
79114 }
80115 }
81116}
@@ -86,7 +121,7 @@ func (uc *UpgradeChecker) Shutdown() {
86121 <- uc .done
87122}
88123
89- // TriggerCheck forces an immediate upgrade check.
124+ // TriggerCheck forces an immediate upgrade check on this instance .
90125func (uc * UpgradeChecker ) TriggerCheck () {
91126 select {
92127 case uc .triggerCh <- struct {}{}:
0 commit comments