You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iceberg.apache.org by ze...@apache.org on 2026/07/16 17:51:21 UTC

(iceberg-go) branch main updated: fix(io): avoid deadlock in MemFS WalkDir callbacks (#1485)

This is an automated email from the ASF dual-hosted git repository.

zeroshade pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg-go.git


The following commit(s) were added to refs/heads/main by this push:
     new 04f7c734 fix(io): avoid deadlock in MemFS WalkDir callbacks (#1485)
04f7c734 is described below

commit 04f7c734dbffb8b46625044a877c07088a6ca8c8
Author: Minh Vu <vu...@gmail.com>
AuthorDate: Thu Jul 16 19:51:16 2026 +0200

    fix(io): avoid deadlock in MemFS WalkDir callbacks (#1485)
---
 io/mem.go      | 21 +++++++++++++++++----
 io/mem_test.go | 36 ++++++++++++++++++++++++++++++++++++
 2 files changed, 53 insertions(+), 4 deletions(-)

diff --git a/io/mem.go b/io/mem.go
index 1e4c3d64..27640059 100644
--- a/io/mem.go
+++ b/io/mem.go
@@ -106,16 +106,29 @@ func (m *MemFS) WriteFile(name string, content []byte) error {
 }
 
 func (m *MemFS) WalkDir(root string, fn fs.WalkDirFunc) error {
-	m.mu.RLock()
-	defer m.mu.RUnlock()
+	type walkEntry struct {
+		path string
+		size int64
+	}
 
 	root = strings.TrimRight(root, "/")
+
+	m.mu.RLock()
+	var entries []walkEntry
 	for key, data := range m.files {
 		if !memPathInRoot(key, root) {
 			continue
 		}
-		info := &memFileInfo{name: filepath.Base(key), size: int64(len(data))}
-		if err := fn(key, fs.FileInfoToDirEntry(info), nil); err != nil {
+		entries = append(entries, walkEntry{
+			path: key,
+			size: int64(len(data)),
+		})
+	}
+	m.mu.RUnlock()
+
+	for _, entry := range entries {
+		info := &memFileInfo{name: filepath.Base(entry.path), size: entry.size}
+		if err := fn(entry.path, fs.FileInfoToDirEntry(info), nil); err != nil {
 			return err
 		}
 	}
diff --git a/io/mem_test.go b/io/mem_test.go
index 02cc4a63..bd1e8519 100644
--- a/io/mem_test.go
+++ b/io/mem_test.go
@@ -22,6 +22,7 @@ import (
 	"io"
 	"io/fs"
 	"testing"
+	"time"
 
 	icebergio "github.com/apache/iceberg-go/io"
 	"github.com/stretchr/testify/assert"
@@ -200,3 +201,38 @@ func TestMemIO_WalkDirDoesNotIncludeSiblingPrefixes(t *testing.T) {
 		})
 	}
 }
+
+func TestMemIO_WalkDirCallbackCanRemoveFiles(t *testing.T) {
+	memIO := icebergio.NewMemFS()
+	require.NoError(t, memIO.WriteFile("mem://bucket/root/1.txt", []byte("1")))
+	require.NoError(t, memIO.WriteFile("mem://bucket/root/2.txt", []byte("22")))
+
+	done := make(chan error, 1)
+	go func() {
+		done <- memIO.WalkDir("mem://bucket/root", func(path string, _ fs.DirEntry, err error) error {
+			if err != nil {
+				return err
+			}
+
+			return memIO.Remove(path)
+		})
+	}()
+
+	select {
+	case err := <-done:
+		require.NoError(t, err)
+	case <-time.After(2 * time.Second):
+		t.Fatal("WalkDir deadlocked when its callback removed a file")
+	}
+
+	var remaining []string
+	require.NoError(t, memIO.WalkDir("mem://bucket/root", func(path string, _ fs.DirEntry, err error) error {
+		if err != nil {
+			return err
+		}
+		remaining = append(remaining, path)
+
+		return nil
+	}))
+	assert.Empty(t, remaining)
+}