From ce27276debd35ea817db79b515ff1051909e2205 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Sat, 13 Jun 2026 13:10:16 -0700 Subject: [PATCH] storage: swap the volume data backend under the data lock The tier-download swap closed v.DataBackend and assigned the new local DiskFile without holding dataFileAccessLock, racing concurrent reads/writes (use of a closed file / nil deref). Add an exported Volume.SwapDataBackend that performs the close-and-replace under the lock, and call it from the tier download. --- weed/server/volume_grpc_tier_download.go | 7 +++---- weed/storage/volume.go | 13 +++++++++++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/weed/server/volume_grpc_tier_download.go b/weed/server/volume_grpc_tier_download.go index d5597273f..4376abae2 100644 --- a/weed/server/volume_grpc_tier_download.go +++ b/weed/server/volume_grpc_tier_download.go @@ -115,10 +115,9 @@ func swapToLocalDatBackend(v *storage.Volume, datFileName string) error { if err != nil { return err } - if v.DataBackend != nil { - v.DataBackend.Close() - } - v.DataBackend = backend.NewDiskFile(dataFile) + // Swap under the volume's data lock so concurrent reads never see a closed + // or half-swapped backend. + v.SwapDataBackend(backend.NewDiskFile(dataFile)) return nil } diff --git a/weed/storage/volume.go b/weed/storage/volume.go index 10249482f..b326f9ab3 100644 --- a/weed/storage/volume.go +++ b/weed/storage/volume.go @@ -310,6 +310,19 @@ func (v *Volume) Close() { v.doClose() } +// SwapDataBackend atomically replaces the data backend (e.g. swapping a +// remote-tier backend for a freshly downloaded local .dat), closing the old +// one. Held under dataFileAccessLock so a concurrent read/write never observes +// a half-swapped or closed backend. +func (v *Volume) SwapDataBackend(newBackend backend.BackendStorageFile) { + v.dataFileAccessLock.Lock() + defer v.dataFileAccessLock.Unlock() + if v.DataBackend != nil { + v.DataBackend.Close() + } + v.DataBackend = newBackend +} + func (v *Volume) doClose() { if v.nm != nil { if err := v.nm.Sync(); err != nil {