Commit f5b3108e authored by Christoph Hellwig's avatar Christoph Hellwig Committed by Trond Myklebust
Browse files

NFS: use a hash table for delegation lookup



nfs_delegation_find_inode currently has to walk the entire list of
delegations per inode, which can become pretty large, and can become even
larger when increasing the delegation watermark.

Add a hash table to speed up the delegation lookup, sized as a fraction
of the delegation watermark.

Signed-off-by: default avatarChristoph Hellwig <hch@lst.de>
Link: https://lore.kernel.org/r/20250718081509.2607553-6-hch@lst.de


Signed-off-by: default avatarTrond Myklebust <trond.myklebust@hammerspace.com>
parent 2fb4af5e
Loading
Loading
Loading
Loading
+27 −1
Original line number Diff line number Diff line
@@ -30,6 +30,13 @@
static unsigned nfs_delegation_watermark = NFS_DEFAULT_DELEGATION_WATERMARK;
module_param_named(delegation_watermark, nfs_delegation_watermark, uint, 0644);

static struct hlist_head *nfs_delegation_hash(struct nfs_server *server,
		const struct nfs_fh *fhandle)
{
	return server->delegation_hash_table +
		(nfs_fhandle_hash(fhandle) & server->delegation_hash_mask);
}

static void __nfs_free_delegation(struct nfs_delegation *delegation)
{
	put_cred(delegation->cred);
@@ -367,6 +374,7 @@ nfs_detach_delegation_locked(struct nfs_inode *nfsi,
		spin_unlock(&delegation->lock);
		return NULL;
	}
	hlist_del_init_rcu(&delegation->hash);
	list_del_rcu(&delegation->super_list);
	delegation->inode = NULL;
	rcu_assign_pointer(nfsi->delegation, NULL);
@@ -529,6 +537,8 @@ int nfs_inode_set_delegation(struct inode *inode, const struct cred *cred,
	spin_unlock(&inode->i_lock);

	list_add_tail_rcu(&delegation->super_list, &server->delegations);
	hlist_add_head_rcu(&delegation->hash,
			nfs_delegation_hash(server, &NFS_I(inode)->fh));
	rcu_assign_pointer(nfsi->delegation, delegation);
	delegation = NULL;

@@ -1166,11 +1176,12 @@ static struct inode *
nfs_delegation_find_inode_server(struct nfs_server *server,
				 const struct nfs_fh *fhandle)
{
	struct hlist_head *head = nfs_delegation_hash(server, fhandle);
	struct nfs_delegation *delegation;
	struct super_block *freeme = NULL;
	struct inode *res = NULL;

	list_for_each_entry_rcu(delegation, &server->delegations, super_list) {
	hlist_for_each_entry_rcu(delegation, head, hash) {
		spin_lock(&delegation->lock);
		if (delegation->inode != NULL &&
		    !test_bit(NFS_DELEGATION_REVOKED, &delegation->flags) &&
@@ -1577,3 +1588,18 @@ bool nfs4_delegation_flush_on_close(const struct inode *inode)
	rcu_read_unlock();
	return ret;
}

int nfs4_delegation_hash_alloc(struct nfs_server *server)
{
	int delegation_buckets, i;

	delegation_buckets = roundup_pow_of_two(nfs_delegation_watermark / 16);
	server->delegation_hash_mask = delegation_buckets - 1;
	server->delegation_hash_table = kmalloc_array(delegation_buckets,
			sizeof(*server->delegation_hash_table), GFP_KERNEL);
	if (!server->delegation_hash_table)
		return -ENOMEM;
	for (i = 0; i < delegation_buckets; i++)
		INIT_HLIST_HEAD(&server->delegation_hash_table[i]);
	return 0;
}
+3 −0
Original line number Diff line number Diff line
@@ -14,6 +14,7 @@
 * NFSv4 delegation
 */
struct nfs_delegation {
	struct hlist_node hash;
	struct list_head super_list;
	const struct cred *cred;
	struct inode *inode;
@@ -123,4 +124,6 @@ static inline int nfs_have_delegated_mtime(struct inode *inode)
						 NFS_DELEGATION_FLAG_TIME);
}

int nfs4_delegation_hash_alloc(struct nfs_server *server);

#endif
+5 −0
Original line number Diff line number Diff line
@@ -802,6 +802,7 @@ static void nfs4_destroy_server(struct nfs_server *server)
	unset_pnfs_layoutdriver(server);
	nfs4_purge_state_owners(server, &freeme);
	nfs4_free_state_owners(&freeme);
	kfree(server->delegation_hash_table);
}

/*
@@ -1096,6 +1097,10 @@ static int nfs4_server_common_setup(struct nfs_server *server,
{
	int error;

	error = nfs4_delegation_hash_alloc(server);
	if (error)
		return error;

	/* data servers support only a subset of NFSv4.1 */
	if (is_ds_only_client(server->nfs_client))
		return -EPROTONOSUPPORT;
+21 −1
Original line number Diff line number Diff line
@@ -10967,6 +10967,26 @@ static const struct inode_operations nfs4_file_inode_operations = {
	.listxattr	= nfs4_listxattr,
};

static struct nfs_server *nfs4_clone_server(struct nfs_server *source,
		struct nfs_fh *fh, struct nfs_fattr *fattr,
		rpc_authflavor_t flavor)
{
	struct nfs_server *server;
	int error;

	server = nfs_clone_server(source, fh, fattr, flavor);
	if (IS_ERR(server))
		return server;

	error = nfs4_delegation_hash_alloc(server);
	if (error) {
		nfs_free_server(server);
		return ERR_PTR(error);
	}

	return server;
}

const struct nfs_rpc_ops nfs_v4_clientops = {
	.version	= 4,			/* protocol version */
	.dentry_ops	= &nfs4_dentry_operations,
@@ -11019,7 +11039,7 @@ const struct nfs_rpc_ops nfs_v4_clientops = {
	.init_client	= nfs4_init_client,
	.free_client	= nfs4_free_client,
	.create_server	= nfs4_create_server,
	.clone_server	= nfs_clone_server,
	.clone_server	= nfs4_clone_server,
	.discover_trunking = nfs4_discover_trunking,
	.enable_swap	= nfs4_enable_swap,
	.disable_swap	= nfs4_disable_swap,
+2 −0
Original line number Diff line number Diff line
@@ -255,6 +255,8 @@ struct nfs_server {
	struct list_head	layouts;
	struct list_head	delegations;
	atomic_long_t		nr_active_delegations;
	unsigned int		delegation_hash_mask;
	struct hlist_head	*delegation_hash_table;
	struct list_head	ss_copies;
	struct list_head	ss_src_copies;