Browse code

devmapper: Provide a knob dm.xfs_nospace_max_retries

When xfs filesystem is being used on top of thin pool, xfs can get ENOSPC
errors from thin pool when thin pool is full. As of now xfs retries the
IO and keeps on retrying and does not give up. This can result in container
application being stuck for a very long time. In fact I have seen instances
of unkillable processes. So that means once thin pool is full and process
gets stuck, container can't be stopped/killed either and only option left
seems to be power recycle of the box.

In another instance, writer did not block but failed after a while. But
when I tried to exit/stop the container, unmounting xfs hanged and only
thing I could do was power cycle the machine.

Now upstream kernel has committed patches where it allows user space to
customize user space behavior in case of errors. One of the knobs is
max_retries, which specifies how many times an IO should be retried
when ENOSPC is encountered.

This patch sets provides a tunable knob (dm.xfs_nospace_max_retries) so
that user can specify value for max_retries and tune xfs behavior. If
one sets this value to 0, xfs will not retry IO when ENOSPC error is
encountered. It will instead give up and shutdown filesystem.

This knob can be useful if one is running into unkillable
processes/containers issue on top of xfs.

Signed-off-by: Vivek Goyal <vgoyal@redhat.com>

Vivek Goyal authored on 2016/08/31 23:13:04
Showing 2 changed files
... ...
@@ -122,6 +122,7 @@ type DeviceSet struct {
122 122
 	uidMaps               []idtools.IDMap
123 123
 	gidMaps               []idtools.IDMap
124 124
 	minFreeSpacePercent   uint32 //min free space percentage in thinpool
125
+	xfsNospaceRetries     string // max retries when xfs receives ENOSPC
125 126
 }
126 127
 
127 128
 // DiskUsage contains information about disk usage and is used when reporting Status of a device.
... ...
@@ -2308,6 +2309,38 @@ func (devices *DeviceSet) Shutdown(home string) error {
2308 2308
 	return nil
2309 2309
 }
2310 2310
 
2311
+// Recent XFS changes allow changing behavior of filesystem in case of errors.
2312
+// When thin pool gets full and XFS gets ENOSPC error, currently it tries
2313
+// IO infinitely and sometimes it can block the container process
2314
+// and process can't be killWith 0 value, XFS will not retry upon error
2315
+// and instead will shutdown filesystem.
2316
+
2317
+func (devices *DeviceSet) xfsSetNospaceRetries(info *devInfo) error {
2318
+	dmDevicePath, err := os.Readlink(info.DevName())
2319
+	if err != nil {
2320
+		return fmt.Errorf("devmapper: readlink failed for device %v:%v", info.DevName(), err)
2321
+	}
2322
+
2323
+	dmDeviceName := path.Base(dmDevicePath)
2324
+	filePath := "/sys/fs/xfs/" + dmDeviceName + "/error/metadata/ENOSPC/max_retries"
2325
+	maxRetriesFile, err := os.OpenFile(filePath, os.O_WRONLY, 0)
2326
+	if err != nil {
2327
+		// Older kernels don't have this feature/file
2328
+		if os.IsNotExist(err) {
2329
+			return nil
2330
+		}
2331
+		return fmt.Errorf("devmapper: Failed to open file %v:%v", filePath, err)
2332
+	}
2333
+	defer maxRetriesFile.Close()
2334
+
2335
+	// Set max retries to 0
2336
+	_, err = maxRetriesFile.WriteString(devices.xfsNospaceRetries)
2337
+	if err != nil {
2338
+		return fmt.Errorf("devmapper: Failed to write string %v to file %v:%v", devices.xfsNospaceRetries, filePath, err)
2339
+	}
2340
+	return nil
2341
+}
2342
+
2311 2343
 // MountDevice mounts the device if not already mounted.
2312 2344
 func (devices *DeviceSet) MountDevice(hash, path, mountLabel string) error {
2313 2345
 	info, err := devices.lookupDeviceWithLock(hash)
... ...
@@ -2348,6 +2381,12 @@ func (devices *DeviceSet) MountDevice(hash, path, mountLabel string) error {
2348 2348
 		return fmt.Errorf("devmapper: Error mounting '%s' on '%s': %s", info.DevName(), path, err)
2349 2349
 	}
2350 2350
 
2351
+	if fstype == "xfs" && devices.xfsNospaceRetries != "" {
2352
+		if err := devices.xfsSetNospaceRetries(info); err != nil {
2353
+			return err
2354
+		}
2355
+	}
2356
+
2351 2357
 	return nil
2352 2358
 }
2353 2359
 
... ...
@@ -2668,6 +2707,12 @@ func NewDeviceSet(root string, doInit bool, options []string, uidMaps, gidMaps [
2668 2668
 			}
2669 2669
 
2670 2670
 			devices.minFreeSpacePercent = uint32(minFreeSpacePercent)
2671
+		case "dm.xfs_nospace_max_retries":
2672
+			_, err := strconv.ParseUint(val, 10, 64)
2673
+			if err != nil {
2674
+				return nil, err
2675
+			}
2676
+			devices.xfsNospaceRetries = val
2671 2677
 		default:
2672 2678
 			return nil, fmt.Errorf("devmapper: Unknown option %s\n", key)
2673 2679
 		}
... ...
@@ -552,6 +552,22 @@ options for `zfs` start with `zfs` and options for `btrfs` start with `btrfs`.
552 552
     $ dockerd --storage-opt dm.min_free_space=10%
553 553
     ```
554 554
 
555
+*  `dm.xfs_nospace_max_retries`
556
+
557
+    Specifies the maximum number of retries XFS should attempt to complete
558
+    IO when ENOSPC (no space) error is returned by underlying storage device.
559
+
560
+    By default XFS retries infinitely for IO to finish and this can result
561
+    in unkillable process. To change this behavior one can set
562
+    xfs_nospace_max_retries to say 0 and XFS will not retry IO after getting
563
+    ENOSPC and will shutdown filesystem.
564
+
565
+    Example use:
566
+
567
+    ```bash
568
+    $ dockerd --storage-opt dm.xfs_nospace_max_retries=0
569
+    ```
570
+
555 571
 #### ZFS options
556 572
 
557 573
 * `zfs.fsname`