From 0caf24af505802a10ce384ae1d87007c554e7d22 Mon Sep 17 00:00:00 2001 From: Nick Craig-Wood Date: Tue, 6 Oct 2026 11:17:45 +0100 Subject: [PATCH] serve: fix --auth-proxy stopping VFS cache uploads after 5 minutes With --auth-proxy the backend of each user is shut down once it has been unused for 5 minutes. With --vfs-cache-mode writes or full, files are uploaded to the backend after the transfer which wrote them has finished, which didn't count as a use. So an upload which hadn't finished 5 minutes after the user's last command (or disconnection for serve sftp) was stopped, leaving the file in the VFS cache but not on the backend. The backend is now kept until it has no files open for write and nothing in the VFS cache waiting to be uploaded. This was introduced in v1.75.1 by f425f8d46 serve: refactor VFS and proxy handling into Provider --- cmd/serve/proxy/proxy.go | 7 +++++++ cmd/serve/proxy/proxy_test.go | 26 ++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/cmd/serve/proxy/proxy.go b/cmd/serve/proxy/proxy.go index ed0bb3395..d3d7fb7af 100644 --- a/cmd/serve/proxy/proxy.go +++ b/cmd/serve/proxy/proxy.go @@ -233,6 +233,13 @@ func New(ctx context.Context, opt *Options, vfsOpt *vfscommon.Options) *Proxy { entry.vfs.Shutdown() } }) + // Shutting down a VFS stops it writing, so keep one which still + // has data to write, e.g. files in the VFS cache waiting to be + // uploaded after the transfer which wrote them has finished. + p.vfsCache.SetCanExpire(func(value any) bool { + entry, ok := value.(cacheEntry) + return !ok || entry.vfs == nil || !entry.vfs.Busy() + }) return p } diff --git a/cmd/serve/proxy/proxy_test.go b/cmd/serve/proxy/proxy_test.go index 411288773..00c99d320 100644 --- a/cmd/serve/proxy/proxy_test.go +++ b/cmd/serve/proxy/proxy_test.go @@ -475,3 +475,29 @@ func TestCallAccessKey(t *testing.T) { _, _, err = p.CallAccessKey("AKID", remoteAddr, false) require.ErrorContains(t, err, "revoked") } + +// TestBusyVFSNotExpired checks a VFS which still has data to write +// isn't expired from the cache, and so shut down, until it is written. +func TestBusyVFSNotExpired(t *testing.T) { + opt := Opt + opt.AuthProxy = "go run ../servetest/proxy_code.go " + t.TempDir() + p := New(context.Background(), &opt, &vfscommon.Opt) + defer p.Shutdown() + p.vfsCache.SetExpireDuration(time.Millisecond).SetExpireInterval(10 * time.Millisecond) + + VFS, _, err := p.Call("user", "pass", false, "192.0.2.1:1024") + require.NoError(t, err) + fh, err := VFS.Create("file.txt") + require.NoError(t, err) + + assert.Never(t, func() bool { + return p.vfsCache.Entries() == 0 + }, 250*time.Millisecond, 10*time.Millisecond, "VFS expired with a file open for write") + assert.Equal(t, int32(1), VFS.Stats()["inUse"], "VFS shut down with a file open for write") + + require.NoError(t, fh.Close()) + assert.Eventually(t, func() bool { + return p.vfsCache.Entries() == 0 + }, 10*time.Second, 10*time.Millisecond, "VFS not expired once it had nothing to write") + assert.Equal(t, int32(0), VFS.Stats()["inUse"], "VFS not shut down once it had nothing to write") +}