From 93e120e7d67313086d8bdecbcb57ea68958f91e4 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 17 Dec 2013 09:12:44 +0100 Subject: [PATCH] Discard all data on devicemapper devices when deleting them This works around the fact that deleting a device in a thin pool doesn't discard the free space. Unfortunately even this is not perfect, as it seems discards are respected only for blocks that has never been shared in the thin device code. However, this has been fixed in the upstream kernel device-mapper tree: http://git.kernel.org/cgit/linux/kernel/git/device-mapper/linux-dm.git/commit/?h=for-next&id=0ab1c92ff748b745c1ed7cde31bb37ad2c5f901a When this hits the kernel I belive this will fully return space for removed images/containers to the host FS. For now it only helps partially (which is better than nothing). Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- graphdriver/devmapper/deviceset.go | 9 ++++++++ graphdriver/devmapper/devmapper.go | 24 ++++++++++++++++++++++ graphdriver/devmapper/devmapper_wrapper.go | 1 + graphdriver/devmapper/driver_test.go | 4 ++++ graphdriver/devmapper/ioctl.go | 11 ++++++++++ 5 files changed, 49 insertions(+) diff --git a/graphdriver/devmapper/deviceset.go b/graphdriver/devmapper/deviceset.go index 7308c0e922..6e3caf657d 100644 --- a/graphdriver/devmapper/deviceset.go +++ b/graphdriver/devmapper/deviceset.go @@ -568,6 +568,15 @@ func (devices *DeviceSet) removeDevice(hash string) error { return fmt.Errorf("hash %s doesn't exists", hash) } + // This is a workaround for the kernel not discarding block so + // on the thin pool when we remove a thinp device, so we do it + // manually + if err := devices.activateDeviceIfNeeded(hash); err == nil { + if err := BlockDeviceDiscard(info.DevName()); err != nil { + utils.Debugf("Error discarding block on device: %s (ignoring)\n", err) + } + } + devinfo, _ := getInfo(info.Name()) if devinfo != nil && devinfo.Exists != 0 { if err := removeDevice(info.Name()); err != nil { diff --git a/graphdriver/devmapper/devmapper.go b/graphdriver/devmapper/devmapper.go index dfbdf385d7..d3eba78a27 100644 --- a/graphdriver/devmapper/devmapper.go +++ b/graphdriver/devmapper/devmapper.go @@ -7,6 +7,7 @@ import ( "fmt" "github.com/dotcloud/docker/utils" "runtime" + "syscall" ) type DevmapperLogger interface { @@ -288,6 +289,29 @@ func GetBlockDeviceSize(file *osFile) (uint64, error) { return uint64(size), nil } +func BlockDeviceDiscard(path string) error { + file, err := osOpenFile(path, osORdWr, 0) + if err != nil { + return err + } + defer file.Close() + + size, err := GetBlockDeviceSize(file) + if err != nil { + return err + } + + if err := ioctlBlkDiscard(file.Fd(), 0, size); err != nil { + return err + } + + // Without this sometimes the remove of the device that happens after + // discard fails with EBUSY. + syscall.Sync() + + return nil +} + // This is the programmatic example of "dmsetup create" func createPool(poolName string, dataFile, metadataFile *osFile) error { task, err := createTask(DeviceCreate, poolName) diff --git a/graphdriver/devmapper/devmapper_wrapper.go b/graphdriver/devmapper/devmapper_wrapper.go index 80d430e2bf..7e6dd5e0cb 100644 --- a/graphdriver/devmapper/devmapper_wrapper.go +++ b/graphdriver/devmapper/devmapper_wrapper.go @@ -66,6 +66,7 @@ type ( // IOCTL consts const ( BlkGetSize64 = C.BLKGETSIZE64 + BlkDiscard = C.BLKDISCARD LoopSetFd = C.LOOP_SET_FD LoopCtlGetFree = C.LOOP_CTL_GET_FREE diff --git a/graphdriver/devmapper/driver_test.go b/graphdriver/devmapper/driver_test.go index b6d997bc2f..9a2e409b63 100644 --- a/graphdriver/devmapper/driver_test.go +++ b/graphdriver/devmapper/driver_test.go @@ -641,6 +641,10 @@ func TestDriverRemove(t *testing.T) { "DmTaskSetMessage", "DmTaskCreate", "DmTaskGetInfo", + "DmTaskSetCookie", + "DmTaskSetTarget", + "DmTaskSetAddNode", + "DmUdevWait", "Mounted", "sysUnmount", ) diff --git a/graphdriver/devmapper/ioctl.go b/graphdriver/devmapper/ioctl.go index 448d2d5a50..f9bf34f353 100644 --- a/graphdriver/devmapper/ioctl.go +++ b/graphdriver/devmapper/ioctl.go @@ -58,3 +58,14 @@ func ioctlBlkGetSize64(fd uintptr) (int64, error) { } return size, nil } + +func ioctlBlkDiscard(fd uintptr, offset, length uint64) error { + var r [2]uint64 + r[0] = offset + r[1] = length + + if _, _, err := sysSyscall(sysSysIoctl, fd, BlkDiscard, uintptr(unsafe.Pointer(&r[0]))); err != 0 { + return err + } + return nil +}