svn commit: r367631 - in head/sys: kern sys
Konstantin Belousov
kib at FreeBSD.org
Fri Nov 13 09:31:58 UTC 2020
Author: kib
Date: Fri Nov 13 09:31:57 2020
New Revision: 367631
URL: https://svnweb.freebsd.org/changeset/base/367631
Log:
Implement vn_lock_pair().
In collaboration with: pho
Reviewed by: mckusick (previous version), markj (previous version)
Tested by: markj (syzkaller), pho
Sponsored by: The FreeBSD Foundation
Differential revision: https://reviews.freebsd.org/D26136
Modified:
head/sys/kern/vfs_vnops.c
head/sys/sys/vnode.h
Modified: head/sys/kern/vfs_vnops.c
==============================================================================
--- head/sys/kern/vfs_vnops.c Fri Nov 13 02:05:45 2020 (r367630)
+++ head/sys/kern/vfs_vnops.c Fri Nov 13 09:31:57 2020 (r367631)
@@ -70,6 +70,7 @@ __FBSDID("$FreeBSD$");
#include <sys/filio.h>
#include <sys/resourcevar.h>
#include <sys/rwlock.h>
+#include <sys/prng.h>
#include <sys/sx.h>
#include <sys/sleepqueue.h>
#include <sys/sysctl.h>
@@ -275,6 +276,10 @@ restart:
vn_finished_write(mp);
if (error) {
NDFREE(ndp, NDF_ONLY_PNBUF);
+ if (error == ERELOOKUP) {
+ NDREINIT(ndp);
+ goto restart;
+ }
return (error);
}
fmode &= ~O_TRUNC;
@@ -1524,6 +1529,7 @@ vn_truncate(struct file *fp, off_t length, struct ucre
vp = fp->f_vnode;
+retry:
/*
* Lock the whole range for truncation. Otherwise split i/o
* might happen partly before and partly after the truncation.
@@ -1550,6 +1556,8 @@ out:
vn_finished_write(mp);
out1:
vn_rangelock_unlock(vp, rl_cookie);
+ if (error == ERELOOKUP)
+ goto retry;
return (error);
}
@@ -3317,4 +3325,92 @@ vn_fallocate(struct file *fp, off_t offset, off_t len,
}
return (error);
+}
+
+static u_long vn_lock_pair_pause_cnt;
+SYSCTL_ULONG(_debug, OID_AUTO, vn_lock_pair_pause, CTLFLAG_RD,
+ &vn_lock_pair_pause_cnt, 0,
+ "Count of vn_lock_pair deadlocks");
+
+static void
+vn_lock_pair_pause(const char *wmesg)
+{
+ atomic_add_long(&vn_lock_pair_pause_cnt, 1);
+ pause(wmesg, prng32_bounded(hz / 10));
+}
+
+/*
+ * Lock pair of vnodes vp1, vp2, avoiding lock order reversal.
+ * vp1_locked indicates whether vp1 is exclusively locked; if not, vp1
+ * must be unlocked. Same for vp2 and vp2_locked. One of the vnodes
+ * can be NULL.
+ *
+ * The function returns with both vnodes exclusively locked, and
+ * guarantees that it does not create lock order reversal with other
+ * threads during its execution. Both vnodes could be unlocked
+ * temporary (and reclaimed).
+ */
+void
+vn_lock_pair(struct vnode *vp1, bool vp1_locked, struct vnode *vp2,
+ bool vp2_locked)
+{
+ int error;
+
+ if (vp1 == NULL && vp2 == NULL)
+ return;
+ if (vp1 != NULL) {
+ if (vp1_locked)
+ ASSERT_VOP_ELOCKED(vp1, "vp1");
+ else
+ ASSERT_VOP_UNLOCKED(vp1, "vp1");
+ } else {
+ vp1_locked = true;
+ }
+ if (vp2 != NULL) {
+ if (vp2_locked)
+ ASSERT_VOP_ELOCKED(vp2, "vp2");
+ else
+ ASSERT_VOP_UNLOCKED(vp2, "vp2");
+ } else {
+ vp2_locked = true;
+ }
+ if (!vp1_locked && !vp2_locked) {
+ vn_lock(vp1, LK_EXCLUSIVE | LK_RETRY);
+ vp1_locked = true;
+ }
+
+ for (;;) {
+ if (vp1_locked && vp2_locked)
+ break;
+ if (vp1_locked && vp2 != NULL) {
+ if (vp1 != NULL) {
+ error = VOP_LOCK1(vp2, LK_EXCLUSIVE | LK_NOWAIT,
+ __FILE__, __LINE__);
+ if (error == 0)
+ break;
+ VOP_UNLOCK(vp1);
+ vp1_locked = false;
+ vn_lock_pair_pause("vlp1");
+ }
+ vn_lock(vp2, LK_EXCLUSIVE | LK_RETRY);
+ vp2_locked = true;
+ }
+ if (vp2_locked && vp1 != NULL) {
+ if (vp2 != NULL) {
+ error = VOP_LOCK1(vp1, LK_EXCLUSIVE | LK_NOWAIT,
+ __FILE__, __LINE__);
+ if (error == 0)
+ break;
+ VOP_UNLOCK(vp2);
+ vp2_locked = false;
+ vn_lock_pair_pause("vlp2");
+ }
+ vn_lock(vp1, LK_EXCLUSIVE | LK_RETRY);
+ vp1_locked = true;
+ }
+ }
+ if (vp1 != NULL)
+ ASSERT_VOP_ELOCKED(vp1, "vp1 ret");
+ if (vp2 != NULL)
+ ASSERT_VOP_ELOCKED(vp2, "vp2 ret");
}
Modified: head/sys/sys/vnode.h
==============================================================================
--- head/sys/sys/vnode.h Fri Nov 13 02:05:45 2020 (r367630)
+++ head/sys/sys/vnode.h Fri Nov 13 09:31:57 2020 (r367631)
@@ -729,6 +729,8 @@ bool vn_isdisk_error(struct vnode *vp, int *errp);
bool vn_isdisk(struct vnode *vp);
int _vn_lock(struct vnode *vp, int flags, const char *file, int line);
#define vn_lock(vp, flags) _vn_lock(vp, flags, __FILE__, __LINE__)
+void vn_lock_pair(struct vnode *vp1, bool vp1_locked, struct vnode *vp2,
+ bool vp2_locked);
int vn_open(struct nameidata *ndp, int *flagp, int cmode, struct file *fp);
int vn_open_cred(struct nameidata *ndp, int *flagp, int cmode,
u_int vn_open_flags, struct ucred *cred, struct file *fp);
More information about the svn-src-head
mailing list