From 5dd033fc3e55ca9cc9d3d5c36085bd2136e3a94e Mon Sep 17 00:00:00 2001 From: james Date: Thu, 5 Nov 2026 18:03:11 -0700 Subject: [PATCH] filer: no lazy remote fetch inside a store transaction A rename onto an existing target deletes the target inside the store transaction, then CreateEntry's FindEntry misses the row and runs the lazy remote fetch, which persists the archive copy on a second connection. The store wrapper drops that call's deadline, so the UPDATE waits without bound on the row lock of the transaction's own uncommitted DELETE while the rename goroutine holds the path's exclusive locks: every later lookup, rename and delete of the path on that filer waits forever. A lookup whose context carries the store transaction skips the fetch. (cherry picked from commit b69e9535c2597d0c8e856f031d3778fddfd4159c) --- weed/filer/filer_lazy_remote.go | 6 ++++++ weed/filer/filer_lazy_remote_test.go | 22 ++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/weed/filer/filer_lazy_remote.go b/weed/filer/filer_lazy_remote.go index 81d4e3996a..6d8cce3593 100644 --- a/weed/filer/filer_lazy_remote.go +++ b/weed/filer/filer_lazy_remote.go @@ -41,6 +41,12 @@ func (f *Filer) maybeLazyFetchFromRemote(ctx context.Context, p util.FullPath) ( return nil, nil } + // A lookup inside a store transaction persists on a second connection, + // which waits on the row locks the transaction holds. + if ctx.Value("tx") != nil { + return nil, nil + } + if f.RemoteStorage == nil { return nil, nil } diff --git a/weed/filer/filer_lazy_remote_test.go b/weed/filer/filer_lazy_remote_test.go index 40d7c5358c..b1d4c6c436 100644 --- a/weed/filer/filer_lazy_remote_test.go +++ b/weed/filer/filer_lazy_remote_test.go @@ -734,6 +734,28 @@ func TestMaybeLazyFetchFromRemote_ContextGuardPreventsRecursion(t *testing.T) { assert.Equal(t, 0, countingStub.statCalls, "guard should prevent StatFile from being called") } +func TestMaybeLazyFetchFromRemote_SkipsInsideStoreTransaction(t *testing.T) { + const storageType = "stub_lazy_tx" + countingStub := &countingRemoteClient{ + stubRemoteClient: stubRemoteClient{ + statResult: &filer_pb.RemoteEntry{RemoteMtime: 1, RemoteSize: 1}, + }, + } + f, _ := newMountedTestFiler(t, storageType, countingStub, 0) + + // the key a store transaction travels under (abstract_sql getTxOrDB) + txCtx := context.WithValue(context.Background(), "tx", struct{}{}) + entry, err := f.maybeLazyFetchFromRemote(txCtx, "/buckets/mybucket/dir/a.txt") + require.NoError(t, err) + assert.Nil(t, entry) + assert.Equal(t, 0, countingStub.statCalls, "a lookup inside a transaction must not consult the remote") + + entry, err = f.maybeLazyFetchFromRemote(context.Background(), "/buckets/mybucket/dir/a.txt") + require.NoError(t, err) + require.NotNil(t, entry) + assert.NotZero(t, countingStub.statCalls, "the same lookup outside a transaction consults the remote") +} + func TestMaybeLazyFetchFromRemote_GuardedClientRejectsEndpoint(t *testing.T) { const storageType = "stub_lazy_guarded" countingStub := &countingRemoteClient{