diff --git a/internal/virtual/engine.go b/internal/virtual/engine.go index 15a973a..a48d388 100644 --- a/internal/virtual/engine.go +++ b/internal/virtual/engine.go @@ -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 { + // A failed lookup keeps the copy: a transient DB error must not force a blocking fetch. + if remote, err := e.getRemote(ctx, name); err == nil && !remote.ServeStale { e.mu.Lock() delete(e.stale, sk) e.mu.Unlock() diff --git a/internal/virtual/engine_stale_test.go b/internal/virtual/engine_stale_test.go index c7d0e16..cadb3f1 100644 --- a/internal/virtual/engine_stale_test.go +++ b/internal/virtual/engine_stale_test.go @@ -328,3 +328,18 @@ func TestMergePrunesExpiredCopiesOfOtherMembers(t *testing.T) { t.Fatal("any merge should prune expired copies of removed members") } } + +func TestServeStaleLookupErrorKeepsCopy(t *testing.T) { + e, _ := staleEngine(true, func() *RPMMember { return rpmPkgMember("a", "1") }) + v1 := fetchRepomd(t, e) + e.getRemote = func(context.Context, string) (*models.Remote, error) { return nil, errors.New("db down") } + if got := fetchRepomd(t, e); got != v1 { + t.Fatal("a failed remote lookup should keep serving the previous copy") + } + waitRefresh(e, rpmStaleKey) + e.mu.Lock() + defer e.mu.Unlock() + if e.stale[rpmStaleKey] == nil || e.stale[rpmStaleKey].m == nil { + t.Fatal("a failed remote lookup must not drop the copy") + } +}