Files
moby/daemon/mounts_test.go
Paweł Gronowski e01269424e daemon: Prevent race between volume live-restore and cleanup
Container exit events can run after prepareMountPoints observes a
running container but before it restores the volume references.
Cleanup then sees zero active mounts, and restore leaves a reference
that is never released.

Hold the container lock across the running-state check and mount-point
preparation so exit cleanup runs wholly before or after reference
restore.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2026-07-20 17:18:41 +02:00

97 lines
2.1 KiB
Go

package daemon
import (
"context"
"sync/atomic"
"testing"
"time"
"github.com/moby/moby/v2/daemon/container"
"github.com/moby/moby/v2/daemon/volume/mounts"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
func TestPrepareMountPointsSerializesLiveRestoreWithCleanup(t *testing.T) {
volume := &blockingLiveRestoreVolume{
restoreStarted: make(chan struct{}),
continueRestore: make(chan struct{}),
}
mountPoint := &mounts.MountPoint{
Volume: volume,
ID: "mount-id",
}
ctr := container.NewBaseContainer("container-id", t.TempDir())
ctr.State.Running = true
ctr.MountPoints["/volume"] = mountPoint
restoreErr := make(chan error, 1)
go func() {
restoreErr <- (&Daemon{}).prepareMountPoints(ctr)
}()
<-volume.restoreStarted
cleanupBeforeRestore := ctr.TryLock()
var cleanupErr error
if cleanupBeforeRestore {
cleanupErr = mountPoint.Cleanup(context.Background())
ctr.Unlock()
}
close(volume.continueRestore)
assert.NilError(t, <-restoreErr)
if !cleanupBeforeRestore {
ctr.Lock()
cleanupErr = mountPoint.Cleanup(context.Background())
ctr.Unlock()
}
assert.NilError(t, cleanupErr)
assert.Check(t, is.Equal(volume.active.Load(), int64(0)))
assert.Check(t, is.Equal(volume.unmounts.Load(), int64(1)))
}
type blockingLiveRestoreVolume struct {
active atomic.Int64
unmounts atomic.Int64
restoreStarted chan struct{}
continueRestore chan struct{}
}
func (v *blockingLiveRestoreVolume) Name() string {
return "test-volume"
}
func (v *blockingLiveRestoreVolume) DriverName() string {
return "test"
}
func (v *blockingLiveRestoreVolume) Path() string {
return ""
}
func (v *blockingLiveRestoreVolume) Mount(string) (string, error) {
return "", nil
}
func (v *blockingLiveRestoreVolume) Unmount(string) error {
v.active.Add(-1)
v.unmounts.Add(1)
return nil
}
func (v *blockingLiveRestoreVolume) CreatedAt() (time.Time, error) {
return time.Time{}, nil
}
func (v *blockingLiveRestoreVolume) Status() map[string]any {
return nil
}
func (v *blockingLiveRestoreVolume) LiveRestoreVolume(context.Context, string) error {
close(v.restoreStarted)
<-v.continueRestore
v.active.Add(1)
return nil
}