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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.