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.
This commit is contained in:
Chris Lu
2026-06-13 13:10:16 -07:00
parent e617a6d2d4
commit ce27276deb
2 changed files with 16 additions and 4 deletions
+3 -4
View File
@@ -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
}
+13
View File
@@ -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 {