mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Fixes Issue # 23418: Race condition between device deferred removal and resume device.
Problem Description: An example scenario that involves deferred removal 1. A new base image gets created (e.g. 'docker load -i'). The base device is activated and mounted at some point in time during image creation. 2. While image creation is in progress, a privileged container is started from another image and the host's mount name space is shared with this container ('docker run --privileged -v /:/host'). 3. Image creation completes and the base device gets unmounted. However, as the privileged container still holds a reference on the base image mount point, the base device cannot be removed right away. So it gets flagged for deferred removal. 4. Next, the privileged container terminates and thus its reference to the base image mount point gets released. The base device (which is flagged for deferred removal) may now be cleaned up by the device-mapper. This opens up an opportunity for a race between a 'kworker' thread (executing the do_deferred_remove() function) and the Docker daemon (executing the CreateSnapDevice() function). This PR cancel the deferred removal, if the device is marked for it. And reschedule the deferred removal later after the device is resumed successfully. Signed-off-by: Shishir Mahajan <shishir.mahajan@redhat.com>
This commit is contained in:
parent
2011320fa9
commit
0e633ee14a
2 changed files with 113 additions and 56 deletions
|
@ -528,7 +528,7 @@ func (devices *DeviceSet) activateDeviceIfNeeded(info *devInfo, ignoreDeleted bo
|
||||||
|
|
||||||
// Make sure deferred removal on device is canceled, if one was
|
// Make sure deferred removal on device is canceled, if one was
|
||||||
// scheduled.
|
// scheduled.
|
||||||
if err := devices.cancelDeferredRemoval(info); err != nil {
|
if err := devices.cancelDeferredRemovalIfNeeded(info); err != nil {
|
||||||
return fmt.Errorf("devmapper: Device Deferred Removal Cancellation Failed: %s", err)
|
return fmt.Errorf("devmapper: Device Deferred Removal Cancellation Failed: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -841,11 +841,56 @@ func (devices *DeviceSet) createRegisterDevice(hash string) (*devInfo, error) {
|
||||||
return info, nil
|
return info, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (devices *DeviceSet) createRegisterSnapDevice(hash string, baseInfo *devInfo, size uint64) error {
|
func (devices *DeviceSet) takeSnapshot(hash string, baseInfo *devInfo, size uint64) error {
|
||||||
if err := devices.poolHasFreeSpace(); err != nil {
|
var (
|
||||||
|
devinfo *devicemapper.Info
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
if err = devices.poolHasFreeSpace(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if devices.deferredRemove {
|
||||||
|
devinfo, err = devicemapper.GetInfoWithDeferred(baseInfo.Name())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if devinfo != nil && devinfo.DeferredRemove != 0 {
|
||||||
|
err = devices.cancelDeferredRemoval(baseInfo)
|
||||||
|
if err != nil {
|
||||||
|
// If Error is ErrEnxio. Device is probably already gone. Continue.
|
||||||
|
if err != devicemapper.ErrEnxio {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
defer devices.deactivateDevice(baseInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
devinfo, err = devicemapper.GetInfo(baseInfo.Name())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
doSuspend := devinfo != nil && devinfo.Exists != 0
|
||||||
|
|
||||||
|
if doSuspend {
|
||||||
|
if err = devicemapper.SuspendDevice(baseInfo.Name()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer devicemapper.ResumeDevice(baseInfo.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = devices.createRegisterSnapDevice(hash, baseInfo, size); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (devices *DeviceSet) createRegisterSnapDevice(hash string, baseInfo *devInfo, size uint64) error {
|
||||||
deviceID, err := devices.getNextFreeDeviceID()
|
deviceID, err := devices.getNextFreeDeviceID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -858,7 +903,7 @@ func (devices *DeviceSet) createRegisterSnapDevice(hash string, baseInfo *devInf
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
if err := devicemapper.CreateSnapDevice(devices.getPoolDevName(), deviceID, baseInfo.Name(), baseInfo.DeviceID); err != nil {
|
if err := devicemapper.CreateSnapDeviceRaw(devices.getPoolDevName(), deviceID, baseInfo.DeviceID); err != nil {
|
||||||
if devicemapper.DeviceIDExists(err) {
|
if devicemapper.DeviceIDExists(err) {
|
||||||
// Device ID already exists. This should not
|
// Device ID already exists. This should not
|
||||||
// happen. Now we have a mechanism to find
|
// happen. Now we have a mechanism to find
|
||||||
|
@ -1886,7 +1931,7 @@ func (devices *DeviceSet) AddDevice(hash, baseHash string, storageOpt map[string
|
||||||
return fmt.Errorf("devmapper: Container size cannot be smaller than %s", units.HumanSize(float64(baseInfo.Size)))
|
return fmt.Errorf("devmapper: Container size cannot be smaller than %s", units.HumanSize(float64(baseInfo.Size)))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := devices.createRegisterSnapDevice(hash, baseInfo, size); err != nil {
|
if err := devices.takeSnapshot(hash, baseInfo, size); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2128,41 +2173,53 @@ func (devices *DeviceSet) removeDevice(devname string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (devices *DeviceSet) cancelDeferredRemoval(info *devInfo) error {
|
func (devices *DeviceSet) cancelDeferredRemovalIfNeeded(info *devInfo) error {
|
||||||
if !devices.deferredRemove {
|
if !devices.deferredRemove {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
logrus.Debugf("devmapper: cancelDeferredRemoval START(%s)", info.Name())
|
logrus.Debugf("devmapper: cancelDeferredRemovalIfNeeded START(%s)", info.Name())
|
||||||
defer logrus.Debugf("devmapper: cancelDeferredRemoval END(%s)", info.Name())
|
defer logrus.Debugf("devmapper: cancelDeferredRemovalIfNeeded END(%s)", info.Name())
|
||||||
|
|
||||||
devinfo, err := devicemapper.GetInfoWithDeferred(info.Name())
|
devinfo, err := devicemapper.GetInfoWithDeferred(info.Name())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if devinfo != nil && devinfo.DeferredRemove == 0 {
|
if devinfo != nil && devinfo.DeferredRemove == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cancel deferred remove
|
// Cancel deferred remove
|
||||||
for i := 0; i < 100; i++ {
|
if err := devices.cancelDeferredRemoval(info); err != nil {
|
||||||
err = devicemapper.CancelDeferredRemove(info.Name())
|
// If Error is ErrEnxio. Device is probably already gone. Continue.
|
||||||
if err == nil {
|
if err != devicemapper.ErrEnxio {
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if err == devicemapper.ErrEnxio {
|
|
||||||
// Device is probably already gone. Return success.
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != devicemapper.ErrBusy {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// If we see EBUSY it may be a transient error,
|
func (devices *DeviceSet) cancelDeferredRemoval(info *devInfo) error {
|
||||||
// sleep a bit a retry a few times.
|
logrus.Debugf("devmapper: cancelDeferredRemoval START(%s)", info.Name())
|
||||||
devices.Unlock()
|
defer logrus.Debugf("devmapper: cancelDeferredRemoval END(%s)", info.Name())
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
devices.Lock()
|
var err error
|
||||||
|
|
||||||
|
// Cancel deferred remove
|
||||||
|
for i := 0; i < 100; i++ {
|
||||||
|
err = devicemapper.CancelDeferredRemove(info.Name())
|
||||||
|
if err != nil {
|
||||||
|
if err == devicemapper.ErrBusy {
|
||||||
|
// If we see EBUSY it may be a transient error,
|
||||||
|
// sleep a bit a retry a few times.
|
||||||
|
devices.Unlock()
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
devices.Lock()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -750,6 +750,33 @@ func activateDevice(poolName string, name string, deviceID int, size uint64, ext
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateSnapDeviceRaw creates a snapshot device. Caller needs to suspend and resume the origin device if it is active.
|
||||||
|
func CreateSnapDeviceRaw(poolName string, deviceID int, baseDeviceID int) error {
|
||||||
|
task, err := TaskCreateNamed(deviceTargetMsg, poolName)
|
||||||
|
if task == nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := task.setSector(0); err != nil {
|
||||||
|
return fmt.Errorf("devicemapper: Can't set sector %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := task.setMessage(fmt.Sprintf("create_snap %d %d", deviceID, baseDeviceID)); err != nil {
|
||||||
|
return fmt.Errorf("devicemapper: Can't set message %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dmSawExist = false // reset before the task is run
|
||||||
|
if err := task.run(); err != nil {
|
||||||
|
// Caller wants to know about ErrDeviceIDExists so that it can try with a different device id.
|
||||||
|
if dmSawExist {
|
||||||
|
return ErrDeviceIDExists
|
||||||
|
}
|
||||||
|
return fmt.Errorf("devicemapper: Error running deviceCreate (createSnapDevice) %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// CreateSnapDevice creates a snapshot based on the device identified by the baseName and baseDeviceId,
|
// CreateSnapDevice creates a snapshot based on the device identified by the baseName and baseDeviceId,
|
||||||
func CreateSnapDevice(poolName string, deviceID int, baseName string, baseDeviceID int) error {
|
func CreateSnapDevice(poolName string, deviceID int, baseName string, baseDeviceID int) error {
|
||||||
devinfo, _ := GetInfo(baseName)
|
devinfo, _ := GetInfo(baseName)
|
||||||
|
@ -761,42 +788,15 @@ func CreateSnapDevice(poolName string, deviceID int, baseName string, baseDevice
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
task, err := TaskCreateNamed(deviceTargetMsg, poolName)
|
if err := CreateSnapDeviceRaw(poolName, deviceID, baseDeviceID); err != nil {
|
||||||
if task == nil {
|
|
||||||
if doSuspend {
|
if doSuspend {
|
||||||
ResumeDevice(baseName)
|
if err2 := ResumeDevice(baseName); err2 != nil {
|
||||||
|
return fmt.Errorf("CreateSnapDeviceRaw Error: (%v): ResumeDevice Error: (%v)", err, err2)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := task.setSector(0); err != nil {
|
|
||||||
if doSuspend {
|
|
||||||
ResumeDevice(baseName)
|
|
||||||
}
|
|
||||||
return fmt.Errorf("devicemapper: Can't set sector %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := task.setMessage(fmt.Sprintf("create_snap %d %d", deviceID, baseDeviceID)); err != nil {
|
|
||||||
if doSuspend {
|
|
||||||
ResumeDevice(baseName)
|
|
||||||
}
|
|
||||||
return fmt.Errorf("devicemapper: Can't set message %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
dmSawExist = false // reset before the task is run
|
|
||||||
if err := task.run(); err != nil {
|
|
||||||
if doSuspend {
|
|
||||||
ResumeDevice(baseName)
|
|
||||||
}
|
|
||||||
// Caller wants to know about ErrDeviceIDExists so that it can try with a different device id.
|
|
||||||
if dmSawExist {
|
|
||||||
return ErrDeviceIDExists
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("devicemapper: Error running deviceCreate (createSnapDevice) %s", err)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if doSuspend {
|
if doSuspend {
|
||||||
if err := ResumeDevice(baseName); err != nil {
|
if err := ResumeDevice(baseName); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue