Keep stale virtual member copy when the remote lookup fails (#171)

A transient remote lookup error dropped the serve_stale copy of a virtual member and forced a blocking fetch, so a short DB blip stalled every merge.

- serve the existing copy on a lookup error and refresh it in the background
- fetch directly without storing anything on a lookup error with no copy
- create copies only after a successful lookup with serve_stale on

Reviewed-on: #171
Co-authored-by: unkin-agent <unkin-agent@unkin.net>
Co-committed-by: unkin-agent <unkin-agent@unkin.net>
This commit was merged in pull request #171.
This commit is contained in:
2026-10-25 20:24:20 +11:00
committed by BenVincent
parent a4a4fe57c9
commit f5d30cc96e
2 changed files with 132 additions and 2 deletions
+7 -2
View File
@@ -548,8 +548,8 @@ func (e *Engine) fetchAlpineMember(ctx context.Context, name, dir string) (*Alpi
// the background, one refresh per member at a time; the next merge picks it up.
func staleMember[T any](ctx context.Context, e *Engine, key, name string, fetch func(context.Context) (*T, error)) (*T, error) {
sk := key + "\x00" + name
remote, err := e.getRemote(ctx, name)
if err != nil || !remote.ServeStale {
remote, lookupErr := e.getRemote(ctx, name)
if lookupErr == nil && !remote.ServeStale {
e.mu.Lock()
delete(e.stale, sk)
e.mu.Unlock()
@@ -560,6 +560,11 @@ func staleMember[T any](ctx context.Context, e *Engine, key, name string, fetch
e.stale = map[string]*staleEntry{}
}
prev := e.stale[sk]
// A failed lookup serves an existing copy but never creates one.
if lookupErr != nil && (prev == nil || prev.m == nil) {
e.mu.Unlock()
return fetch(ctx)
}
// A cold fetch holds a placeholder so eviction or toggle-off during it is seen.
token := prev
if prev == nil || prev.m == nil {