feat(misc): add background updater for Antigravity version caching

Introduce `StartAntigravityVersionUpdater` to periodically refresh the cached Antigravity version using a non-blocking background process. Updated main server flow to initialize the updater.
This commit is contained in:
Luis Pater
2026-04-04 22:09:11 +08:00
parent c2d4137fb9
commit 3774b56e9f
2 changed files with 90 additions and 34 deletions
+2
View File
@@ -502,6 +502,7 @@ func main() {
if standalone { if standalone {
// Standalone mode: start an embedded local server and connect TUI client to it. // Standalone mode: start an embedded local server and connect TUI client to it.
managementasset.StartAutoUpdater(context.Background(), configFilePath) managementasset.StartAutoUpdater(context.Background(), configFilePath)
misc.StartAntigravityVersionUpdater(context.Background())
if !localModel { if !localModel {
registry.StartModelsUpdater(context.Background()) registry.StartModelsUpdater(context.Background())
} }
@@ -577,6 +578,7 @@ func main() {
} else { } else {
// Start the main proxy service // Start the main proxy service
managementasset.StartAutoUpdater(context.Background(), configFilePath) managementasset.StartAutoUpdater(context.Background(), configFilePath)
misc.StartAntigravityVersionUpdater(context.Background())
if !localModel { if !localModel {
registry.StartModelsUpdater(context.Background()) registry.StartModelsUpdater(context.Background())
} }
+88 -34
View File
@@ -2,7 +2,9 @@
package misc package misc
import ( import (
"context"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"net/http" "net/http"
"sync" "sync"
@@ -24,14 +26,69 @@ type antigravityRelease struct {
} }
var ( var (
cachedAntigravityVersion string cachedAntigravityVersion = antigravityFallbackVersion
antigravityVersionMu sync.RWMutex antigravityVersionMu sync.RWMutex
antigravityVersionExpiry time.Time antigravityVersionExpiry time.Time
antigravityUpdaterOnce sync.Once
) )
// AntigravityLatestVersion returns the latest antigravity version from the releases API. // StartAntigravityVersionUpdater starts a background goroutine that periodically refreshes the cached antigravity version.
// It caches the result for antigravityVersionCacheTTL and falls back to antigravityFallbackVersion // This is intentionally decoupled from request execution to avoid blocking executors on version lookups.
// if the fetch fails. func StartAntigravityVersionUpdater(ctx context.Context) {
antigravityUpdaterOnce.Do(func() {
go runAntigravityVersionUpdater(ctx)
})
}
func runAntigravityVersionUpdater(ctx context.Context) {
if ctx == nil {
ctx = context.Background()
}
ticker := time.NewTicker(antigravityVersionCacheTTL / 2)
defer ticker.Stop()
log.Infof("periodic antigravity version refresh started (interval=%s)", antigravityVersionCacheTTL/2)
refreshAntigravityVersion(ctx)
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
refreshAntigravityVersion(ctx)
}
}
}
func refreshAntigravityVersion(ctx context.Context) {
version, errFetch := fetchAntigravityLatestVersion(ctx)
antigravityVersionMu.Lock()
defer antigravityVersionMu.Unlock()
now := time.Now()
if errFetch == nil {
cachedAntigravityVersion = version
antigravityVersionExpiry = now.Add(antigravityVersionCacheTTL)
log.WithField("version", version).Info("fetched latest antigravity version")
return
}
if cachedAntigravityVersion == "" || now.After(antigravityVersionExpiry) {
cachedAntigravityVersion = antigravityFallbackVersion
antigravityVersionExpiry = now.Add(antigravityVersionCacheTTL)
log.WithError(errFetch).Warn("failed to refresh antigravity version, using fallback version")
return
}
log.WithError(errFetch).Debug("failed to refresh antigravity version, keeping cached value")
}
// AntigravityLatestVersion returns the cached antigravity version refreshed by StartAntigravityVersionUpdater.
// It falls back to antigravityFallbackVersion if the cache is empty or stale.
func AntigravityLatestVersion() string { func AntigravityLatestVersion() string {
antigravityVersionMu.RLock() antigravityVersionMu.RLock()
if cachedAntigravityVersion != "" && time.Now().Before(antigravityVersionExpiry) { if cachedAntigravityVersion != "" && time.Now().Before(antigravityVersionExpiry) {
@@ -41,18 +98,7 @@ func AntigravityLatestVersion() string {
} }
antigravityVersionMu.RUnlock() antigravityVersionMu.RUnlock()
antigravityVersionMu.Lock() return antigravityFallbackVersion
defer antigravityVersionMu.Unlock()
// Double-check after acquiring write lock.
if cachedAntigravityVersion != "" && time.Now().Before(antigravityVersionExpiry) {
return cachedAntigravityVersion
}
version := fetchAntigravityLatestVersion()
cachedAntigravityVersion = version
antigravityVersionExpiry = time.Now().Add(antigravityVersionCacheTTL)
return version
} }
// AntigravityUserAgent returns the User-Agent string for antigravity requests // AntigravityUserAgent returns the User-Agent string for antigravity requests
@@ -61,37 +107,45 @@ func AntigravityUserAgent() string {
return fmt.Sprintf("antigravity/%s darwin/arm64", AntigravityLatestVersion()) return fmt.Sprintf("antigravity/%s darwin/arm64", AntigravityLatestVersion())
} }
func fetchAntigravityLatestVersion() string { func fetchAntigravityLatestVersion(ctx context.Context) (string, error) {
client := &http.Client{Timeout: antigravityFetchTimeout} if ctx == nil {
resp, err := client.Get(antigravityReleasesURL) ctx = context.Background()
if err != nil {
log.WithError(err).Warn("failed to fetch antigravity releases, using fallback version")
return antigravityFallbackVersion
} }
defer resp.Body.Close()
client := &http.Client{Timeout: antigravityFetchTimeout}
httpReq, errReq := http.NewRequestWithContext(ctx, http.MethodGet, antigravityReleasesURL, nil)
if errReq != nil {
return "", fmt.Errorf("build antigravity releases request: %w", errReq)
}
resp, errDo := client.Do(httpReq)
if errDo != nil {
return "", fmt.Errorf("fetch antigravity releases: %w", errDo)
}
defer func() {
if errClose := resp.Body.Close(); errClose != nil {
log.WithError(errClose).Warn("antigravity releases response body close error")
}
}()
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
log.WithField("status", resp.StatusCode).Warn("antigravity releases API returned non-200, using fallback version") return "", fmt.Errorf("antigravity releases API returned status %d", resp.StatusCode)
return antigravityFallbackVersion
} }
var releases []antigravityRelease var releases []antigravityRelease
if err := json.NewDecoder(resp.Body).Decode(&releases); err != nil { if errDecode := json.NewDecoder(resp.Body).Decode(&releases); errDecode != nil {
log.WithError(err).Warn("failed to decode antigravity releases response, using fallback version") return "", fmt.Errorf("decode antigravity releases response: %w", errDecode)
return antigravityFallbackVersion
} }
if len(releases) == 0 { if len(releases) == 0 {
log.Warn("antigravity releases API returned empty list, using fallback version") return "", errors.New("antigravity releases API returned empty list")
return antigravityFallbackVersion
} }
version := releases[0].Version version := releases[0].Version
if version == "" { if version == "" {
log.Warn("antigravity releases API returned empty version, using fallback version") return "", errors.New("antigravity releases API returned empty version")
return antigravityFallbackVersion
} }
log.WithField("version", version).Info("fetched latest antigravity version") return version, nil
return version
} }