Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3bc303c2 authored by Jeff Layton's avatar Jeff Layton Committed by Steve French
Browse files

cifs: convert oplock breaks to use slow_work facility (try #4)



This is the fourth respin of the patch to convert oplock breaks to
use the slow_work facility.

A customer of ours was testing a backport of one of the earlier
patchsets, and hit a "Busy inodes after umount..." problem. An oplock
break job had raced with a umount, and the superblock got torn down and
its memory reused. When the oplock break job tried to dereference the
inode->i_sb, the kernel oopsed.

This patchset has the oplock break job hold an inode and vfsmount
reference until the oplock break completes.  With this, there should be
no need to take a tcon reference (the vfsmount implicitly holds one
already).

Currently, when an oplock break comes in there's a chance that the
oplock break job won't occur if the allocation of the oplock_q_entry
fails. There are also some rather nasty races in the allocation and
handling these structs.

Rather than allocating oplock queue entries when an oplock break comes
in, add a few extra fields to the cifsFileInfo struct. Get rid of the
dedicated cifs_oplock_thread as well and queue the oplock break job to
the slow_work thread pool.

This approach also has the advantage that the oplock break jobs can
potentially run in parallel rather than be serialized like they are
today.

Signed-off-by: default avatarJeff Layton <jlayton@redhat.com>
Signed-off-by: default avatarSteve French <sfrench@us.ibm.com>
parent 48541bd3
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -2,6 +2,7 @@ config CIFS
	tristate "CIFS support (advanced network filesystem, SMBFS successor)"
	depends on INET
	select NLS
	select SLOW_WORK
	help
	  This is the client VFS module for the Common Internet File System
	  (CIFS) protocol which is the successor to the Server Message Block
+5 −90
Original line number Diff line number Diff line
@@ -64,9 +64,6 @@ unsigned int multiuser_mount = 0;
unsigned int extended_security = CIFSSEC_DEF;
/* unsigned int ntlmv2_support = 0; */
unsigned int sign_CIFS_PDUs = 1;
extern struct task_struct *oplockThread; /* remove sparse warning */
struct task_struct *oplockThread = NULL;
/* extern struct task_struct * dnotifyThread; remove sparse warning */
static const struct super_operations cifs_super_ops;
unsigned int CIFSMaxBufSize = CIFS_MAX_MSGSIZE;
module_param(CIFSMaxBufSize, int, 0);
@@ -973,89 +970,12 @@ cifs_destroy_mids(void)
	kmem_cache_destroy(cifs_oplock_cachep);
}

static int cifs_oplock_thread(void *dummyarg)
{
	struct oplock_q_entry *oplock_item;
	struct cifsTconInfo *pTcon;
	struct inode *inode;
	__u16  netfid;
	int rc, waitrc = 0;

	set_freezable();
	do {
		if (try_to_freeze())
			continue;

		spin_lock(&cifs_oplock_lock);
		if (list_empty(&cifs_oplock_list)) {
			spin_unlock(&cifs_oplock_lock);
			set_current_state(TASK_INTERRUPTIBLE);
			schedule_timeout(39*HZ);
		} else {
			oplock_item = list_entry(cifs_oplock_list.next,
						struct oplock_q_entry, qhead);
			cFYI(1, ("found oplock item to write out"));
			pTcon = oplock_item->tcon;
			inode = oplock_item->pinode;
			netfid = oplock_item->netfid;
			spin_unlock(&cifs_oplock_lock);
			DeleteOplockQEntry(oplock_item);
			/* can not grab inode sem here since it would
				deadlock when oplock received on delete
				since vfs_unlink holds the i_mutex across
				the call */
			/* mutex_lock(&inode->i_mutex);*/
			if (S_ISREG(inode->i_mode)) {
#ifdef CONFIG_CIFS_EXPERIMENTAL
				if (CIFS_I(inode)->clientCanCacheAll == 0)
					break_lease(inode, FMODE_READ);
				else if (CIFS_I(inode)->clientCanCacheRead == 0)
					break_lease(inode, FMODE_WRITE);
#endif
				rc = filemap_fdatawrite(inode->i_mapping);
				if (CIFS_I(inode)->clientCanCacheRead == 0) {
					waitrc = filemap_fdatawait(
							      inode->i_mapping);
					invalidate_remote_inode(inode);
				}
				if (rc == 0)
					rc = waitrc;
			} else
				rc = 0;
			/* mutex_unlock(&inode->i_mutex);*/
			if (rc)
				CIFS_I(inode)->write_behind_rc = rc;
			cFYI(1, ("Oplock flush inode %p rc %d",
				inode, rc));

				/* releasing stale oplock after recent reconnect
				of smb session using a now incorrect file
				handle is not a data integrity issue but do
				not bother sending an oplock release if session
				to server still is disconnected since oplock
				already released by the server in that case */
			if (!pTcon->need_reconnect) {
				rc = CIFSSMBLock(0, pTcon, netfid,
						0 /* len */ , 0 /* offset */, 0,
						0, LOCKING_ANDX_OPLOCK_RELEASE,
						false /* wait flag */);
				cFYI(1, ("Oplock release rc = %d", rc));
			}
			set_current_state(TASK_INTERRUPTIBLE);
			schedule_timeout(1);  /* yield in case q were corrupt */
		}
	} while (!kthread_should_stop());

	return 0;
}

static int __init
init_cifs(void)
{
	int rc = 0;
	cifs_proc_init();
	INIT_LIST_HEAD(&cifs_tcp_ses_list);
	INIT_LIST_HEAD(&cifs_oplock_list);
#ifdef CONFIG_CIFS_EXPERIMENTAL
	INIT_LIST_HEAD(&GlobalDnotifyReqList);
	INIT_LIST_HEAD(&GlobalDnotifyRsp_Q);
@@ -1084,7 +1004,6 @@ init_cifs(void)
	rwlock_init(&GlobalSMBSeslock);
	rwlock_init(&cifs_tcp_ses_lock);
	spin_lock_init(&GlobalMid_Lock);
	spin_lock_init(&cifs_oplock_lock);

	if (cifs_max_pending < 2) {
		cifs_max_pending = 2;
@@ -1119,18 +1038,15 @@ init_cifs(void)
	if (rc)
		goto out_unregister_key_type;
#endif
	oplockThread = kthread_run(cifs_oplock_thread, NULL, "cifsoplockd");
	if (IS_ERR(oplockThread)) {
		rc = PTR_ERR(oplockThread);
		cERROR(1, ("error %d create oplock thread", rc));
		goto out_unregister_dfs_key_type;
	}
	rc = slow_work_register_user();
	if (rc)
		goto out_unregister_resolver_key;

	return 0;

 out_unregister_dfs_key_type:
#ifdef CONFIG_CIFS_DFS_UPCALL
 out_unregister_resolver_key:
	unregister_key_type(&key_type_dns_resolver);
#ifdef CONFIG_CIFS_DFS_UPCALL
 out_unregister_key_type:
#endif
#ifdef CONFIG_CIFS_UPCALL
@@ -1165,7 +1081,6 @@ exit_cifs(void)
	cifs_destroy_inodecache();
	cifs_destroy_mids();
	cifs_destroy_request_bufs();
	kthread_stop(oplockThread);
}

MODULE_AUTHOR("Steve French <sfrench@us.ibm.com>");
+5 −7
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@
 */
#include <linux/in.h>
#include <linux/in6.h>
#include <linux/slow-work.h>
#include "cifs_fs_sb.h"
#include "cifsacl.h"
/*
@@ -346,14 +347,16 @@ struct cifsFileInfo {
	/* lock scope id (0 if none) */
	struct file *pfile; /* needed for writepage */
	struct inode *pInode; /* needed for oplock break */
	struct vfsmount *mnt;
	struct mutex lock_mutex;
	struct list_head llist; /* list of byte range locks we have. */
	bool closePend:1;	/* file is marked to close */
	bool invalidHandle:1;	/* file closed via session abend */
	bool messageMode:1;	/* for pipes: message vs byte mode */
	bool oplock_break_cancelled:1;
	atomic_t count;		/* reference count */
	struct mutex fh_mutex; /* prevents reopen race after dead ses*/
	struct cifs_search_info srch_inf;
	struct slow_work oplock_break; /* slow_work job for oplock breaks */
};

/* Take a reference on the file private data */
@@ -670,12 +673,6 @@ GLOBAL_EXTERN rwlock_t cifs_tcp_ses_lock;
 */
GLOBAL_EXTERN rwlock_t GlobalSMBSeslock;

/* Global list of oplocks */
GLOBAL_EXTERN struct list_head cifs_oplock_list;

/* Protects the cifs_oplock_list */
GLOBAL_EXTERN spinlock_t cifs_oplock_lock;

/* Outstanding dir notify requests */
GLOBAL_EXTERN struct list_head GlobalDnotifyReqList;
/* DirNotify response queue */
@@ -726,3 +723,4 @@ GLOBAL_EXTERN unsigned int cifs_min_rcv; /* min size of big ntwrk buf pool */
GLOBAL_EXTERN unsigned int cifs_min_small;  /* min size of small buf pool */
GLOBAL_EXTERN unsigned int cifs_max_pending; /* MAX requests at once to server*/

extern const struct slow_work_ops cifs_oplock_break_ops;
+1 −5
Original line number Diff line number Diff line
@@ -86,17 +86,13 @@ extern int CIFS_SessSetup(unsigned int xid, struct cifsSesInfo *ses,
			     const int stage,
			     const struct nls_table *nls_cp);
extern __u16 GetNextMid(struct TCP_Server_Info *server);
extern struct oplock_q_entry *AllocOplockQEntry(struct inode *, u16,
						 struct cifsTconInfo *);
extern void DeleteOplockQEntry(struct oplock_q_entry *);
extern void DeleteTconOplockQEntries(struct cifsTconInfo *);
extern struct timespec cifs_NTtimeToUnix(__le64 utc_nanoseconds_since_1601);
extern u64 cifs_UnixTimeToNT(struct timespec);
extern struct timespec cnvrtDosUnixTm(__le16 le_date, __le16 le_time,
				      int offset);

extern int cifs_posix_open(char *full_path, struct inode **pinode,
			   struct super_block *sb, int mode, int oflags,
			   struct vfsmount *mnt, int mode, int oflags,
			   __u32 *poplock, __u16 *pnetfid, int xid);
extern void cifs_unix_basic_to_fattr(struct cifs_fattr *fattr,
				     FILE_UNIX_BASIC_INFO *info,
+1 −0
Original line number Diff line number Diff line
@@ -94,6 +94,7 @@ static void mark_open_files_invalid(struct cifsTconInfo *pTcon)
	list_for_each_safe(tmp, tmp1, &pTcon->openFileList) {
		open_file = list_entry(tmp, struct cifsFileInfo, tlist);
		open_file->invalidHandle = true;
		open_file->oplock_break_cancelled = true;
	}
	write_unlock(&GlobalSMBSeslock);
	/* BB Add call to invalidate_inodes(sb) for all superblocks mounted
Loading