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

View File

@@ -502,6 +502,7 @@ func main() {
if standalone {
// Standalone mode: start an embedded local server and connect TUI client to it.
managementasset.StartAutoUpdater(context.Background(), configFilePath)
misc.StartAntigravityVersionUpdater(context.Background())
if !localModel {
registry.StartModelsUpdater(context.Background())
}
@@ -577,6 +578,7 @@ func main() {
} else {
// Start the main proxy service
managementasset.StartAutoUpdater(context.Background(), configFilePath)
misc.StartAntigravityVersionUpdater(context.Background())
if !localModel {
registry.StartModelsUpdater(context.Background())
}

View File

@@ -2,7 +2,9 @@
package misc
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"sync"
@@ -24,14 +26,69 @@ type antigravityRelease struct {
}
var (
cachedAntigravityVersion string
cachedAntigravityVersion = antigravityFallbackVersion
antigravityVersionMu sync.RWMutex
antigravityVersionExpiry time.Time
antigravityUpdaterOnce sync.Once
)
// AntigravityLatestVersion returns the latest antigravity version from the releases API.
// It caches the result for antigravityVersionCacheTTL and falls back to antigravityFallbackVersion
// if the fetch fails.
// StartAntigravityVersionUpdater starts a background goroutine that periodically refreshes the cached antigravity version.
// This is intentionally decoupled from request execution to avoid blocking executors on version lookups.
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 {
antigravityVersionMu.RLock()
if cachedAntigravityVersion != "" && time.Now().Before(antigravityVersionExpiry) {
@@ -41,18 +98,7 @@ func AntigravityLatestVersion() string {
}
antigravityVersionMu.RUnlock()
antigravityVersionMu.Lock()
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
return antigravityFallbackVersion
}
// AntigravityUserAgent returns the User-Agent string for antigravity requests
@@ -61,37 +107,45 @@ func AntigravityUserAgent() string {
return fmt.Sprintf("antigravity/%s darwin/arm64", AntigravityLatestVersion())
}
func fetchAntigravityLatestVersion() string {
client := &http.Client{Timeout: antigravityFetchTimeout}
resp, err := client.Get(antigravityReleasesURL)
if err != nil {
log.WithError(err).Warn("failed to fetch antigravity releases, using fallback version")
return antigravityFallbackVersion
func fetchAntigravityLatestVersion(ctx context.Context) (string, error) {
if ctx == nil {
ctx = context.Background()
}
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 {
log.WithField("status", resp.StatusCode).Warn("antigravity releases API returned non-200, using fallback version")
return antigravityFallbackVersion
return "", fmt.Errorf("antigravity releases API returned status %d", resp.StatusCode)
}
var releases []antigravityRelease
if err := json.NewDecoder(resp.Body).Decode(&releases); err != nil {
log.WithError(err).Warn("failed to decode antigravity releases response, using fallback version")
return antigravityFallbackVersion
if errDecode := json.NewDecoder(resp.Body).Decode(&releases); errDecode != nil {
return "", fmt.Errorf("decode antigravity releases response: %w", errDecode)
}
if len(releases) == 0 {
log.Warn("antigravity releases API returned empty list, using fallback version")
return antigravityFallbackVersion
return "", errors.New("antigravity releases API returned empty list")
}
version := releases[0].Version
if version == "" {
log.Warn("antigravity releases API returned empty version, using fallback version")
return antigravityFallbackVersion
return "", errors.New("antigravity releases API returned empty version")
}
log.WithField("version", version).Info("fetched latest antigravity version")
return version
return version, nil
}