Browse code

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 <alexl@redhat.com> (github: alexlarsson)

Alexander Larsson authored on 2013/12/17 17:12:44
Showing 5 changed files
... ...
@@ -568,6 +568,15 @@ func (devices *DeviceSet) removeDevice(hash string) error {
568 568
 		return fmt.Errorf("hash %s doesn't exists", hash)
569 569
 	}
570 570
 
571
+	// This is a workaround for the kernel not discarding block so
572
+	// on the thin pool when we remove a thinp device, so we do it
573
+	// manually
574
+	if err := devices.activateDeviceIfNeeded(hash); err == nil {
575
+		if err := BlockDeviceDiscard(info.DevName()); err != nil {
576
+			utils.Debugf("Error discarding block on device: %s (ignoring)\n", err)
577
+		}
578
+	}
579
+
571 580
 	devinfo, _ := getInfo(info.Name())
572 581
 	if devinfo != nil && devinfo.Exists != 0 {
573 582
 		if err := removeDevice(info.Name()); err != nil {
... ...
@@ -7,6 +7,7 @@ import (
7 7
 	"fmt"
8 8
 	"github.com/dotcloud/docker/utils"
9 9
 	"runtime"
10
+	"syscall"
10 11
 )
11 12
 
12 13
 type DevmapperLogger interface {
... ...
@@ -288,6 +289,29 @@ func GetBlockDeviceSize(file *osFile) (uint64, error) {
288 288
 	return uint64(size), nil
289 289
 }
290 290
 
291
+func BlockDeviceDiscard(path string) error {
292
+	file, err := osOpenFile(path, osORdWr, 0)
293
+	if err != nil {
294
+		return err
295
+	}
296
+	defer file.Close()
297
+
298
+	size, err := GetBlockDeviceSize(file)
299
+	if err != nil {
300
+		return err
301
+	}
302
+
303
+	if err := ioctlBlkDiscard(file.Fd(), 0, size); err != nil {
304
+		return err
305
+	}
306
+
307
+	// Without this sometimes the remove of the device that happens after
308
+	// discard fails with EBUSY.
309
+	syscall.Sync()
310
+
311
+	return nil
312
+}
313
+
291 314
 // This is the programmatic example of "dmsetup create"
292 315
 func createPool(poolName string, dataFile, metadataFile *osFile) error {
293 316
 	task, err := createTask(DeviceCreate, poolName)
... ...
@@ -66,6 +66,7 @@ type (
66 66
 // IOCTL consts
67 67
 const (
68 68
 	BlkGetSize64 = C.BLKGETSIZE64
69
+	BlkDiscard   = C.BLKDISCARD
69 70
 
70 71
 	LoopSetFd       = C.LOOP_SET_FD
71 72
 	LoopCtlGetFree  = C.LOOP_CTL_GET_FREE
... ...
@@ -641,6 +641,10 @@ func TestDriverRemove(t *testing.T) {
641 641
 			"DmTaskSetMessage",
642 642
 			"DmTaskCreate",
643 643
 			"DmTaskGetInfo",
644
+			"DmTaskSetCookie",
645
+			"DmTaskSetTarget",
646
+			"DmTaskSetAddNode",
647
+			"DmUdevWait",
644 648
 			"Mounted",
645 649
 			"sysUnmount",
646 650
 		)
... ...
@@ -58,3 +58,14 @@ func ioctlBlkGetSize64(fd uintptr) (int64, error) {
58 58
 	}
59 59
 	return size, nil
60 60
 }
61
+
62
+func ioctlBlkDiscard(fd uintptr, offset, length uint64) error {
63
+	var r [2]uint64
64
+	r[0] = offset
65
+	r[1] = length
66
+
67
+	if _, _, err := sysSyscall(sysSysIoctl, fd, BlkDiscard, uintptr(unsafe.Pointer(&r[0]))); err != 0 {
68
+		return err
69
+	}
70
+	return nil
71
+}