Skipped unlocks
Subsystems (5)
PATCH v5 2/6 NFS: Move the flock open mode check into nfs_flock()
In reply to Benjamin CoddingtonWe only need to check lock exclusive/shared types against open mode when flock() is used on NFS, so move it into the flock-specific path instead of checking it for all locks.
--- fs/nfs/file.c | 18 ++++++++++++++++-- fs/nfs/nfs4proc.c | 14 -------------- 2 files changed, 16 insertions(+), 16 deletions(-)
fs/nfs/file.c +16 -2
@@ -820,9 +820,23 @@ int nfs_flock(struct file *filp, int cmd, struct file_lock *fl)if (NFS_SERVER (inode)->flags &NFS_MOUNT_LOCAL_FLOCK ) is_local =1 ; -/* We're simulating flock() locks using posix locks on the server */ -if (fl->fl_type ==F_UNLCK ) +/* +* VFS doesn't require the open mode to match a flock() lock's type. +* NFS, however, may simulate flock() locking with posix locking which +* requires the open mode to match the lock type. +*/ +switch (fl->fl_type) { +case F_UNLCK :return do_unlk (filp, cmd, fl, is_local); +case F_RDLCK : +if (!(filp->f_mode &FMODE_READ )) +return -EBADF ; +break ; +case F_WRLCK : +if (!(filp->f_mode &FMODE_WRITE )) +return -EBADF ; + } +return do_setlk (filp, cmd, fl, is_local); }EXPORT_SYMBOL_GPL (nfs_flock);
fs/nfs/nfs4proc.c +0 -14
@@ -6592,20 +6592,6 @@ nfs4_proc_lock(struct file *filp, int cmd, struct file_lock *request) !test_bit(NFS_STATE_POSIX_LOCKS, &state->flags))return -ENOLCK ; -/* -* Don't rely on the VFS having checked the file open mode, -* since it won't do this for flock() locks. -*/ -switch (request->fl_type) { -case F_RDLCK : -if (!(filp->f_mode &FMODE_READ )) -return -EBADF ; -break ; -case F_WRLCK : -if (!(filp->f_mode &FMODE_WRITE )) -return -EBADF ; - } - status =nfs4_set_lock_state (state, request);if (status !=0 )return status;
Signature · 1 line
2.9.3
v5 cover Skipped unlocks
Well over a year ago I made other attempts to fix the problem of NFS failing to send an unlock when signalled. Those attempts were terrible.
Here's yet another version, this time using the approach suggested by Jeff Layton to utilize an RPC waitqueue to defer unlock tasks until IO has completed. In order to do this for NLM without violating all the layers, a new nlmclnt_operations struct is defined and passed in to allow the NLM client to call back out to NFS at various stages.
Comments and review are welcomed.
since v1:
- add Christoph's reviewed-by on 1/4 and 2/4 and fixup switch indentation on 2/4
since v2:
- don't sleep in rpciod to wait for I/O completion, just send the unlock immediately for both v3 and v4.
since v3:
- don't just skip the wait for FL_CLOSE. Instead, build new machinery into NLM to allow waiting in rpc_call_prepare().
since v4:
- move the uoc_rpcwaitq outside #if IS_ENABLED(CONFIG_NFS_V4), since we need it for nfsv3. Add some explanation for the fuse change in 3/6.
Benjamin Coddington (6):
NFS4: remove a redundant lock range check
NFS: Move the flock open mode check into nfs_flock()
locks: Set FL_CLOSE when removing flock locks on close()
NFS: Add an iocounter wait function for async RPC tasks
lockd: Introduce nlmclnt_operations
NFS: Always wait for I/O completion before unlock
fs/fuse/file.c | 2 +- fs/lockd/clntproc.c | 26 +++++++++++++++++++++++++- fs/locks.c | 2 +- fs/nfs/client.c | 1 + fs/nfs/file.c | 28 +++++++++++++++++++++------- fs/nfs/nfs3proc.c | 38 +++++++++++++++++++++++++++++++++++++- fs/nfs/nfs4proc.c | 27 +++++++-------------------- fs/nfs/pagelist.c | 26 +++++++++++++++++++++++++- fs/nfs/proc.c | 2 +- include/linux/fs.h | 2 ++ include/linux/lockd/bind.h | 11 +++++++++-- include/linux/lockd/lockd.h | 2 ++ include/linux/nfs_fs.h | 1 + include/linux/nfs_fs_sb.h | 1 + include/linux/nfs_page.h | 1 + 15 files changed, 135 insertions(+), 35 deletions(-)
Signature · 1 line
2.9.3
PATCH v5 1/6 NFS4: remove a redundant lock range check
flock64_to_posix_lock() is already doing this check
--- fs/nfs/nfs4proc.c | 3 --- 1 file changed, 3 deletions(-)
fs/nfs/nfs4proc.c +0 -3
@@ -6570,9 +6570,6 @@ nfs4_proc_lock(struct file *filp, int cmd, struct file_lock *request) ctx =nfs_file_open_context (filp); state = ctx->state; -if (request->fl_start <0 || request->fl_end <0 ) -return -EINVAL ; -if (IS_GETLK (cmd)) {if (state !=NULL )return nfs4_proc_getlk (state,F_GETLK , request);
Signature · 1 line
2.9.3
PATCH v5 3/6 locks: Set FL_CLOSE when removing flock locks on close()
In reply to Benjamin CoddingtonSet FL_CLOSE in fl_flags as in locks_remove_posix() when clearing locks. NFS will check for this flag to ensure an unlock is sent in a following patch.
Fuse handles flock and posix locks differently for FL_CLOSE, and so requires a fixup to retain the existing behavior for flock.
--- fs/fuse/file.c | 2 +- fs/locks.c | 2 +- include/linux/fs.h | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-)
fs/fuse/file.c +1 -1
@@ -2172,7 +2172,7 @@ static int fuse_setlk(struct file *file, struct file_lock *fl, int flock) }/* Unlock on close is handled by the flush method */ -if (fl->fl_flags &FL_CLOSE ) +if (fl->fl_flags &FL_CLOSE_POSIX ==FL_CLOSE_POSIX )return 0 ;fuse_lk_fill (&args, file, fl, opcode, pid, flock, &inarg);
fs/locks.c +1 -1
@@ -2504,7 +2504,7 @@ locks_remove_flock(struct file *filp, struct file_lock_context *flctx) .fl_owner = filp, .fl_pid = current->tgid, .fl_file = filp, - .fl_flags = FL_FLOCK, + .fl_flags = FL_FLOCK | FL_CLOSE, .fl_type = F_UNLCK, .fl_end = OFFSET_MAX, };
include/linux/fs.h +2 -0
@@ -903,6 +903,8 @@ static inline struct file *get_file(struct file *f)#define FL_OFDLCK 1024/* lock is "owned" by struct file */ #define FL_LAYOUT 2048/* outstanding pNFS layout */ +#define FL_CLOSE_POSIX (FL_POSIX | FL_CLOSE) +/* * Special return value from posix_lock_file() and vfs_lock_file() for * asynchronous locking.
Signature · 1 line
2.9.3
PATCH v5 4/6 NFS: Add an iocounter wait function for async RPC tasks
In reply to Benjamin CoddingtonBy sleeping on a new NFS Unlock-On-Close waitqueue, rpc tasks may wait for a lock context's iocounter to reach zero. The rpc waitqueue is only woken when the open_context has the NFS_CONTEXT_UNLOCK flag set in order to mitigate spurious wake-ups for any iocounter reaching zero.
--- fs/nfs/client.c | 1 + fs/nfs/pagelist.c | 26 +++++++++++++++++++++++++- include/linux/nfs_fs.h | 1 + include/linux/nfs_fs_sb.h | 1 + include/linux/nfs_page.h | 1 + 5 files changed, 29 insertions(+), 1 deletion(-)
fs/nfs/client.c +1 -0
@@ -218,6 +218,7 @@ static void nfs_cb_idr_remove_locked(struct nfs_client *clp)static void pnfs_init_server (struct nfs_server *server) {rpc_init_wait_queue (&server->roc_rpcwaitq,"pNFS ROC" ); +rpc_init_wait_queue (&server->uoc_rpcwaitq,"NFS UOC" ); }#else
fs/nfs/pagelist.c +25 -1
@@ -115,6 +115,27 @@ nfs_iocounter_wait(struct nfs_lock_context *l_ctx) TASK_KILLABLE); } +bool +nfs_async_iocounter_wait (struct rpc_task *task,void *data) +{ +struct nfs_lock_context *l_ctx = data; +struct inode *inode =d_inode (l_ctx->open_context->dentry); +bool ret = false; + +if (atomic_read (&l_ctx->io_count) >0 ) { +rpc_sleep_on (&NFS_SERVER (inode)->uoc_rpcwaitq, task,NULL ); + ret = true; + } + +if (atomic_read (&l_ctx->io_count) ==0 ) { +rpc_wake_up_queued_task (&NFS_SERVER (inode)->uoc_rpcwaitq, task); + ret = false; + } + +return ret; +} +EXPORT_SYMBOL_GPL (nfs_async_iocounter_wait); +/* * nfs_page_group_lock - lock the head of the page group * @req - request in group that is to be locked
@@ -398,8 +419,11 @@ static void nfs_clear_request(struct nfs_page *req) req->wb_page =NULL ; }if (l_ctx !=NULL ) { -if (atomic_dec_and_test (&l_ctx->io_count)) +if (atomic_dec_and_test (&l_ctx->io_count)) {wake_up_atomic_t (&l_ctx->io_count); +if (test_bit (NFS_CONTEXT_UNLOCK , &ctx->flags)) +rpc_wake_up (&NFS_SERVER (d_inode (ctx->dentry))->uoc_rpcwaitq); + }nfs_put_lock_context (l_ctx); req->wb_lock_context =NULL ; }
include/linux/nfs_fs.h +1 -0
@@ -76,6 +76,7 @@ struct nfs_open_context {#define NFS_CONTEXT_ERROR_WRITE (0)#define NFS_CONTEXT_RESEND_WRITES (1)#define NFS_CONTEXT_BAD (2) +#define NFS_CONTEXT_UNLOCK (3)int error;struct list_head list;
include/linux/nfs_fs_sb.h +1 -0
@@ -222,6 +222,7 @@ struct nfs_server {u32 mountd_version;unsigned short mountd_port;unsigned short mountd_protocol; +struct rpc_wait_queue uoc_rpcwaitq; };/* Server capabilities */
include/linux/nfs_page.h +1 -0
@@ -141,6 +141,7 @@ extern int nfs_page_group_lock(struct nfs_page *, bool);extern void nfs_page_group_lock_wait (struct nfs_page *);extern void nfs_page_group_unlock (struct nfs_page *);extern bool nfs_page_group_sync_on_bit (struct nfs_page *,unsigned int ); +extern bool nfs_async_iocounter_wait (struct rpc_task *,void *);/* * Lock the page of an asynchronous request
Signature · 1 line
2.9.3
PATCH v5 5/6 lockd: Introduce nlmclnt_operations
In reply to Benjamin CoddingtonNFS would enjoy the ability to modify the behavior of the NLM client's unlock RPC task in order to delay the transmission of the unlock until IO that was submitted under that lock has completed. This ability can ensure that the NLM client will always complete the transmission of an unlock even if the waiting caller has been interrupted with fatal signal.
A struct nlmclnt_operations and callback data pointer can be passed to nlmclnt_proc(). The struct nlmclnt_operations defines three callback operations that will be used in a following patch:
nlmclnt_alloc_call - used to call back after a successful allocation of
a struct nlm_rqst in nlmclnt_proc().
nlmclnt_unlock_prepare - used to call back during NLM unlock's
rpc_call_prepare. The NLM client defers calling rpc_call_start() until this callback returns false.
nlmclnt_release_call - used to call back when the NLM client's struct
nlm_rqst is freed.
--- fs/lockd/clntproc.c | 26 +++++++++++++++++++++++++- fs/nfs/nfs3proc.c | 2 +- fs/nfs/proc.c | 2 +- include/linux/lockd/bind.h | 11 +++++++++-- include/linux/lockd/lockd.h | 2 ++ 5 files changed, 38 insertions(+), 5 deletions(-)
fs/lockd/clntproc.c +25 -1
@@ -150,9 +150,12 @@ static void nlmclnt_release_lockargs(struct nlm_rqst *req)* @host: address of a valid nlm_host context representing the NLM server * @cmd: fcntl-style file lock operation to perform * @fl: address of arguments for the lock operation +* @nlmclnt_ops: operations to call back out of nlm +* @data: address of data to be sent along with callback * */ -int nlmclnt_proc (struct nlm_host *host,int cmd,struct file_lock *fl) +int nlmclnt_proc (struct nlm_host *host,int cmd,struct file_lock *fl, +const struct nlmclnt_operations *nlmclnt_ops,void *data) {struct nlm_rqst *call;int status;
@@ -161,6 +164,9 @@ int nlmclnt_proc(struct nlm_host *host, int cmd, struct file_lock *fl)if (call ==NULL )return -ENOMEM ; +if (nlmclnt_ops && nlmclnt_ops->nlmclnt_alloc_call) + nlmclnt_ops->nlmclnt_alloc_call (data); +nlmclnt_locks_init_private (fl, host);if (!fl->fl_u.nfs_fl.owner) {/* lockowner allocation has failed */
@@ -169,6 +175,8 @@ int nlmclnt_proc(struct nlm_host *host, int cmd, struct file_lock *fl) }/* Set up the argument struct */ nlmclnt_setlockargs (call, fl); + call->nlmclnt_ops = nlmclnt_ops; + call->callback_data = data;if (IS_SETLK (cmd) ||IS_SETLKW (cmd)) {if (fl->fl_type !=F_UNLCK ) {
@@ -216,6 +224,8 @@ void nlmclnt_release_call(struct nlm_rqst *call) {if (!atomic_dec_and_test (&call->a_count))return ; +if (call->nlmclnt_ops && call->nlmclnt_ops->nlmclnt_release_call) + call->nlmclnt_ops->nlmclnt_release_call (call->callback_data);nlmclnt_release_host (call->a_host);nlmclnt_release_lockargs (call);kfree (call);
@@ -687,6 +697,19 @@ nlmclnt_unlock(struct nlm_rqst *req, struct file_lock *fl)return status; } +static void nlmclnt_unlock_prepare (struct rpc_task *task,void *data) +{ +struct nlm_rqst *req = data; +const struct nlmclnt_operations *nlmclnt_ops = req->nlmclnt_ops; +bool defer_call = false; + +if (nlmclnt_ops && nlmclnt_ops->nlmclnt_unlock_prepare) + defer_call = nlmclnt_ops->nlmclnt_unlock_prepare (task, req->callback_data); + +if (!defer_call) +rpc_call_start (task); +} +static void nlmclnt_unlock_callback (struct rpc_task *task,void *data) {struct nlm_rqst *req = data;
@@ -720,6 +743,7 @@ static void nlmclnt_unlock_callback(struct rpc_task *task, void *data) }static const struct rpc_call_ops nlmclnt_unlock_ops = { + .rpc_call_prepare = nlmclnt_unlock_prepare, .rpc_call_done = nlmclnt_unlock_callback, .rpc_release = nlmclnt_rpc_release, };
fs/nfs/nfs3proc.c +1 -1
@@ -870,7 +870,7 @@ nfs3_proc_lock(struct file *filp, int cmd, struct file_lock *fl) {struct inode *inode =file_inode (filp); -return nlmclnt_proc (NFS_SERVER (inode)->nlm_host, cmd, fl); +return nlmclnt_proc (NFS_SERVER (inode)->nlm_host, cmd, fl,NULL ,NULL ); }static int nfs3_have_delegation (struct inode *inode,fmode_t flags)
fs/nfs/proc.c +1 -1
@@ -638,7 +638,7 @@ nfs_proc_lock(struct file *filp, int cmd, struct file_lock *fl) {struct inode *inode =file_inode (filp); -return nlmclnt_proc (NFS_SERVER (inode)->nlm_host, cmd, fl); +return nlmclnt_proc (NFS_SERVER (inode)->nlm_host, cmd, fl,NULL ,NULL ); }/* Helper functions for NFS lock bounds checking */
include/linux/lockd/bind.h +9 -2
@@ -18,6 +18,7 @@/* Dummy declarations */ struct svc_rqst ; +struct rpc_task ;/* * This is the set of functions for lockd->nfsd communication
@@ -52,8 +53,14 @@ struct nlmclnt_initdata {extern struct nlm_host *nlmclnt_init (const struct nlmclnt_initdata *nlm_init);extern void nlmclnt_done (struct nlm_host *host); -extern int nlmclnt_proc (struct nlm_host *host,int cmd, -struct file_lock *fl); +struct nlmclnt_operations { +void (*nlmclnt_alloc_call)(void *); +bool (*nlmclnt_unlock_prepare)(struct rpc_task *,void *); +void (*nlmclnt_release_call)(void *); +}; + +extern int nlmclnt_proc (struct nlm_host *host,int cmd,struct file_lock *fl, +const struct nlmclnt_operations *nlmclnt_ops,void *data);extern int lockd_up (struct net *net);extern void lockd_down (struct net *net);
include/linux/lockd/lockd.h +2 -0
@@ -142,6 +142,8 @@ struct nlm_rqst {struct nlm_block * a_block;unsigned int a_retries;/* Retry count */ u8 a_owner[NLMCLNT_OHSIZE ]; +const struct nlmclnt_operations * nlmclnt_ops; +void * callback_data; };/*
Signature · 1 line
2.9.3
PATCH v5 6/6 NFS: Always wait for I/O completion before unlock
In reply to Benjamin CoddingtonNFS attempts to wait for read and write completion before unlocking in order to ensure that the data returned was protected by the lock. When this waiting is interrupted by a signal, the unlock may be skipped, and messages similar to the following are seen in the kernel ring buffer:
[20.167876] Leaked locks on dev=0x0:0x2b ino=0x8dd4c3: [20.168286] POSIX: fl_owner=ffff880078b06940 fl_flags=0x1 fl_type=0x0 fl_pid=20183 [20.168727] POSIX: fl_owner=ffff880078b06680 fl_flags=0x1 fl_type=0x0 fl_pid=20185
For NFSv3, the missing unlock will cause the server to refuse conflicting locks indefinitely. For NFSv4, the leftover lock will be removed by the server after the lease timeout.
This patch fixes this issue by skipping the usual wait in nfs_iocounter_wait if the FL_CLOSE flag is set when signaled. Instead, the wait happens in the unlock RPC task on the NFS UOC rpc_waitqueue.
For NFSv3, use lockd's new nlmclnt_operations along with nfs_async_iocounter_wait to defer NLM's unlock task until the lock context's iocounter reaches zero.
For NFSv4, call nfs_async_iocounter_wait() directly from unlock's current rpc_call_prepare.
--- fs/nfs/file.c | 10 +++++----- fs/nfs/nfs3proc.c | 38 +++++++++++++++++++++++++++++++++++++- fs/nfs/nfs4proc.c | 10 +++++++--- 3 files changed, 49 insertions(+), 9 deletions(-)
fs/nfs/file.c +5 -5
@@ -697,14 +697,14 @@ do_unlk(struct file *filp, int cmd, struct file_lock *fl, int is_local)if (!IS_ERR (l_ctx)) { status =nfs_iocounter_wait (l_ctx);nfs_put_lock_context (l_ctx); -if (status <0 ) +/* NOTE: special case +* If we're signalled while cleaning up locks on process exit, we +* still need to complete the unlock. +*/ +if (status <0 && !(fl->fl_flags &FL_CLOSE ))return status; } -/* NOTE: special case -* If we're signalled while cleaning up locks on process exit, we -* still need to complete the unlock. -*/ /* * Use local locking if mounted with "-onolock" or with appropriate * "-olocal_lock="
fs/nfs/nfs3proc.c +37 -1
@@ -865,12 +865,48 @@ static void nfs3_proc_commit_setup(struct nfs_commit_data *data, struct rpc_mess msg->rpc_proc = &nfs3_procedures[NFS3PROC_COMMIT ]; } +void nfs3_nlm_alloc_call (void *data) +{ +struct nfs_lock_context *l_ctx = data; +nfs_get_lock_context (l_ctx->open_context); +} + +void nfs3_nlm_release_call (void *data) +{ +struct nfs_lock_context *l_ctx = data; +nfs_put_lock_context (l_ctx); +} + +const struct nlmclnt_operations nlmclnt_fl_close_lock_ops = { + .nlmclnt_alloc_call = nfs3_nlm_alloc_call, + .nlmclnt_unlock_prepare = nfs_async_iocounter_wait, + .nlmclnt_release_call = nfs3_nlm_release_call, +}; +static int nfs3_proc_lock (struct file *filp,int cmd,struct file_lock *fl) {struct inode *inode =file_inode (filp); +struct nfs_lock_context *l_ctx =NULL ; +const struct nlmclnt_operations *nlmclnt_ops =NULL ; +int status; -return nlmclnt_proc (NFS_SERVER (inode)->nlm_host, cmd, fl,NULL ,NULL ); +if (fl->fl_flags &FL_CLOSE ) { + l_ctx =nfs_get_lock_context (nfs_file_open_context (filp)); +if (IS_ERR (l_ctx)) { + l_ctx =NULL ; + }else { +set_bit (NFS_CONTEXT_UNLOCK , &l_ctx->open_context->flags); + nlmclnt_ops = &nlmclnt_fl_close_lock_ops; + } + } + + status =nlmclnt_proc (NFS_SERVER (inode)->nlm_host, cmd, fl, + nlmclnt_ops, l_ctx); +if (l_ctx) +nfs_put_lock_context (l_ctx); + +return status; }static int nfs3_have_delegation (struct inode *inode,fmode_t flags)
fs/nfs/nfs4proc.c +7 -3
@@ -5905,7 +5905,7 @@ struct nfs4_unlockdata {struct nfs_locku_args arg;struct nfs_locku_res res;struct nfs4_lock_state *lsp; -struct nfs_open_context *ctx; +struct nfs_lock_context *l_ctx;struct file_lock fl;struct nfs_server *server;unsigned long timestamp;
@@ -5929,7 +5929,7 @@ static struct nfs4_unlockdata *nfs4_alloc_unlockdata(struct file_lock *fl, p->lsp = lsp;atomic_inc (&lsp->ls_count);/* Ensure we don't close file until we're done freeing locks! */ - p->ctx =get_nfs_open_context (ctx); + p->l_ctx =nfs_get_lock_context (ctx);memcpy (&p->fl, fl,sizeof (p->fl)); p->server =NFS_SERVER (inode);return p;
@@ -5940,7 +5940,7 @@ static void nfs4_locku_release_calldata(void *data)struct nfs4_unlockdata *calldata = data;nfs_free_seqid (calldata->arg.seqid);nfs4_put_lock_state (calldata->lsp); -put_nfs_open_context (calldata->ctx); +nfs_put_lock_context (calldata->l_ctx);kfree (calldata); }
@@ -5981,6 +5981,10 @@ static void nfs4_locku_prepare(struct rpc_task *task, void *data) {struct nfs4_unlockdata *calldata = data; +if (calldata->fl.fl_flags &FL_CLOSE && +nfs_async_iocounter_wait (task, calldata->l_ctx)) +return ; +if (nfs_wait_on_sequence (calldata->arg.seqid, task) !=0 ) goto out_wait;nfs4_stateid_copy (&calldata->arg.stateid, &calldata->lsp->ls_stateid);
Signature · 1 line
2.9.3
Re: [PATCH 3/6] locks: Set FL_CLOSE when removing flock locks on close()
In reply to Benjamin CoddingtonQuoted 45 lines · Benjamin Coddington
Set
FL_CLOSEinfl_flagsas inlocks_remove_posix()when clearing locks. NFS will check for this flag to ensure an unlock is sent in a following patch.Fuse handles flock and posix locks differently for
FL_CLOSE, and so requires a fixup to retain the existing behavior for flock.Signed-off-by Benjamin Coddington <[email protected]>--- fs/fuse/file.c | 2 +- fs/locks.c | 2 +- include/linux/fs.h | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-)fs/fuse/file.c +1
-1@@ -2172,7 +2172,7 @@ static int fuse_setlk(struct file *file, struct file_lock *fl, int flock) }/* Unlock on close is handled by the flush method */ -if (fl->fl_flags &FL_CLOSE ) +if (fl->fl_flags &FL_CLOSE_POSIX ==FL_CLOSE_POSIX )return 0 ;fuse_lk_fill (&args, file, fl, opcode, pid, flock, &inarg);fs/locks.c +1
-1@@ -2504,7 +2504,7 @@ locks_remove_flock(struct file *filp, struct file_lock_context *flctx) .fl_owner = filp, .fl_pid = current->tgid, .fl_file = filp, - .fl_flags = FL_FLOCK, + .fl_flags = FL_FLOCK | FL_CLOSE, .fl_type = F_UNLCK, .fl_end = OFFSET_MAX, };include/linux/fs.h +0
-0
include/linux/fs.h static inline struct file *get_file(struct file *f) · line 910
Quoted 8 lines
include/linux/fs.h +2
-0@@ -903,6 +903,8 @@ static inline struct file *get_file(struct file *f)#define FL_OFDLCK 1024/* lock is "owned" by struct file */ #define FL_LAYOUT 2048/* outstanding pNFS layout */ +#define FL_CLOSE_POSIX (FL_POSIX | FL_CLOSE) +/* * Special return value from posix_lock_file() and vfs_lock_file() for * asynchronous locking.
Re: [PATCH 4/6] NFS: Add an iocounter wait function for async RPC tasks
In reply to Benjamin CoddingtonQuoted 41 lines · Benjamin Coddington
By sleeping on a new NFS Unlock-On-Close waitqueue, rpc tasks may wait for a lock context's iocounter to reach zero. The rpc waitqueue is only woken when the
open_contexthas theNFS_CONTEXT_UNLOCKflag set in order to mitigate spurious wake-ups for any iocounter reaching zero.Signed-off-by Benjamin Coddington <[email protected]>--- fs/nfs/client.c | 1 + fs/nfs/pagelist.c | 26 +++++++++++++++++++++++++- include/linux/nfs_fs.h | 1 + include/linux/nfs_fs_sb.h | 1 + include/linux/nfs_page.h | 1 + 5 files changed, 29 insertions(+), 1 deletion(-)fs/nfs/client.c +1
-0@@ -218,6 +218,7 @@ static void nfs_cb_idr_remove_locked(struct nfs_client *clp)static void pnfs_init_server (struct nfs_server *server) {rpc_init_wait_queue (&server->roc_rpcwaitq,"pNFS ROC" ); +rpc_init_wait_queue (&server->uoc_rpcwaitq,"NFS UOC" ); }#else fs/nfs/pagelist.c +7
-0@@ -115,6 +115,27 @@ nfs_iocounter_wait(struct nfs_lock_context *l_ctx) TASK_KILLABLE); } +bool +nfs_async_iocounter_wait (struct rpc_task *task,void *data) +{ +struct nfs_lock_context *l_ctx = data; +struct inode *inode =d_inode (l_ctx->open_context->dentry); +bool ret = false; +
fs/nfs/pagelist.c nfs_iocounter_wait(struct nfs_lock_context *l_ctx) · line 134
Quoted 10 lines
fs/nfs/pagelist.c +10
-0+if (atomic_read (&l_ctx->io_count) >0 ) { +rpc_sleep_on (&NFS_SERVER (inode)->uoc_rpcwaitq, task,NULL ); + ret = true; + } + +if (atomic_read (&l_ctx->io_count) ==0 ) { +rpc_wake_up_queued_task (&NFS_SERVER (inode)->uoc_rpcwaitq, task); + ret = false; + } +
nit: Would it be better to do the atomic read once into a local variable and then compare it afterward?
Quoted 49 lines
fs/nfs/pagelist.c +8
-1+return ret; +} +EXPORT_SYMBOL_GPL (nfs_async_iocounter_wait); +/* * nfs_page_group_lock - lock the head of the page group * @req - request in group that is to be locked @@ -398,8 +419,11 @@ static void nfs_clear_request(struct nfs_page *req) req->wb_page =NULL ; }if (l_ctx !=NULL ) { -if (atomic_dec_and_test (&l_ctx->io_count)) +if (atomic_dec_and_test (&l_ctx->io_count)) {wake_up_atomic_t (&l_ctx->io_count); +if (test_bit (NFS_CONTEXT_UNLOCK , &ctx->flags)) +rpc_wake_up (&NFS_SERVER (d_inode (ctx->dentry))->uoc_rpcwaitq); + }nfs_put_lock_context (l_ctx); req->wb_lock_context =NULL ; }include/linux/nfs_fs.h +1
-0@@ -76,6 +76,7 @@ struct nfs_open_context {#define NFS_CONTEXT_ERROR_WRITE (0)#define NFS_CONTEXT_RESEND_WRITES (1)#define NFS_CONTEXT_BAD (2) +#define NFS_CONTEXT_UNLOCK (3)int error;struct list_head list;include/linux/nfs_fs_sb.h +1
-0@@ -222,6 +222,7 @@ struct nfs_server {u32 mountd_version;unsigned short mountd_port;unsigned short mountd_protocol; +struct rpc_wait_queue uoc_rpcwaitq; };/* Server capabilities */ include/linux/nfs_page.h +0
-0
include/linux/nfs_page.h extern int nfs_page_group_lock(struct nfs_page *, bool); · line 147
Quoted 7 lines
include/linux/nfs_page.h +1
-0@@ -141,6 +141,7 @@ extern int nfs_page_group_lock(struct nfs_page *, bool);extern void nfs_page_group_lock_wait (struct nfs_page *);extern void nfs_page_group_unlock (struct nfs_page *);extern bool nfs_page_group_sync_on_bit (struct nfs_page *,unsigned int ); +extern bool nfs_async_iocounter_wait (struct rpc_task *,void *);/* * Lock the page of an asynchronous request
Looks reasonable otherwise:
Re: [PATCH 4/6] NFS: Add an iocounter wait function for async RPC tasks
Quoted 49 lines · Jeff Layton
Quoted 48 lines · Benjamin Coddington
By sleeping on a new NFS Unlock-On-Close waitqueue, rpc tasks may wait for a lock context's iocounter to reach zero. The rpc waitqueue is only woken when the
open_contexthas theNFS_CONTEXT_UNLOCKflag set in order to mitigate spurious wake-ups for any iocounter reaching zero.Signed-off-by Benjamin Coddington <[email protected]>--- fs/nfs/client.c | 1 + fs/nfs/pagelist.c | 26 +++++++++++++++++++++++++- include/linux/nfs_fs.h | 1 + include/linux/nfs_fs_sb.h | 1 + include/linux/nfs_page.h | 1 + 5 files changed, 29 insertions(+), 1 deletion(-)fs/nfs/client.c +1
-0@@ -218,6 +218,7 @@ static void nfs_cb_idr_remove_locked(struct nfs_client *clp)static void pnfs_init_server (struct nfs_server *server) {rpc_init_wait_queue (&server->roc_rpcwaitq,"pNFS ROC" ); +rpc_init_wait_queue (&server->uoc_rpcwaitq,"NFS UOC" ); }#else
fs/nfs/pagelist.c +12
-0@@ -115,6 +115,27 @@ nfs_iocounter_wait(struct nfs_lock_context *l_ctx) TASK_KILLABLE); } +bool +nfs_async_iocounter_wait (struct rpc_task *task,void *data) +{ +struct nfs_lock_context *l_ctx = data; +struct inode *inode =d_inode (l_ctx->open_context->dentry); +bool ret = false; + +if (atomic_read (&l_ctx->io_count) >0 ) { +rpc_sleep_on (&NFS_SERVER (inode)->uoc_rpcwaitq, task,NULL ); + ret = true; + } +
Quoted 8 lines
fs/nfs/pagelist.c nfs_iocounter_wait(struct nfs_lock_context *l_ctx) · line 134
Quoted 5 lines
fs/nfs/pagelist.c +5
-0+if (atomic_read (&l_ctx->io_count) ==0 ) { +rpc_wake_up_queued_task (&NFS_SERVER (inode)->uoc_rpcwaitq, task); + ret = false; + } +nit: Would it be better to do the atomic read once into a local variable and then compare it afterward?
Reading it twice catches the race where we are about to put the task on the wait queue, but then the iocounter goes to zero and the queue wakes.
Quoted 58 lines
Quoted 58 lines
fs/nfs/pagelist.c +4
-0+return ret; +} +EXPORT_SYMBOL_GPL (nfs_async_iocounter_wait); +/* * nfs_page_group_lock - lock the head of the page group * @req - request in group that is to be locked
fs/nfs/pagelist.c +4
-1@@ -398,8 +419,11 @@ static void nfs_clear_request(struct nfs_page *req) req->wb_page =NULL ; }if (l_ctx !=NULL ) { -if (atomic_dec_and_test (&l_ctx->io_count)) +if (atomic_dec_and_test (&l_ctx->io_count)) {wake_up_atomic_t (&l_ctx->io_count); +if (test_bit (NFS_CONTEXT_UNLOCK , &ctx->flags)) +rpc_wake_up (&NFS_SERVER (d_inode (ctx->dentry))->uoc_rpcwaitq); + }nfs_put_lock_context (l_ctx); req->wb_lock_context =NULL ;fs/nfs/pagelist.c +0
-0}include/linux/nfs_fs.h +1
-0@@ -76,6 +76,7 @@ struct nfs_open_context {#define NFS_CONTEXT_ERROR_WRITE (0)#define NFS_CONTEXT_RESEND_WRITES (1)#define NFS_CONTEXT_BAD (2) +#define NFS_CONTEXT_UNLOCK (3)int error;struct list_head list;include/linux/nfs_fs_sb.h +1
-0@@ -222,6 +222,7 @@ struct nfs_server {u32 mountd_version;unsigned short mountd_port;unsigned short mountd_protocol; +struct rpc_wait_queue uoc_rpcwaitq; };/* Server capabilities */ include/linux/nfs_page.h +1
-0@@ -141,6 +141,7 @@ extern int nfs_page_group_lock(struct nfs_page *, bool);extern void nfs_page_group_lock_wait (struct nfs_page *);extern void nfs_page_group_unlock (struct nfs_page *);extern bool nfs_page_group_sync_on_bit (struct nfs_page *,unsigned int ); +extern bool nfs_async_iocounter_wait (struct rpc_task *,void *);
Quoted 6 lines
Quoted 2 lines
/*
* Lock the page of an asynchronous requestLooks reasonable otherwise:
Reviewed-by Jeff Layton <[email protected]>
Thanks for the review, and all the help..
Ben
Re: [PATCH 5/6] lockd: Introduce nlmclnt_operations
In reply to Benjamin CoddingtonQuoted 34 lines · Benjamin Coddington
NFS would enjoy the ability to modify the behavior of the NLM client's unlock RPC task in order to delay the transmission of the unlock until IO that was submitted under that lock has completed. This ability can ensure that the NLM client will always complete the transmission of an unlock even if the waiting caller has been interrupted with fatal signal.
A struct
nlmclnt_operationsand callback data pointer can be passed tonlmclnt_proc(). The structnlmclnt_operationsdefines three callback operations that will be used in a following patch:
nlmclnt_alloc_call- used to call back after a successful allocation of
a structnlm_rqstinnlmclnt_proc().
nlmclnt_unlock_prepare- used to call back during NLM unlock's
rpc_call_prepare. The NLM client defers callingrpc_call_start()until this callback returns false.
nlmclnt_release_call- used to call back when the NLM client's struct
nlm_rqstis freed.Signed-off-by Benjamin Coddington <[email protected]>--- fs/lockd/clntproc.c | 26 +++++++++++++++++++++++++- fs/nfs/nfs3proc.c | 2 +- fs/nfs/proc.c | 2 +- include/linux/lockd/bind.h | 11 +++++++++-- include/linux/lockd/lockd.h | 2 ++ 5 files changed, 38 insertions(+), 5 deletions(-)fs/lockd/clntproc.c +0
-0
fs/lockd/clntproc.c static void nlmclnt_release_lockargs(struct nlm_rqst *req) · line 154
Quoted 5 lines
fs/lockd/clntproc.c +2
-0@@ -150,9 +150,12 @@ static void nlmclnt_release_lockargs(struct nlm_rqst *req)* @host: address of a valid nlm_host context representing the NLM server * @cmd: fcntl-style file lock operation to perform * @fl: address of arguments for the lock operation +* @nlmclnt_ops: operations to call back out of nlm +* @data: address of data to be sent along with callback
I now get that you're using the presence or absence of the ops pointer as an indicator for the underlying layers to do the callbacks or not. That's a little odd, and should probably be clearly documented.
I still think it might be cleaner to put these ops in the host and handle the condition of whether to run them in the callbacks themselves, but I can live with what you have here. We can always change that later if we find it to be problematic.
Quoted 105 lines
fs/lockd/clntproc.c +5
-1* */ -int nlmclnt_proc (struct nlm_host *host,int cmd,struct file_lock *fl) +int nlmclnt_proc (struct nlm_host *host,int cmd,struct file_lock *fl, +const struct nlmclnt_operations *nlmclnt_ops,void *data) {struct nlm_rqst *call;int status;@@ -161,6 +164,9 @@ int nlmclnt_proc(struct nlm_host *host, int cmd, struct file_lock *fl)if (call ==NULL )return -ENOMEM ; +if (nlmclnt_ops && nlmclnt_ops->nlmclnt_alloc_call) + nlmclnt_ops->nlmclnt_alloc_call (data); +nlmclnt_locks_init_private (fl, host);fs/lockd/clntproc.c +18
-0if (!fl->fl_u.nfs_fl.owner) {/* lockowner allocation has failed */ @@ -169,6 +175,8 @@ int nlmclnt_proc(struct nlm_host *host, int cmd, struct file_lock *fl) }/* Set up the argument struct */ nlmclnt_setlockargs (call, fl); + call->nlmclnt_ops = nlmclnt_ops; + call->callback_data = data;if (IS_SETLK (cmd) ||IS_SETLKW (cmd)) {if (fl->fl_type !=F_UNLCK ) {@@ -216,6 +224,8 @@ void nlmclnt_release_call(struct nlm_rqst *call) {if (!atomic_dec_and_test (&call->a_count))return ; +if (call->nlmclnt_ops && call->nlmclnt_ops->nlmclnt_release_call) + call->nlmclnt_ops->nlmclnt_release_call (call->callback_data);nlmclnt_release_host (call->a_host);nlmclnt_release_lockargs (call);kfree (call);@@ -687,6 +697,19 @@ nlmclnt_unlock(struct nlm_rqst *req, struct file_lock *fl)return status; } +static void nlmclnt_unlock_prepare (struct rpc_task *task,void *data) +{ +struct nlm_rqst *req = data; +const struct nlmclnt_operations *nlmclnt_ops = req->nlmclnt_ops; +bool defer_call = false; + +if (nlmclnt_ops && nlmclnt_ops->nlmclnt_unlock_prepare) + defer_call = nlmclnt_ops->nlmclnt_unlock_prepare (task, req->callback_data); + +if (!defer_call) +rpc_call_start (task); +} +static void nlmclnt_unlock_callback (struct rpc_task *task,void *data) {struct nlm_rqst *req = data;@@ -720,6 +743,7 @@ static void nlmclnt_unlock_callback(struct rpc_task *task, void *data) }static const struct rpc_call_ops nlmclnt_unlock_ops = { + .rpc_call_prepare = nlmclnt_unlock_prepare, .rpc_call_done = nlmclnt_unlock_callback, .rpc_release = nlmclnt_rpc_release, };fs/nfs/nfs3proc.c +1
-1@@ -870,7 +870,7 @@ nfs3_proc_lock(struct file *filp, int cmd, struct file_lock *fl) {struct inode *inode =file_inode (filp); -return nlmclnt_proc (NFS_SERVER (inode)->nlm_host, cmd, fl); +return nlmclnt_proc (NFS_SERVER (inode)->nlm_host, cmd, fl,NULL ,NULL ); }static int nfs3_have_delegation (struct inode *inode,fmode_t flags)fs/nfs/proc.c +1
-1@@ -638,7 +638,7 @@ nfs_proc_lock(struct file *filp, int cmd, struct file_lock *fl) {struct inode *inode =file_inode (filp); -return nlmclnt_proc (NFS_SERVER (inode)->nlm_host, cmd, fl); +return nlmclnt_proc (NFS_SERVER (inode)->nlm_host, cmd, fl,NULL ,NULL ); }/* Helper functions for NFS lock bounds checking */ include/linux/lockd/bind.h +1
-0@@ -18,6 +18,7 @@/* Dummy declarations */ struct svc_rqst ; +struct rpc_task ;/* * This is the set of functions for lockd->nfsd communication
include/linux/lockd/bind.h struct nlmclnt_initdata { · line 56
Quoted 5 lines
include/linux/lockd/bind.h +0
-2@@ -52,8 +53,14 @@ struct nlmclnt_initdata {extern struct nlm_host *nlmclnt_init (const struct nlmclnt_initdata *nlm_init);extern void nlmclnt_done (struct nlm_host *host); -extern int nlmclnt_proc (struct nlm_host *host,int cmd, -struct file_lock *fl);
With a new ops struct like the one below, it's good to add some comments that explain what each of these operations does, any locking constraints, etc. Laying out the rules now makes it simpler to follow them later.
Quoted 16 lines
include/linux/lockd/bind.h +8
-0+struct nlmclnt_operations { +void (*nlmclnt_alloc_call)(void *); +bool (*nlmclnt_unlock_prepare)(struct rpc_task *,void *); +void (*nlmclnt_release_call)(void *); +}; + +extern int nlmclnt_proc (struct nlm_host *host,int cmd,struct file_lock *fl, +const struct nlmclnt_operations *nlmclnt_ops,void *data);extern int lockd_up (struct net *net);extern void lockd_down (struct net *net);include/linux/lockd/lockd.h +0
-0
include/linux/lockd/lockd.h struct nlm_rqst { · line 146
Quoted 5 lines
include/linux/lockd/lockd.h +2
-0@@ -142,6 +142,8 @@ struct nlm_rqst {struct nlm_block * a_block;unsigned int a_retries;/* Retry count */ u8 a_owner[NLMCLNT_OHSIZE ]; +const struct nlmclnt_operations * nlmclnt_ops; +void * callback_data;
nit: please fix the alignment of the struct there. Also, I think these pointers should have an "a_" in order to follow the convention.
include/linux/lockd/lockd.h struct nlm_rqst { · line 149
Quoted 3 lines
include/linux/lockd/lockd.h +0
-0};/*
Overall though, I think this looks good. Assuming you fix the nits:
Re: [PATCH 6/6] NFS: Always wait for I/O completion before unlock
In reply to Benjamin CoddingtonQuoted 17 lines · Benjamin Coddington
NFS attempts to wait for read and write completion before unlocking in order to ensure that the data returned was protected by the lock. When this waiting is interrupted by a signal, the unlock may be skipped, and messages similar to the following are seen in the kernel ring buffer:
[20.167876] Leaked locks on dev=0x0:0x2b ino=0x8dd4c3: [20.168286] POSIX: fl_owner=ffff880078b06940 fl_flags=0x1 fl_type=0x0 fl_pid=20183 [20.168727] POSIX: fl_owner=ffff880078b06680 fl_flags=0x1 fl_type=0x0 fl_pid=20185For NFSv3, the missing unlock will cause the server to refuse conflicting locks indefinitely. For NFSv4, the leftover lock will be removed by the server after the lease timeout.
This patch fixes this issue by skipping the usual wait in
nfs_iocounter_waitif theFL_CLOSEflag is set when signaled. Instead, the wait happens in the unlock RPC task on the NFS UOCrpc_waitqueue.
Quoted 7 lines
For NFSv3, use lockd's new
nlmclnt_operationsalong withnfs_async_iocounter_waitto defer NLM's unlock task until the lock context's iocounter reaches zero.For NFSv4, call
nfs_async_iocounter_wait()directly from unlock's currentrpc_call_prepare.
Dare I ask about v2? :)
Hmm actually, it looks like v2 could use the same operations as v3. I don't see anything protocol-specific there.
Quoted 63 lines
Signed-off-by Benjamin Coddington <[email protected]>--- fs/nfs/file.c | 10 +++++----- fs/nfs/nfs3proc.c | 38 +++++++++++++++++++++++++++++++++++++- fs/nfs/nfs4proc.c | 10 +++++++--- 3 files changed, 49 insertions(+), 9 deletions(-)fs/nfs/file.c +5
-5@@ -697,14 +697,14 @@ do_unlk(struct file *filp, int cmd, struct file_lock *fl, int is_local)if (!IS_ERR (l_ctx)) { status =nfs_iocounter_wait (l_ctx);nfs_put_lock_context (l_ctx); -if (status <0 ) +/* NOTE: special case +* If we're signalled while cleaning up locks on process exit, we +* still need to complete the unlock. +*/ +if (status <0 && !(fl->fl_flags &FL_CLOSE ))return status; } -/* NOTE: special case -* If we're signalled while cleaning up locks on process exit, we -* still need to complete the unlock. -*/ /* * Use local locking if mounted with "-onolock" or with appropriate * "-olocal_lock=" fs/nfs/nfs3proc.c +20
-0@@ -865,12 +865,48 @@ static void nfs3_proc_commit_setup(struct nfs_commit_data *data, struct rpc_mess msg->rpc_proc = &nfs3_procedures[NFS3PROC_COMMIT ]; } +void nfs3_nlm_alloc_call (void *data) +{ +struct nfs_lock_context *l_ctx = data; +nfs_get_lock_context (l_ctx->open_context); +} + +void nfs3_nlm_release_call (void *data) +{ +struct nfs_lock_context *l_ctx = data; +nfs_put_lock_context (l_ctx); +} + +const struct nlmclnt_operations nlmclnt_fl_close_lock_ops = { + .nlmclnt_alloc_call = nfs3_nlm_alloc_call, + .nlmclnt_unlock_prepare = nfs_async_iocounter_wait, + .nlmclnt_release_call = nfs3_nlm_release_call, +}; +static int nfs3_proc_lock (struct file *filp,int cmd,struct file_lock *fl) {struct inode *inode =file_inode (filp); +struct nfs_lock_context *l_ctx =NULL ; +const struct nlmclnt_operations *nlmclnt_ops =NULL ;
fs/nfs/nfs3proc.c static void nfs3_proc_commit_setup(struct nfs_commit_data *data, struct rpc_mess · line 900
Quoted 10 lines
fs/nfs/nfs3proc.c +8
-1+int status; -return nlmclnt_proc (NFS_SERVER (inode)->nlm_host, cmd, fl,NULL ,NULL ); +if (fl->fl_flags &FL_CLOSE ) { + l_ctx =nfs_get_lock_context (nfs_file_open_context (filp)); +if (IS_ERR (l_ctx)) { + l_ctx =NULL ; + }else { +set_bit (NFS_CONTEXT_UNLOCK , &l_ctx->open_context->flags); + nlmclnt_ops = &nlmclnt_fl_close_lock_ops;
FWIW, I'm not a huge fan of using the pointer to indicate whether to run the operations or not. IMO, it'd be cleaner to:
1) store the pointer to the operations struct in the nlm_host, pass it a pointer to it in the nlmclnt_initdata.
2) make the decision to do the operations or not based on the setting of NFS_CONTEXT_UNLOCK in the callbacks themselves. If it's not set, just return quickly without doing anything.
Quoted 54 lines
fs/nfs/nfs3proc.c +9
-0+ } + } + + status =nlmclnt_proc (NFS_SERVER (inode)->nlm_host, cmd, fl, + nlmclnt_ops, l_ctx); +if (l_ctx) +nfs_put_lock_context (l_ctx); + +return status; }static int nfs3_have_delegation (struct inode *inode,fmode_t flags)fs/nfs/nfs4proc.c +7
-3@@ -5905,7 +5905,7 @@ struct nfs4_unlockdata {struct nfs_locku_args arg;struct nfs_locku_res res;struct nfs4_lock_state *lsp; -struct nfs_open_context *ctx; +struct nfs_lock_context *l_ctx;struct file_lock fl;struct nfs_server *server;unsigned long timestamp;@@ -5929,7 +5929,7 @@ static struct nfs4_unlockdata *nfs4_alloc_unlockdata(struct file_lock *fl, p->lsp = lsp;atomic_inc (&lsp->ls_count);/* Ensure we don't close file until we're done freeing locks! */ - p->ctx =get_nfs_open_context (ctx); + p->l_ctx =nfs_get_lock_context (ctx);memcpy (&p->fl, fl,sizeof (p->fl)); p->server =NFS_SERVER (inode);return p;@@ -5940,7 +5940,7 @@ static void nfs4_locku_release_calldata(void *data)struct nfs4_unlockdata *calldata = data;nfs_free_seqid (calldata->arg.seqid);nfs4_put_lock_state (calldata->lsp); -put_nfs_open_context (calldata->ctx); +nfs_put_lock_context (calldata->l_ctx);kfree (calldata); }@@ -5981,6 +5981,10 @@ static void nfs4_locku_prepare(struct rpc_task *task, void *data) {struct nfs4_unlockdata *calldata = data; +if (calldata->fl.fl_flags &FL_CLOSE && +nfs_async_iocounter_wait (task, calldata->l_ctx)) +return ; +if (nfs_wait_on_sequence (calldata->arg.seqid, task) !=0 ) goto out_wait;nfs4_stateid_copy (&calldata->arg.stateid, &calldata->lsp->ls_stateid);
Signature · 1 line
Jeff Layton <[email protected]>
Re: [PATCH 6/6] NFS: Always wait for I/O completion before unlock
Quoted 111 lines · Jeff Layton
Quoted 17 lines · Benjamin Coddington
NFS attempts to wait for read and write completion before unlocking in order to ensure that the data returned was protected by the lock. When this waiting is interrupted by a signal, the unlock may be skipped, and messages similar to the following are seen in the kernel ring buffer:
[20.167876] Leaked locks on dev=0x0:0x2b ino=0x8dd4c3: [20.168286] POSIX: fl_owner=ffff880078b06940 fl_flags=0x1 fl_type=0x0 fl_pid=20183 [20.168727] POSIX: fl_owner=ffff880078b06680 fl_flags=0x1 fl_type=0x0 fl_pid=20185For NFSv3, the missing unlock will cause the server to refuse conflicting locks indefinitely. For NFSv4, the leftover lock will be removed by the server after the lease timeout.
This patch fixes this issue by skipping the usual wait in
nfs_iocounter_waitif theFL_CLOSEflag is set when signaled. Instead, the wait happens in the unlock RPC task on the NFS UOCrpc_waitqueue.Quoted 7 lines
For NFSv3, use lockd's new
nlmclnt_operationsalong withnfs_async_iocounter_waitto defer NLM's unlock task until the lock context's iocounter reaches zero.For NFSv4, call
nfs_async_iocounter_wait()directly from unlock's currentrpc_call_prepare.Dare I ask about v2? :)
Hmm actually, it looks like v2 could use the same operations as v3. I don't see anything protocol-specific there.
Quoted 69 lines
Signed-off-by Benjamin Coddington <[email protected]>--- fs/nfs/file.c | 10 +++++----- fs/nfs/nfs3proc.c | 38 +++++++++++++++++++++++++++++++++++++- fs/nfs/nfs4proc.c | 10 +++++++--- 3 files changed, 49 insertions(+), 9 deletions(-)fs/nfs/file.c +5
-5@@ -697,14 +697,14 @@ do_unlk(struct file *filp, int cmd, struct file_lock *fl, int is_local)if (!IS_ERR (l_ctx)) { status =nfs_iocounter_wait (l_ctx);nfs_put_lock_context (l_ctx); -if (status <0 ) +/* NOTE: special case +* If we're signalled while cleaning up locks on process exit, we +* still need to complete the unlock. +*/ +if (status <0 && !(fl->fl_flags &FL_CLOSE ))return status; } -/* NOTE: special case -* If we're signalled while cleaning up locks on process exit, we -* still need to complete the unlock. -*/ /* * Use local locking if mounted with "-onolock" or with appropriate fs/nfs/file.c +0
-0* "-olocal_lock=" fs/nfs/nfs3proc.c +0
-0@@ -865,12 +865,48 @@ static void nfs3_proc_commit_setup(struct
nfs_commit_data*data, structrpc_messfs/nfs/nfs3proc.c +21
-1msg->rpc_proc = &nfs3_procedures[NFS3PROC_COMMIT ]; } +void nfs3_nlm_alloc_call (void *data) +{ +struct nfs_lock_context *l_ctx = data; +nfs_get_lock_context (l_ctx->open_context); +} + +void nfs3_nlm_release_call (void *data) +{ +struct nfs_lock_context *l_ctx = data; +nfs_put_lock_context (l_ctx); +} + +const struct nlmclnt_operations nlmclnt_fl_close_lock_ops = { + .nlmclnt_alloc_call = nfs3_nlm_alloc_call, + .nlmclnt_unlock_prepare = nfs_async_iocounter_wait, + .nlmclnt_release_call = nfs3_nlm_release_call, +}; +static int nfs3_proc_lock (struct file *filp,int cmd,struct file_lock *fl) {struct inode *inode =file_inode (filp); +struct nfs_lock_context *l_ctx =NULL ; +const struct nlmclnt_operations *nlmclnt_ops =NULL ; +int status; - return nlmclnt_proc(NFS_SERVER(inode)->nlm_host, cmd, fl, NULL,NULL);
fs/nfs/nfs3proc.c static void nfs3_proc_commit_setup(struct nfs_commit_data *data, struct rpc_mess · line 900
Quoted 7 lines
fs/nfs/nfs3proc.c +7
-0+if (fl->fl_flags &FL_CLOSE ) { + l_ctx =nfs_get_lock_context (nfs_file_open_context (filp)); +if (IS_ERR (l_ctx)) { + l_ctx =NULL ; + }else { +set_bit (NFS_CONTEXT_UNLOCK , &l_ctx->open_context->flags); + nlmclnt_ops = &nlmclnt_fl_close_lock_ops;FWIW, I'm not a huge fan of using the pointer to indicate whether to run the operations or not. IMO, it'd be cleaner to:
Quoted 6 lines
1) store the pointer to the operations struct in the
nlm_host, pass it a pointer to it in thenlmclnt_initdata.2) make the decision to do the operations or not based on the setting of
NFS_CONTEXT_UNLOCKin the callbacks themselves. If it's not set, just return quickly without doing anything.
Yes, I can see how that could be a bit neater. And I can add in the same behavior for v2 while making this change.
I'll work it up that way and send it again.
Ben
kbuild test robot
build botRe: [PATCH 3/6] locks: Set FL_CLOSE when removing flock locks on close()
In reply to Benjamin Coddingtonreproduce:
Hi Benjamin,
[auto build test WARNING on nfs/linux-next]
[also build test WARNING on v4.11-rc5 next-20170407]
[if your patch is applied to the wrong git tree, please drop us a note to help improve the system]
url: https://github.com/0day-ci/linux/commits/Benjamin-Coddington/Skipped-unlocks/20170407-045120 base: git://git.linux-nfs.org/projects/trondmy/linux-nfs.git linux-next config: x86_64-randconfig-n0-04081158 (attached as .config) compiler: gcc-4.8 (Debian 4.8.4-1) 4.8.4 reproduce: # save the attached .config to linux build tree make ARCH=x86_64
All warnings (new ones prefixed by >>):
fs/fuse/file.c: In function 'fuse_setlk':
fs/fuse/file.c:2171:2: warning: suggest parentheses around comparison in operand of '&' [-Wparentheses]
if (fl->fl_flags & FL_CLOSE_POSIX == FL_CLOSE_POSIX)
^
fs/fuse/file.c:2171:2: warning: suggest parentheses around comparison in operand of '&' [-Wparentheses]
In file included from include/uapi/linux/stddef.h:1:0,
from include/linux/stddef.h:4,
from include/uapi/linux/posix_types.h:4,
from include/uapi/linux/types.h:13,
from include/linux/types.h:5,
from include/uapi/linux/fuse.h:121,
from fs/fuse/fuse_i.h:12,
from fs/fuse/file.c:9:
>> include/linux/compiler.h:163:17: warning: suggest parentheses around comparison in operand of '&' [-Wparentheses]
static struct ftrace_branch_data \
^
include/linux/compiler.h:158:23: note: in expansion of macro '__trace_if'
#define if(cond, ...) __trace_if( (cond , ## __VA_ARGS__) )
^
fs/fuse/file.c:2171:2: note: in expansion of macro 'if'
if (fl->fl_flags & FL_CLOSE_POSIX == FL_CLOSE_POSIX)
^
--
fs//fuse/file.c: In function 'fuse_setlk':
fs//fuse/file.c:2171:2: warning: suggest parentheses around comparison in operand of '&' [-Wparentheses]
if (fl->fl_flags & FL_CLOSE_POSIX == FL_CLOSE_POSIX)
^
fs//fuse/file.c:2171:2: warning: suggest parentheses around comparison in operand of '&' [-Wparentheses]
In file included from include/uapi/linux/stddef.h:1:0,
from include/linux/stddef.h:4,
from include/uapi/linux/posix_types.h:4,
from include/uapi/linux/types.h:13,
from include/linux/types.h:5,
from include/uapi/linux/fuse.h:121,
from fs//fuse/fuse_i.h:12,
from fs//fuse/file.c:9:
>> include/linux/compiler.h:163:17: warning: suggest parentheses around comparison in operand of '&' [-Wparentheses]
static struct ftrace_branch_data \
^
include/linux/compiler.h:158:23: note: in expansion of macro '__trace_if'
#define if(cond, ...) __trace_if( (cond , ## __VA_ARGS__) )
^
fs//fuse/file.c:2171:2: note: in expansion of macro 'if'
if (fl->fl_flags & FL_CLOSE_POSIX == FL_CLOSE_POSIX)
^vim +163 include/linux/compiler.h
d45ae1f7 Steven Rostedt (VMware 2017-01-17 147) # define likely(x) (__branch_check__(x, 1, __builtin_constant_p(x)))
1f0d69a9 Steven Rostedt 2008-11-12 148 # endif
1f0d69a9 Steven Rostedt 2008-11-12 149 # ifndef unlikely
d45ae1f7 Steven Rostedt (VMware 2017-01-17 150) # define unlikely(x) (__branch_check__(x, 0, __builtin_constant_p(x)))
1f0d69a9 Steven Rostedt 2008-11-12 151 # endif
2bcd521a Steven Rostedt 2008-11-21 152
2bcd521a Steven Rostedt 2008-11-21 153 #ifdef CONFIG_PROFILE_ALL_BRANCHES
2bcd521a Steven Rostedt 2008-11-21 154 /*
2bcd521a Steven Rostedt 2008-11-21 155 * "Define 'is'", Bill Clinton
2bcd521a Steven Rostedt 2008-11-21 156 * "Define 'if'", Steven Rostedt
2bcd521a Steven Rostedt 2008-11-21 157 */
ab3c9c68 Linus Torvalds 2009-04-07 158 #define if(cond, ...) __trace_if( (cond , ## __VA_ARGS__) )
ab3c9c68 Linus Torvalds 2009-04-07 159 #define __trace_if(cond) \
b33c8ff4 Arnd Bergmann 2016-02-12 160 if (__builtin_constant_p(!!(cond)) ? !!(cond) : \
2bcd521a Steven Rostedt 2008-11-21 161 ({ \
2bcd521a Steven Rostedt 2008-11-21 162 int ______r; \
2bcd521a Steven Rostedt 2008-11-21 @163 static struct ftrace_branch_data \
2bcd521a Steven Rostedt 2008-11-21 164 __attribute__((__aligned__(4))) \
2bcd521a Steven Rostedt 2008-11-21 165 __attribute__((section("_ftrace_branch"))) \
2bcd521a Steven Rostedt 2008-11-21 166 ______f = { \
2bcd521a Steven Rostedt 2008-11-21 167 .func = __func__, \
2bcd521a Steven Rostedt 2008-11-21 168 .file = __FILE__, \
2bcd521a Steven Rostedt 2008-11-21 169 .line = __LINE__, \
2bcd521a Steven Rostedt 2008-11-21 170 }; \
2bcd521a Steven Rostedt 2008-11-21 171 ______r = !!(cond); \:::::: The code at line 163 was first introduced by commit
:::::: 2bcd521a684cc94befbe2ce7d5b613c841b0d304 trace: profile all if conditionals
:::::: TO: Steven Rostedt <[email protected]> :::::: CC: Ingo Molnar <[email protected]>
---
0-DAY kernel test infrastructure Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all Intel Corporation