From: Steve French <smfrench@gmail.com>
To: linux-cifs@vger.kernel.org
Cc: Shyam Prasad N <sprasad@microsoft.com>
Subject: [PATCH 12/16] cifs: allow dcache population to happen asynchronously
Date: Tue, 23 Jun 2026 15:13:39 -0500 [thread overview]
Message-ID: <20260623201344.2043841-12-stfrench@microsoft.com> (raw)
In-Reply-To: <20260623201344.2043841-1-stfrench@microsoft.com>
From: Shyam Prasad N <sprasad@microsoft.com>
Today cifs_readdir populates the dcache inline whenever it
emits dentries. This introduces a perf bottleneck in
readdir performance.
This change attempts to make dcache population asynchronous
and parallel by doing this inside workers. It introduces a new
flag that lets reval and lookup wait for dentry population instead.
Signed-off-by: Shyam Prasad N <sprasad@microsoft.com>
---
fs/smb/client/cifs_fs_sb.h | 1 +
fs/smb/client/cifsfs.c | 22 +++++++++-
fs/smb/client/cifsglob.h | 2 +
fs/smb/client/readdir.c | 88 +++++++++++++++++++++++++++++++++++++-
4 files changed, 110 insertions(+), 3 deletions(-)
diff --git a/fs/smb/client/cifs_fs_sb.h b/fs/smb/client/cifs_fs_sb.h
index 84e7e366b0ff..0efc1483f3ab 100644
--- a/fs/smb/client/cifs_fs_sb.h
+++ b/fs/smb/client/cifs_fs_sb.h
@@ -71,5 +71,6 @@ struct cifs_sb_info {
* Available once the mount has completed.
*/
struct dentry *root;
+ bool sb_dying:1; /* superblock is being destroyed, skip dcache ops */
};
#endif /* _CIFS_FS_SB_H */
diff --git a/fs/smb/client/cifsfs.c b/fs/smb/client/cifsfs.c
index deebe5d440de..43386cb329a0 100644
--- a/fs/smb/client/cifsfs.c
+++ b/fs/smb/client/cifsfs.c
@@ -71,6 +71,7 @@ bool disable_legacy_dialects; /* false by default */
bool enable_gcm_256 = true;
bool require_gcm_256; /* false by default */
bool enable_negotiate_signing; /* false by default */
+bool dcache_populate_async; /* false by default */
unsigned int global_secflags = CIFSSEC_DEF;
/* unsigned int ntlmv2_support = 0; */
@@ -235,6 +236,9 @@ MODULE_PARM_DESC(require_gcm_256, "Require strongest (256 bit) GCM encryption. D
module_param(enable_negotiate_signing, bool, 0644);
MODULE_PARM_DESC(enable_negotiate_signing, "Enable negotiating packet signing algorithm with server. Default: n/N/0");
+module_param(dcache_populate_async, bool, 0644);
+MODULE_PARM_DESC(dcache_populate_async, "Enable asynchronous dcache population during readdir to improve performance on large directories. Default: n/N/0");
+
module_param(disable_legacy_dialects, bool, 0644);
MODULE_PARM_DESC(disable_legacy_dialects, "To improve security it may be "
"helpful to restrict the ability to "
@@ -389,6 +393,11 @@ static void cifs_kill_sb(struct super_block *sb)
* and close all deferred file handles before we kill the sb.
*/
if (cifs_sb->root) {
+ /* Mark superblock as dying to skip expensive dcache ops in flight */
+ cifs_sb->sb_dying = true;
+ /* Wait for dcache work to complete and clean up */
+ flush_workqueue(cifs_dcache_wq);
+
close_all_cached_dirs(cifs_sb);
cifs_close_all_deferred_files_sb(cifs_sb);
@@ -2157,9 +2166,17 @@ init_cifs(void)
goto out_destroy_serverclose_wq;
}
+ /* WQ_UNBOUND allows dcache work to run on any CPU for parallelism */
+ cifs_dcache_wq = alloc_workqueue("cifs_dcache",
+ WQ_UNBOUND | WQ_FREEZABLE, 0);
+ if (!cifs_dcache_wq) {
+ rc = -ENOMEM;
+ goto out_destroy_cfid_put_wq;
+ }
+
rc = cifs_init_inodecache();
if (rc)
- goto out_destroy_cfid_put_wq;
+ goto out_destroy_dcache_wq;
rc = cifs_init_netfs();
if (rc)
@@ -2239,6 +2256,8 @@ init_cifs(void)
cifs_destroy_netfs();
out_destroy_inodecache:
cifs_destroy_inodecache();
+out_destroy_dcache_wq:
+ destroy_workqueue(cifs_dcache_wq);
out_destroy_cfid_put_wq:
destroy_workqueue(cfid_put_wq);
out_destroy_serverclose_wq:
@@ -2287,6 +2306,7 @@ exit_cifs(void)
destroy_workqueue(fileinfo_put_wq);
destroy_workqueue(serverclose_wq);
destroy_workqueue(cfid_put_wq);
+ destroy_workqueue(cifs_dcache_wq);
destroy_workqueue(cifsiod_wq);
cifs_proc_clean();
}
diff --git a/fs/smb/client/cifsglob.h b/fs/smb/client/cifsglob.h
index aa87421c75c0..8fca761aa69f 100644
--- a/fs/smb/client/cifsglob.h
+++ b/fs/smb/client/cifsglob.h
@@ -2142,6 +2142,7 @@ extern unsigned int sign_CIFS_PDUs; /* enable smb packet signing */
extern bool enable_gcm_256; /* allow optional negotiate of strongest signing (aes-gcm-256) */
extern bool require_gcm_256; /* require use of strongest signing (aes-gcm-256) */
extern bool enable_negotiate_signing; /* request use of faster (GMAC) signing if available */
+extern bool dcache_populate_async; /* enable async dcache population during readdir */
extern bool linuxExtEnabled;/*enable Linux/Unix CIFS extensions*/
extern unsigned int CIFSMaxBufSize; /* max size not including hdr */
extern unsigned int cifs_min_rcv; /* min size of big ntwrk buf pool */
@@ -2163,6 +2164,7 @@ extern struct workqueue_struct *cifsoplockd_wq;
extern struct workqueue_struct *deferredclose_wq;
extern struct workqueue_struct *serverclose_wq;
extern struct workqueue_struct *cfid_put_wq;
+extern struct workqueue_struct *cifs_dcache_wq;
extern __u32 cifs_lock_secret;
extern mempool_t *cifs_sm_req_poolp;
diff --git a/fs/smb/client/readdir.c b/fs/smb/client/readdir.c
index cbebb6f980e9..babe3bde55f2 100644
--- a/fs/smb/client/readdir.c
+++ b/fs/smb/client/readdir.c
@@ -13,6 +13,7 @@
#include <linux/pagemap.h>
#include <linux/slab.h>
#include <linux/stat.h>
+#include <linux/workqueue.h>
#include "cifsglob.h"
#include "cifsproto.h"
#include "cifs_unicode.h"
@@ -24,6 +25,19 @@
#include "cached_dir.h"
#include "reparse.h"
+/* Workqueue for async dcache population */
+struct workqueue_struct *cifs_dcache_wq;
+
+/* Work item for async dcache population */
+struct cifs_dcache_work {
+ struct work_struct work;
+ struct dentry *parent; /* dget() reference to parent dir */
+ char *name; /* kstrdup() copy of filename */
+ unsigned int namelen;
+ struct cifs_fattr fattr; /* Copy of attributes */
+ struct cached_fid *cfid; /* ref-counted cfid for cifs_complete_pending_dcache */
+};
+
/*
* To be safe - for UCS to UTF-8 with strings loaded with the rare long
* characters alloc more to account for such multibyte target UTF-8
@@ -171,6 +185,65 @@ cifs_prime_dcache(struct dentry *parent, struct qstr *name,
dput(dentry);
}
+/*
+ * Async dcache population work handler.
+ * Delegates to cifs_prime_dcache then signals completion to unblock waiters.
+ */
+static void cifs_dcache_work_handler(struct work_struct *work)
+{
+ struct cifs_dcache_work *dcache_work =
+ container_of(work, struct cifs_dcache_work, work);
+ struct qstr name = QSTR_INIT(dcache_work->name, dcache_work->namelen);
+ struct cifs_sb_info *cifs_sb = CIFS_SB(dcache_work->parent->d_sb);
+
+ cifs_dbg(FYI, "%s: async dcache for %s\n", __func__, name.name);
+
+ /* Skip expensive dcache operations if superblock is being torn down */
+ if (!cifs_sb->sb_dying) {
+ cifs_prime_dcache(dcache_work->parent, &name, &dcache_work->fattr);
+ cifs_complete_pending_dcache(dcache_work->cfid, dcache_work->name,
+ dcache_work->namelen);
+ }
+ close_cached_dir(dcache_work->cfid);
+ dput(dcache_work->parent);
+ kfree(dcache_work->name);
+ kfree(dcache_work);
+}
+
+/*
+ * Queue async dcache population work.
+ * Returns true if work was queued, false if sync fallback needed.
+ */
+static bool cifs_queue_dcache_work(struct dentry *parent, const char *name,
+ unsigned int namelen, struct cifs_fattr *fattr,
+ struct cached_fid *cfid)
+{
+ struct cifs_dcache_work *work;
+
+ if (!cfid)
+ return false;
+
+ work = kzalloc(sizeof(*work), GFP_KERNEL);
+ if (!work)
+ return false;
+
+ work->name = kstrndup(name, namelen, GFP_KERNEL);
+ if (!work->name) {
+ kfree(work);
+ return false;
+ }
+
+ work->parent = dget(parent);
+ work->namelen = namelen;
+ memcpy(&work->fattr, fattr, sizeof(work->fattr));
+ kref_get(&cfid->refcount);
+ work->cfid = cfid;
+
+ INIT_WORK(&work->work, cifs_dcache_work_handler);
+ queue_work(cifs_dcache_wq, &work->work);
+ return true;
+}
+
static void
cifs_fill_common_info(struct cifs_fattr *fattr, struct cifs_sb_info *cifs_sb)
{
@@ -923,8 +996,19 @@ static int cifs_filldir(char *find_entry, struct file *file,
*/
fattr.cf_flags |= CIFS_FATTR_NEED_REVAL;
- add_to_cached_dir(cfid, ctx, name.name, name.len, &fattr, file);
- cifs_prime_dcache(file_dentry(file), &name, &fattr);
+ /* queue async dcache population if enabled; fallback to sync if disabled or queueing fails */
+ bool cached = add_to_cached_dir(cfid, ctx, name.name, name.len, &fattr, file);
+
+ if (dcache_populate_async && cached &&
+ cifs_queue_dcache_work(file_dentry(file), name.name, name.len,
+ &fattr, cfid)) {
+ /* Async: handler will call cifs_prime_dcache + cifs_complete_pending_dcache */
+ cifs_dbg(FYI, "Queued async dcache population for %.*s\n", name.len, name.name);
+ } else {
+ cifs_prime_dcache(file_dentry(file), &name, &fattr);
+ if (cached)
+ cifs_complete_pending_dcache(cfid, name.name, name.len);
+ }
return !cifs_dir_emit(ctx, name.name, name.len, &fattr);
}
--
2.53.0
next prev parent reply other threads:[~2026-06-23 20:20 UTC|newest]
Thread overview: 16+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-06-23 20:13 [PATCH 01/16] cifs: define variable sized buffer for querydir responses Steve French
2026-06-23 20:13 ` [PATCH 02/16] cifs: optimize readdir for small directories Steve French
2026-06-23 20:13 ` [PATCH 03/16] cifs: optimize readdir for larger directories Steve French
2026-06-23 20:13 ` [PATCH 04/16] cifs: reorganize cached dir helpers Steve French
2026-06-23 20:13 ` [PATCH 05/16] cifs: make cfid locks more granular Steve French
2026-06-23 20:13 ` [PATCH 06/16] cifs: query dir should reuse cfid even if not fully cached Steve French
2026-06-23 20:13 ` [PATCH 07/16] cifs: back cached_dirents with page cache Steve French
2026-06-23 20:13 ` [PATCH 08/16] cifs: in place changes to cached_dirents when dir lease is held Steve French
2026-06-23 20:13 ` [PATCH 09/16] cifs: register a shrinker to manage cached_dirents Steve French
2026-06-23 20:13 ` [PATCH 10/16] cifs: option to disable time-based eviction of cache Steve French
2026-06-23 20:13 ` [PATCH 11/16] cifs: option to set unlimited number of cached dirs Steve French
2026-06-23 20:13 ` Steve French [this message]
2026-06-23 20:13 ` [PATCH 13/16] cifs: trace points for cached_dir operations Steve French
2026-06-23 20:13 ` [PATCH 14/16] cifs: discard functions to ensure that mid callbacks get called Steve French
2026-06-23 20:13 ` [PATCH 15/16] cifs: keep cfids in rbtree for efficient lookups Steve French
2026-06-23 20:13 ` [PATCH 16/16] cifs: invalidate cached_dirents if population aborted Steve French
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=20260623201344.2043841-12-stfrench@microsoft.com \
--to=smfrench@gmail.com \
--cc=linux-cifs@vger.kernel.org \
--cc=sprasad@microsoft.com \
/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