From: NeilBrown <neilb@ownmail.net>
To: Linus Torvalds <torvalds@linux-foundation.org>,
Alexander Viro <viro@zeniv.linux.org.uk>,
Christian Brauner <brauner@kernel.org>, Jan Kara <jack@suse.cz>,
Jeff Layton <jlayton@kernel.org>,
Trond Myklebust <trondmy@kernel.org>,
Anna Schumaker <anna@kernel.org>,
Miklos Szeredi <miklos@szeredi.hu>,
Amir Goldstein <amir73il@gmail.com>, Jeremy Kerr <jk@ozlabs.org>,
Ard Biesheuvel <ardb@kernel.org>,
Christoph Hellwig <hch@infradead.org>
Cc: linux-efi@vger.kernel.org, linux-fsdevel@vger.kernel.org,
linux-nfs@vger.kernel.org, linux-unionfs@vger.kernel.org,
linux-kernel@vger.kernel
Subject: [PATCH v2 05/19] VFS: introduce d_alloc_noblock()
Date: Mon, 27 Apr 2026 13:29:38 +1000 [thread overview]
Message-ID: <20260427033527.773006-6-neilb@ownmail.net> (raw)
In-Reply-To: <20260427033527.773006-1-neilb@ownmail.net>
From: NeilBrown <neil@brown.name>
Several filesystems use the results of readdir to prime the dcache.
These filesystems use d_alloc_parallel() which can block if there is a
concurrent lookup. Blocking in that case is pointless as the lookup
will add info to the dcache and there is no value in the readdir waiting
to see if it should add the info too.
Also these calls to d_alloc_parallel() are made while the parent
directory is locked. A proposed change to locking will lock the parent
later, after d_alloc_parallel(). This means it won't be safe to wait in
d_alloc_parallel() while holding the directory lock.
So this patch introduces d_alloc_noblock() which doesn't block but
instead returns ERR_PTR(-EWOULDBLOCK). Filesystems that prime the
dcache (smb/client, nfs, fuse, cephfs) can now use that and ignore
-EWOULDBLOCK errors as harmless.
Unlike d_alloc_parallel(), d_alloc_noblock() calculates the hash and
performs a lookup before an allocation, as that is what all callers
want.
Signed-off-by: NeilBrown <neil@brown.name>
---
fs/dcache.c | 91 +++++++++++++++++++++++++++++++++++++++---
include/linux/dcache.h | 1 +
2 files changed, 87 insertions(+), 5 deletions(-)
diff --git a/fs/dcache.c b/fs/dcache.c
index 2dcefa60db32..dc06e70695d2 100644
--- a/fs/dcache.c
+++ b/fs/dcache.c
@@ -32,6 +32,7 @@
#include <linux/bit_spinlock.h>
#include <linux/rculist_bl.h>
#include <linux/list_lru.h>
+#include <linux/namei.h>
#include "internal.h"
#include "mount.h"
@@ -2672,8 +2673,16 @@ static inline bool d_must_wait(struct dentry *dentry)
return true;
}
-struct dentry *d_alloc_parallel(struct dentry *parent,
- const struct qstr *name)
+/* What to do when __d_alloc_parallel finds a d_in_lookup dentry */
+enum alloc_para {
+ ALLOC_PARA_WAIT,
+ ALLOC_PARA_FAIL,
+};
+
+static inline
+struct dentry *__d_alloc_parallel(struct dentry *parent,
+ const struct qstr *name,
+ enum alloc_para how)
{
unsigned int hash = name->hash;
struct hlist_bl_head *b = in_lookup_hash(parent, hash);
@@ -2755,9 +2764,20 @@ struct dentry *d_alloc_parallel(struct dentry *parent,
* wait for them to finish
*/
spin_lock(&dentry->d_lock);
- wait_var_event_spinlock(&dentry->d_flags,
- !d_must_wait(dentry),
- &dentry->d_lock);
+ if (d_in_lookup(dentry))
+ switch (how) {
+ case ALLOC_PARA_FAIL:
+ spin_unlock(&dentry->d_lock);
+ dput(new);
+ dput(dentry);
+ return ERR_PTR(-EWOULDBLOCK);
+ case ALLOC_PARA_WAIT:
+ wait_var_event_spinlock(&dentry->d_flags,
+ !d_must_wait(dentry),
+ &dentry->d_lock);
+ /* ... and continue */
+ }
+
/*
* it's not in-lookup anymore; in principle we should repeat
* everything from dcache lookup, but it's likely to be what
@@ -2786,8 +2806,69 @@ struct dentry *d_alloc_parallel(struct dentry *parent,
dput(dentry);
goto retry;
}
+
+/**
+ * d_alloc_parallel() - allocate a new dentry and ensure uniqueness
+ * @parent - dentry of the parent
+ * @name - name of the dentry within that parent.
+ *
+ * A new dentry is allocated and, providing it is unique, added to the
+ * relevant index.
+ * If an existing dentry is found with the same parent/name that is
+ * not d_in_lookup(), then that is returned instead.
+ * If the existing dentry is d_in_lookup(), d_alloc_parallel() waits for
+ * that lookup to complete before returning the dentry and then ensures the
+ * match is still valid.
+ * Thus if the returned dentry is d_in_lookup() then the caller has
+ * exclusive access until it completes the lookup.
+ * If the returned dentry is not d_in_lookup() then a lookup has
+ * already completed.
+ *
+ * The @name must already have ->hash set, as can be achieved
+ * by e.g. try_lookup_noperm().
+ *
+ * Returns: the dentry, whether found or allocated, or an error %-ENOMEM.
+ */
+struct dentry *d_alloc_parallel(struct dentry *parent,
+ const struct qstr *name)
+{
+ return __d_alloc_parallel(parent, name, ALLOC_PARA_WAIT);
+}
EXPORT_SYMBOL(d_alloc_parallel);
+/**
+ * d_alloc_noblock() - find or allocate a new dentry
+ * @parent - dentry of the parent
+ * @name - name of the dentry within that parent.
+ *
+ * A new dentry is allocated and, providing it is unique, added to the
+ * relevant index.
+ * If an existing dentry is found with the same parent/name that is
+ * not d_in_lookup() then that is returned instead.
+ * If the existing dentry is d_in_lookup(), d_alloc_noblock()
+ * returns with error %-EWOULDBLOCK.
+ * Thus if the returned dentry is d_in_lookup() then the caller has
+ * exclusive access until it completes the lookup.
+ * If the returned dentry is not d_in_lookup() then a lookup has
+ * already completed.
+ *
+ * The @name need not already have ->hash set.
+ *
+ * Returns: the dentry, whether found or allocated, or an error
+ * %-ENOMEM, %-EWOULDBLOCK, and anything returned by ->d_hash().
+ */
+struct dentry *d_alloc_noblock(struct dentry *parent,
+ struct qstr *name)
+{
+ struct dentry *de;
+
+ de = try_lookup_noperm(name, parent);
+ if (!de)
+ de = __d_alloc_parallel(parent, name, ALLOC_PARA_FAIL);
+ return de;
+}
+EXPORT_SYMBOL(d_alloc_noblock);
+
/*
* - Unhash the dentry
* - Retrieve and clear the waitqueue head in dentry
diff --git a/include/linux/dcache.h b/include/linux/dcache.h
index 14b91a7d0bb6..85e8570cbd48 100644
--- a/include/linux/dcache.h
+++ b/include/linux/dcache.h
@@ -257,6 +257,7 @@ extern void d_delete(struct dentry *);
extern struct dentry * d_alloc(struct dentry *, const struct qstr *);
extern struct dentry * d_alloc_anon(struct super_block *);
extern struct dentry * d_alloc_parallel(struct dentry *, const struct qstr *);
+extern struct dentry * d_alloc_noblock(struct dentry *, struct qstr *);
extern struct dentry * d_splice_alias(struct inode *, struct dentry *);
/* weird procfs mess; *NOT* exported */
extern struct dentry * d_splice_alias_ops(struct inode *, struct dentry *,
--
2.50.0.107.gf914562f5916.dirty
next prev parent reply other threads:[~2026-04-27 3:36 UTC|newest]
Thread overview: 22+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-04-27 3:29 [PATCH v2 00/19] Prepare to lift lookup out of exclusive lock for directory ops NeilBrown
2026-04-27 3:29 ` [PATCH v2 01/19] VFS: fix various typos in documentation for start_creating start_removing etc NeilBrown
2026-04-27 3:29 ` [PATCH v2 02/19] VFS: enhance d_splice_alias() to handle in-lookup dentries NeilBrown
2026-04-27 3:29 ` [PATCH v2 03/19] VFS: allow d_alloc_name() to be used with ->d_hash NeilBrown
2026-04-27 3:29 ` [PATCH v2 04/19] VFS: use wait_var_event for waiting in d_alloc_parallel() NeilBrown
2026-04-27 3:29 ` NeilBrown [this message]
2026-04-27 3:29 ` [PATCH v2 06/19] VFS: add d_duplicate() NeilBrown
2026-04-27 3:29 ` [PATCH v2 07/19] VFS: Add LOOKUP_SHARED flag NeilBrown
2026-04-27 3:29 ` [PATCH v2 08/19] VFS/xfs/ntfs: drop parent lock across d_alloc_parallel() in d_add_ci() NeilBrown
2026-04-27 3:29 ` [PATCH v2 09/19] ovl: stop using lookup_one() in iterate_shared() handling NeilBrown
2026-04-27 3:29 ` [PATCH v2 10/19] VFS/ovl: add d_alloc_noblock_return() NeilBrown
2026-04-27 3:29 ` [PATCH v2 11/19] efivarfs: use d_alloc_name() NeilBrown
2026-04-27 3:29 ` [PATCH v2 12/19] shmem: use d_duplicate() NeilBrown
2026-04-27 3:29 ` [PATCH v2 13/19] nfs: remove d_drop()/d_alloc_parallel() from nfs_atomic_open() NeilBrown
2026-04-27 3:29 ` [PATCH v2 14/19] nfs: use d_splice_alias() in nfs_link() NeilBrown
2026-04-27 3:29 ` [PATCH v2 15/19] nfs: don't d_drop() before d_splice_alias() NeilBrown
2026-04-27 3:29 ` [PATCH v2 16/19] nfs: don't d_drop() before d_splice_alias() in atomic_create NeilBrown
2026-04-27 3:29 ` [PATCH v2 17/19] nfs: Use d_alloc_noblock() in nfs_prime_dcache() NeilBrown
2026-04-27 3:29 ` [PATCH v2 18/19] nfs: use d_alloc_noblock() in silly-rename NeilBrown
2026-04-27 3:29 ` [PATCH v2 19/19] nfs: use d_duplicate() NeilBrown
2026-04-27 3:47 ` [PATCH v2 00/19] Prepare to lift lookup out of exclusive lock for directory ops Al Viro
2026-04-27 8:41 ` [syzbot ci] " syzbot ci
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260427033527.773006-6-neilb@ownmail.net \
--to=neilb@ownmail.net \
--cc=amir73il@gmail.com \
--cc=anna@kernel.org \
--cc=ardb@kernel.org \
--cc=brauner@kernel.org \
--cc=hch@infradead.org \
--cc=jack@suse.cz \
--cc=jk@ozlabs.org \
--cc=jlayton@kernel.org \
--cc=linux-efi@vger.kernel.org \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-kernel@vger.kernel \
--cc=linux-nfs@vger.kernel.org \
--cc=linux-unionfs@vger.kernel.org \
--cc=miklos@szeredi.hu \
--cc=neil@brown.name \
--cc=torvalds@linux-foundation.org \
--cc=trondmy@kernel.org \
--cc=viro@zeniv.linux.org.uk \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox