autofs4: allow RCU-walk to walk through autofs4

This series teaches autofs about RCU-walk so that we don't drop straight
into REF-walk when we hit an autofs directory, and so that we avoid
spinlocks as much as possible when performing an RCU-walk.

This is needed so that the benefits of the recent NFS support for
RCU-walk are fully available when NFS filesystems are automounted.

Patches have been carefully reviewed and tested both with test suites
and in production - thanks a lot to Ian Kent for his support there.

This patch (of 6):

Any attempt to look up a pathname that passes though an autofs4 mount is
currently forced out of RCU-walk into REF-walk.

This can significantly hurt performance of many-thread work loads on
many-core systems, especially if the automounted filesystem supports
RCU-walk but doesn't get to benefit from it.

So if autofs4_d_manage is called with rcu_walk set, only fail with -ECHILD
if it is necessary to wait longer than a spinlock.

Signed-off-by: NeilBrown <neilb@suse.de>
Reviewed-by: Ian Kent <raven@themaw.net>
Tested-by: Ian Kent <raven@themaw.net>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
This commit is contained in:
NeilBrown 2014-10-13 15:52:14 -07:00 committed by Linus Torvalds
parent 8a273345dc
commit 23bfc2a24e
4 changed files with 34 additions and 18 deletions

View file

@ -148,7 +148,7 @@ void autofs4_free_ino(struct autofs_info *);
/* Expiration */ /* Expiration */
int is_autofs4_dentry(struct dentry *); int is_autofs4_dentry(struct dentry *);
int autofs4_expire_wait(struct dentry *dentry); int autofs4_expire_wait(struct dentry *dentry, int rcu_walk);
int autofs4_expire_run(struct super_block *, struct vfsmount *, int autofs4_expire_run(struct super_block *, struct vfsmount *,
struct autofs_sb_info *, struct autofs_sb_info *,
struct autofs_packet_expire __user *); struct autofs_packet_expire __user *);

View file

@ -450,7 +450,7 @@ static int autofs_dev_ioctl_requester(struct file *fp,
ino = autofs4_dentry_ino(path.dentry); ino = autofs4_dentry_ino(path.dentry);
if (ino) { if (ino) {
err = 0; err = 0;
autofs4_expire_wait(path.dentry); autofs4_expire_wait(path.dentry, 0);
spin_lock(&sbi->fs_lock); spin_lock(&sbi->fs_lock);
param->requester.uid = from_kuid_munged(current_user_ns(), ino->uid); param->requester.uid = from_kuid_munged(current_user_ns(), ino->uid);
param->requester.gid = from_kgid_munged(current_user_ns(), ino->gid); param->requester.gid = from_kgid_munged(current_user_ns(), ino->gid);

View file

@ -461,7 +461,7 @@ found:
return expired; return expired;
} }
int autofs4_expire_wait(struct dentry *dentry) int autofs4_expire_wait(struct dentry *dentry, int rcu_walk)
{ {
struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb);
struct autofs_info *ino = autofs4_dentry_ino(dentry); struct autofs_info *ino = autofs4_dentry_ino(dentry);
@ -471,6 +471,8 @@ int autofs4_expire_wait(struct dentry *dentry)
spin_lock(&sbi->fs_lock); spin_lock(&sbi->fs_lock);
if (ino->flags & AUTOFS_INF_EXPIRING) { if (ino->flags & AUTOFS_INF_EXPIRING) {
spin_unlock(&sbi->fs_lock); spin_unlock(&sbi->fs_lock);
if (rcu_walk)
return -ECHILD;
DPRINTK("waiting for expire %p name=%.*s", DPRINTK("waiting for expire %p name=%.*s",
dentry, dentry->d_name.len, dentry->d_name.name); dentry, dentry->d_name.len, dentry->d_name.name);

View file

@ -210,7 +210,8 @@ next:
return NULL; return NULL;
} }
static struct dentry *autofs4_lookup_expiring(struct dentry *dentry) static struct dentry *autofs4_lookup_expiring(struct dentry *dentry,
bool rcu_walk)
{ {
struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb);
struct dentry *parent = dentry->d_parent; struct dentry *parent = dentry->d_parent;
@ -229,6 +230,11 @@ static struct dentry *autofs4_lookup_expiring(struct dentry *dentry)
struct dentry *expiring; struct dentry *expiring;
struct qstr *qstr; struct qstr *qstr;
if (rcu_walk) {
spin_unlock(&sbi->lookup_lock);
return ERR_PTR(-ECHILD);
}
ino = list_entry(p, struct autofs_info, expiring); ino = list_entry(p, struct autofs_info, expiring);
expiring = ino->dentry; expiring = ino->dentry;
@ -264,13 +270,15 @@ next:
return NULL; return NULL;
} }
static int autofs4_mount_wait(struct dentry *dentry) static int autofs4_mount_wait(struct dentry *dentry, bool rcu_walk)
{ {
struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb);
struct autofs_info *ino = autofs4_dentry_ino(dentry); struct autofs_info *ino = autofs4_dentry_ino(dentry);
int status = 0; int status = 0;
if (ino->flags & AUTOFS_INF_PENDING) { if (ino->flags & AUTOFS_INF_PENDING) {
if (rcu_walk)
return -ECHILD;
DPRINTK("waiting for mount name=%.*s", DPRINTK("waiting for mount name=%.*s",
dentry->d_name.len, dentry->d_name.name); dentry->d_name.len, dentry->d_name.name);
status = autofs4_wait(sbi, dentry, NFY_MOUNT); status = autofs4_wait(sbi, dentry, NFY_MOUNT);
@ -280,20 +288,22 @@ static int autofs4_mount_wait(struct dentry *dentry)
return status; return status;
} }
static int do_expire_wait(struct dentry *dentry) static int do_expire_wait(struct dentry *dentry, bool rcu_walk)
{ {
struct dentry *expiring; struct dentry *expiring;
expiring = autofs4_lookup_expiring(dentry); expiring = autofs4_lookup_expiring(dentry, rcu_walk);
if (IS_ERR(expiring))
return PTR_ERR(expiring);
if (!expiring) if (!expiring)
return autofs4_expire_wait(dentry); return autofs4_expire_wait(dentry, rcu_walk);
else { else {
/* /*
* If we are racing with expire the request might not * If we are racing with expire the request might not
* be quite complete, but the directory has been removed * be quite complete, but the directory has been removed
* so it must have been successful, just wait for it. * so it must have been successful, just wait for it.
*/ */
autofs4_expire_wait(expiring); autofs4_expire_wait(expiring, 0);
autofs4_del_expiring(expiring); autofs4_del_expiring(expiring);
dput(expiring); dput(expiring);
} }
@ -345,7 +355,7 @@ static struct vfsmount *autofs4_d_automount(struct path *path)
* and the directory was removed, so just go ahead and try * and the directory was removed, so just go ahead and try
* the mount. * the mount.
*/ */
status = do_expire_wait(dentry); status = do_expire_wait(dentry, 0);
if (status && status != -EAGAIN) if (status && status != -EAGAIN)
return NULL; return NULL;
@ -353,7 +363,7 @@ static struct vfsmount *autofs4_d_automount(struct path *path)
spin_lock(&sbi->fs_lock); spin_lock(&sbi->fs_lock);
if (ino->flags & AUTOFS_INF_PENDING) { if (ino->flags & AUTOFS_INF_PENDING) {
spin_unlock(&sbi->fs_lock); spin_unlock(&sbi->fs_lock);
status = autofs4_mount_wait(dentry); status = autofs4_mount_wait(dentry, 0);
if (status) if (status)
return ERR_PTR(status); return ERR_PTR(status);
goto done; goto done;
@ -394,7 +404,7 @@ static struct vfsmount *autofs4_d_automount(struct path *path)
} }
ino->flags |= AUTOFS_INF_PENDING; ino->flags |= AUTOFS_INF_PENDING;
spin_unlock(&sbi->fs_lock); spin_unlock(&sbi->fs_lock);
status = autofs4_mount_wait(dentry); status = autofs4_mount_wait(dentry, 0);
spin_lock(&sbi->fs_lock); spin_lock(&sbi->fs_lock);
ino->flags &= ~AUTOFS_INF_PENDING; ino->flags &= ~AUTOFS_INF_PENDING;
if (status) { if (status) {
@ -430,21 +440,25 @@ static int autofs4_d_manage(struct dentry *dentry, bool rcu_walk)
return 0; return 0;
} }
/* We need to sleep, so we need pathwalk to be in ref-mode */
if (rcu_walk)
return -ECHILD;
/* Wait for pending expires */ /* Wait for pending expires */
do_expire_wait(dentry); if (do_expire_wait(dentry, rcu_walk) == -ECHILD)
return -ECHILD;
/* /*
* This dentry may be under construction so wait on mount * This dentry may be under construction so wait on mount
* completion. * completion.
*/ */
status = autofs4_mount_wait(dentry); status = autofs4_mount_wait(dentry, rcu_walk);
if (status) if (status)
return status; return status;
if (rcu_walk)
/* it is always safe to return 0 as the worst that
* will happen is we retry in REF-walk mode.
* Better than always taking a lock.
*/
return 0;
spin_lock(&sbi->fs_lock); spin_lock(&sbi->fs_lock);
/* /*
* If the dentry has been selected for expire while we slept * If the dentry has been selected for expire while we slept