From ce4e6c2b3383dfd85857776292bfaf66b9e450c6 Mon Sep 17 00:00:00 2001 From: Aram Kocharyan Date: Tue, 11 Aug 2026 03:36:38 +0400 Subject: [PATCH] tempodb: tolerate blocks removed during local listing Signed-off-by: Aram Kocharyan --- tempodb/backend/local/local.go | 7 ++++++ tempodb/backend/local/local_test.go | 34 +++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/tempodb/backend/local/local.go b/tempodb/backend/local/local.go index 90572c61316..16426ace0a6 100644 --- a/tempodb/backend/local/local.go +++ b/tempodb/backend/local/local.go @@ -2,6 +2,7 @@ package local import ( "context" + "errors" "io" "io/fs" "os" @@ -199,6 +200,12 @@ func (rw *Backend) ListBlocks(_ context.Context, tenant string) (metas []uuid.UU fff := os.DirFS(rootPath) err = fs.WalkDir(fff, ".", func(path string, _ fs.DirEntry, err error) error { if err != nil { + // Blocks can be deleted by retention while the blocklist is being + // walked. A vanished child is no longer part of the blocklist, so + // skip it instead of failing the entire tenant poll. + if path != "." && errors.Is(err, fs.ErrNotExist) { + return nil + } return err } diff --git a/tempodb/backend/local/local_test.go b/tempodb/backend/local/local_test.go index d9bb5e4a1f9..5ecbc073a68 100644 --- a/tempodb/backend/local/local_test.go +++ b/tempodb/backend/local/local_test.go @@ -7,6 +7,7 @@ import ( "fmt" "math/rand" "os" + "path/filepath" "sync" "testing" @@ -81,6 +82,39 @@ func TestReadWrite(t *testing.T) { assert.Len(t, cm, 1) } +func TestListBlocksAllowsConcurrentBlockDeletion(t *testing.T) { + path := t.TempDir() + r, _, _, err := New(&Config{Path: path}) + require.NoError(t, err) + + tenant := "tenant" + blockID := uuid.New() + blockPath := filepath.Join(path, tenant, blockID.String()) + require.NoError(t, os.MkdirAll(blockPath, 0o700)) + require.NoError(t, os.WriteFile(filepath.Join(blockPath, backend.MetaName), []byte("meta"), 0o600)) + + ctx, cancel := context.WithCancel(context.Background()) + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + for ctx.Err() == nil { + _ = os.RemoveAll(blockPath) + _ = os.MkdirAll(blockPath, 0o700) + _ = os.WriteFile(filepath.Join(blockPath, backend.MetaName), []byte("meta"), 0o600) + } + }() + t.Cleanup(func() { + cancel() + wg.Wait() + }) + + for range 1_000 { + _, _, err = r.ListBlocks(context.Background(), tenant) + require.NoError(t, err) + } +} + func TestShutdownLeavesTenantsWithBlocks(t *testing.T) { r, w, _, err := New(&Config{ Path: t.TempDir(),