The branch main has been updated by jah:

URL: 
https://cgit.FreeBSD.org/src/commit/?id=a8c732f4e52ec4d64e963035f87d79c270953cbc

commit a8c732f4e52ec4d64e963035f87d79c270953cbc
Author:     Jason A. Harmening <j...@freebsd.org>
AuthorDate: 2021-08-08 05:29:46 +0000
Commit:     Jason A. Harmening <j...@freebsd.org>
CommitDate: 2021-08-20 20:20:50 +0000

    VFS: add retry limit and delay for failed recursive unmounts
    
    A forcible unmount attempt may fail due to a transient condition, but
    it may also fail due to some issue in the filesystem implementation
    that will indefinitely prevent successful unmount.  In such a case,
    the retry logic in the recursive unmount facility will cause the
    deferred unmount taskqueue to execute constantly.
    
    Avoid this scenario by imposing a retry limit, with a default value
    of 10, beyond which the recursive unmount facility will emit a log
    message and give up.  Additionally, introduce a grace period, with
    a default value of 1s, between successive unmount retries on the
    same mount.
    
    Create a new sysctl node, vfs.deferred_unmount, to export the total
    number of failed recursive unmount attempts since boot, and to allow
    the retry limit and retry grace period to be tuned.
    
    Reviewed by:    kib (earlier revision), mkusick
    Differential Revision:  https://reviews.freebsd.org/D31450
---
 sys/kern/vfs_mount.c | 77 ++++++++++++++++++++++++++++++++++++++++++----------
 sys/sys/mount.h      |  1 +
 2 files changed, 64 insertions(+), 14 deletions(-)

diff --git a/sys/kern/vfs_mount.c b/sys/kern/vfs_mount.c
index 92e70e45d46e..0fb5694ebed5 100644
--- a/sys/kern/vfs_mount.c
+++ b/sys/kern/vfs_mount.c
@@ -95,6 +95,24 @@ SYSCTL_BOOL(_vfs, OID_AUTO, recursive_forced_unmount, 
CTLFLAG_RW,
     &recursive_forced_unmount, 0, "Recursively unmount stacked upper mounts"
     " when a file system is forcibly unmounted");
 
+static SYSCTL_NODE(_vfs, OID_AUTO, deferred_unmount,
+    CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "deferred unmount controls");
+
+static unsigned int    deferred_unmount_retry_limit = 10;
+SYSCTL_UINT(_vfs_deferred_unmount, OID_AUTO, retry_limit, CTLFLAG_RW,
+    &deferred_unmount_retry_limit, 0,
+    "Maximum number of retries for deferred unmount failure");
+
+static int     deferred_unmount_retry_delay_hz;
+SYSCTL_INT(_vfs_deferred_unmount, OID_AUTO, retry_delay_hz, CTLFLAG_RW,
+    &deferred_unmount_retry_delay_hz, 0,
+    "Delay in units of [1/kern.hz]s when retrying a failed deferred unmount");
+
+static int     deferred_unmount_total_retries = 0;
+SYSCTL_INT(_vfs_deferred_unmount, OID_AUTO, total_retries, CTLFLAG_RD,
+    &deferred_unmount_total_retries, 0,
+    "Total number of retried deferred unmounts");
+
 MALLOC_DEFINE(M_MOUNT, "mount", "vfs mount structure");
 MALLOC_DEFINE(M_STATFS, "statfs", "statfs structure");
 static uma_zone_t mount_zone;
@@ -110,8 +128,7 @@ EVENTHANDLER_LIST_DEFINE(vfs_mounted);
 EVENTHANDLER_LIST_DEFINE(vfs_unmounted);
 
 static void vfs_deferred_unmount(void *arg, int pending);
-static struct task deferred_unmount_task =
-    TASK_INITIALIZER(0, vfs_deferred_unmount, NULL);;
+static struct timeout_task deferred_unmount_task;
 static struct mtx deferred_unmount_lock;
 MTX_SYSINIT(deferred_unmount, &deferred_unmount_lock, "deferred_unmount",
     MTX_DEF);
@@ -166,7 +183,9 @@ mount_fini(void *mem, int size)
 static void
 vfs_mount_init(void *dummy __unused)
 {
-
+       TIMEOUT_TASK_INIT(taskqueue_deferred_unmount, &deferred_unmount_task,
+           0, vfs_deferred_unmount, NULL);
+       deferred_unmount_retry_delay_hz = hz;
        mount_zone = uma_zcreate("Mountpoints", sizeof(struct mount), NULL,
            NULL, mount_init, mount_fini, UMA_ALIGN_CACHE, UMA_ZONE_NOFREE);
 }
@@ -688,6 +707,7 @@ vfs_mount_alloc(struct vnode *vp, struct vfsconf *vfsp, 
const char *fspath,
        TAILQ_INIT(&mp->mnt_uppers);
        TAILQ_INIT(&mp->mnt_notify);
        mp->mnt_taskqueue_flags = 0;
+       mp->mnt_unmount_retries = 0;
        return (mp);
 }
 
@@ -1895,7 +1915,8 @@ vfs_mount_fetch_counter(struct mount *mp, enum 
mount_counter which)
 }
 
 static bool
-deferred_unmount_enqueue(struct mount *mp, uint64_t flags, bool requeue)
+deferred_unmount_enqueue(struct mount *mp, uint64_t flags, bool requeue,
+    int timeout_ticks)
 {
        bool enqueued;
 
@@ -1910,8 +1931,8 @@ deferred_unmount_enqueue(struct mount *mp, uint64_t 
flags, bool requeue)
        mtx_unlock(&deferred_unmount_lock);
 
        if (enqueued) {
-               taskqueue_enqueue(taskqueue_deferred_unmount,
-                   &deferred_unmount_task);
+               taskqueue_enqueue_timeout(taskqueue_deferred_unmount,
+                   &deferred_unmount_task, timeout_ticks);
        }
 
        return (enqueued);
@@ -1926,6 +1947,8 @@ vfs_deferred_unmount(void *argi __unused, int pending 
__unused)
        STAILQ_HEAD(, mount) local_unmounts;
        uint64_t flags;
        struct mount *mp, *tmp;
+       int error;
+       unsigned int retries;
        bool unmounted;
 
        STAILQ_INIT(&local_unmounts);
@@ -1937,14 +1960,30 @@ vfs_deferred_unmount(void *argi __unused, int pending 
__unused)
                flags = mp->mnt_taskqueue_flags;
                KASSERT((flags & MNT_DEFERRED) != 0,
                    ("taskqueue unmount without MNT_DEFERRED"));
-               if (dounmount(mp, flags, curthread) != 0) {
+               error = dounmount(mp, flags, curthread);
+               if (error != 0) {
                        MNT_ILOCK(mp);
                        unmounted = ((mp->mnt_kern_flag & MNTK_REFEXPIRE) != 0);
                        MNT_IUNLOCK(mp);
-                       if (!unmounted)
-                               deferred_unmount_enqueue(mp, flags, true);
-                       else
+
+                       /*
+                        * The deferred unmount thread is the only thread that
+                        * modifies the retry counts, so locking/atomics aren't
+                        * needed here.
+                        */
+                       retries = (mp->mnt_unmount_retries)++;
+                       deferred_unmount_total_retries++;
+                       if (!unmounted && retries < 
deferred_unmount_retry_limit) {
+                               deferred_unmount_enqueue(mp, flags, true,
+                                   -deferred_unmount_retry_delay_hz);
+                       } else {
+                               if (retries >= deferred_unmount_retry_limit) {
+                                       printf("giving up on deferred unmount "
+                                           "of %s after %d retries, error 
%d\n",
+                                           mp->mnt_stat.f_mntonname, retries, 
error);
+                               }
                                vfs_rel(mp);
+                       }
                }
        }
 }
@@ -1960,6 +1999,7 @@ dounmount(struct mount *mp, uint64_t flags, struct thread 
*td)
        int error;
        uint64_t async_flag;
        int mnt_gen_r;
+       unsigned int retries;
 
        KASSERT((flags & MNT_DEFERRED) == 0 ||
            (flags & (MNT_RECURSE | MNT_FORCE)) == (MNT_RECURSE | MNT_FORCE),
@@ -1976,7 +2016,7 @@ dounmount(struct mount *mp, uint64_t flags, struct thread 
*td)
         */
        if ((flags & MNT_DEFERRED) != 0 &&
            taskqueue_member(taskqueue_deferred_unmount, curthread) == 0) {
-               if (!deferred_unmount_enqueue(mp, flags, false))
+               if (!deferred_unmount_enqueue(mp, flags, false, 0))
                        vfs_rel(mp);
                return (EINPROGRESS);
        }
@@ -2017,9 +2057,16 @@ dounmount(struct mount *mp, uint64_t flags, struct 
thread *td)
                mp->mnt_kern_flag |= MNTK_RECURSE;
                mp->mnt_upper_pending++;
                TAILQ_FOREACH(upper, &mp->mnt_uppers, mnt_upper_link) {
+                       retries = upper->mp->mnt_unmount_retries;
+                       if (retries > deferred_unmount_retry_limit) {
+                               error = EBUSY;
+                               continue;
+                       }
                        MNT_IUNLOCK(mp);
+
                        vfs_ref(upper->mp);
-                       if (!deferred_unmount_enqueue(upper->mp, flags, false))
+                       if (!deferred_unmount_enqueue(upper->mp, flags,
+                           false, 0))
                                vfs_rel(upper->mp);
                        MNT_ILOCK(mp);
                }
@@ -2029,6 +2076,7 @@ dounmount(struct mount *mp, uint64_t flags, struct thread 
*td)
                        mp->mnt_kern_flag &= ~MNTK_UPPER_WAITER;
                        wakeup(&mp->mnt_uppers);
                }
+
                /*
                 * If we're not on the taskqueue, wait until the uppers list
                 * is drained before proceeding with unmount.  Otherwise, if
@@ -2043,8 +2091,9 @@ dounmount(struct mount *mp, uint64_t flags, struct thread 
*td)
                        }
                } else if (!TAILQ_EMPTY(&mp->mnt_uppers)) {
                        MNT_IUNLOCK(mp);
-                       deferred_unmount_enqueue(mp, flags, true);
-                       return (0);
+                       if (error == 0)
+                               deferred_unmount_enqueue(mp, flags, true, 0);
+                       return (error);
                }
                MNT_IUNLOCK(mp);
                KASSERT(TAILQ_EMPTY(&mp->mnt_uppers), ("mnt_uppers not empty"));
diff --git a/sys/sys/mount.h b/sys/sys/mount.h
index f4b5945d3ad0..8368595b685b 100644
--- a/sys/sys/mount.h
+++ b/sys/sys/mount.h
@@ -261,6 +261,7 @@ struct mount {
        TAILQ_HEAD(, mount_upper_node) mnt_notify; /* (i) upper mounts for 
notification */
        STAILQ_ENTRY(mount) mnt_taskqueue_link; /* (d) our place in deferred 
unmount list */
        uint64_t        mnt_taskqueue_flags;    /* (d) unmount flags passed 
from taskqueue */
+       unsigned int    mnt_unmount_retries;    /* (d) # of failed deferred 
unmount attempts */
 };
 #endif /* _WANT_MOUNT || _KERNEL */
 
_______________________________________________
dev-commits-src-all@freebsd.org mailing list
https://lists.freebsd.org/mailman/listinfo/dev-commits-src-all
To unsubscribe, send any mail to "dev-commits-src-all-unsubscr...@freebsd.org"

Reply via email to