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:
parent
8a273345dc
commit
23bfc2a24e
|
@ -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 *);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue