diff options
author | Trond Myklebust <Trond.Myklebust@netapp.com> | 2005-06-22 13:16:31 -0400 |
---|---|---|
committer | Trond Myklebust <Trond.Myklebust@netapp.com> | 2005-06-22 16:07:42 -0400 |
commit | ecdbf769b2cb8903e07cd482334c714d89fd1146 (patch) | |
tree | 9d02ce4daee662c2711762564662cebc521e3da3 /fs/lockd | |
parent | 4f15e2b1f4f3a56e46201714b39436c32218d547 (diff) |
[PATCH] NLM: fix a client-side race on blocking locks.
If the lock blocks, the server may send us a GRANTED message that
races with the reply to our LOCK request. Make sure that we catch
the GRANTED by queueing up our request on the nlm_blocked list
before we send off the first LOCK rpc call.
Signed-off-by: Trond Myklebust <Trond.Myklebust@netapp.com>
Diffstat (limited to 'fs/lockd')
-rw-r--r-- | fs/lockd/clntlock.c | 99 | ||||
-rw-r--r-- | fs/lockd/clntproc.c | 40 |
2 files changed, 90 insertions, 49 deletions
diff --git a/fs/lockd/clntlock.c b/fs/lockd/clntlock.c index 44adb84183b6..006bb9e14579 100644 --- a/fs/lockd/clntlock.c +++ b/fs/lockd/clntlock.c | |||
@@ -42,23 +42,51 @@ struct nlm_wait { | |||
42 | static LIST_HEAD(nlm_blocked); | 42 | static LIST_HEAD(nlm_blocked); |
43 | 43 | ||
44 | /* | 44 | /* |
45 | * Block on a lock | 45 | * Queue up a lock for blocking so that the GRANTED request can see it |
46 | */ | 46 | */ |
47 | int | 47 | int nlmclnt_prepare_block(struct nlm_rqst *req, struct nlm_host *host, struct file_lock *fl) |
48 | nlmclnt_block(struct nlm_host *host, struct file_lock *fl, u32 *statp) | 48 | { |
49 | struct nlm_wait *block; | ||
50 | |||
51 | BUG_ON(req->a_block != NULL); | ||
52 | block = kmalloc(sizeof(*block), GFP_KERNEL); | ||
53 | if (block == NULL) | ||
54 | return -ENOMEM; | ||
55 | block->b_host = host; | ||
56 | block->b_lock = fl; | ||
57 | init_waitqueue_head(&block->b_wait); | ||
58 | block->b_status = NLM_LCK_BLOCKED; | ||
59 | |||
60 | list_add(&block->b_list, &nlm_blocked); | ||
61 | req->a_block = block; | ||
62 | |||
63 | return 0; | ||
64 | } | ||
65 | |||
66 | void nlmclnt_finish_block(struct nlm_rqst *req) | ||
49 | { | 67 | { |
50 | struct nlm_wait block, **head; | 68 | struct nlm_wait *block = req->a_block; |
51 | int err; | ||
52 | u32 pstate; | ||
53 | 69 | ||
54 | block.b_host = host; | 70 | if (block == NULL) |
55 | block.b_lock = fl; | 71 | return; |
56 | init_waitqueue_head(&block.b_wait); | 72 | req->a_block = NULL; |
57 | block.b_status = NLM_LCK_BLOCKED; | 73 | list_del(&block->b_list); |
58 | list_add(&block.b_list, &nlm_blocked); | 74 | kfree(block); |
75 | } | ||
59 | 76 | ||
60 | /* Remember pseudo nsm state */ | 77 | /* |
61 | pstate = host->h_state; | 78 | * Block on a lock |
79 | */ | ||
80 | long nlmclnt_block(struct nlm_rqst *req, long timeout) | ||
81 | { | ||
82 | struct nlm_wait *block = req->a_block; | ||
83 | long ret; | ||
84 | |||
85 | /* A borken server might ask us to block even if we didn't | ||
86 | * request it. Just say no! | ||
87 | */ | ||
88 | if (!req->a_args.block) | ||
89 | return -EAGAIN; | ||
62 | 90 | ||
63 | /* Go to sleep waiting for GRANT callback. Some servers seem | 91 | /* Go to sleep waiting for GRANT callback. Some servers seem |
64 | * to lose callbacks, however, so we're going to poll from | 92 | * to lose callbacks, however, so we're going to poll from |
@@ -68,23 +96,16 @@ nlmclnt_block(struct nlm_host *host, struct file_lock *fl, u32 *statp) | |||
68 | * a 1 minute timeout would do. See the comment before | 96 | * a 1 minute timeout would do. See the comment before |
69 | * nlmclnt_lock for an explanation. | 97 | * nlmclnt_lock for an explanation. |
70 | */ | 98 | */ |
71 | sleep_on_timeout(&block.b_wait, 30*HZ); | 99 | ret = wait_event_interruptible_timeout(block->b_wait, |
100 | block->b_status != NLM_LCK_BLOCKED, | ||
101 | timeout); | ||
72 | 102 | ||
73 | list_del(&block.b_list); | 103 | if (block->b_status != NLM_LCK_BLOCKED) { |
74 | 104 | req->a_res.status = block->b_status; | |
75 | if (!signalled()) { | 105 | block->b_status = NLM_LCK_BLOCKED; |
76 | *statp = block.b_status; | ||
77 | return 0; | ||
78 | } | 106 | } |
79 | 107 | ||
80 | /* Okay, we were interrupted. Cancel the pending request | 108 | return ret; |
81 | * unless the server has rebooted. | ||
82 | */ | ||
83 | if (pstate == host->h_state && (err = nlmclnt_cancel(host, fl)) < 0) | ||
84 | printk(KERN_NOTICE | ||
85 | "lockd: CANCEL call failed (errno %d)\n", -err); | ||
86 | |||
87 | return -ERESTARTSYS; | ||
88 | } | 109 | } |
89 | 110 | ||
90 | /* | 111 | /* |
@@ -94,27 +115,23 @@ u32 | |||
94 | nlmclnt_grant(struct nlm_lock *lock) | 115 | nlmclnt_grant(struct nlm_lock *lock) |
95 | { | 116 | { |
96 | struct nlm_wait *block; | 117 | struct nlm_wait *block; |
118 | u32 res = nlm_lck_denied; | ||
97 | 119 | ||
98 | /* | 120 | /* |
99 | * Look up blocked request based on arguments. | 121 | * Look up blocked request based on arguments. |
100 | * Warning: must not use cookie to match it! | 122 | * Warning: must not use cookie to match it! |
101 | */ | 123 | */ |
102 | list_for_each_entry(block, &nlm_blocked, b_list) { | 124 | list_for_each_entry(block, &nlm_blocked, b_list) { |
103 | if (nlm_compare_locks(block->b_lock, &lock->fl)) | 125 | if (nlm_compare_locks(block->b_lock, &lock->fl)) { |
104 | break; | 126 | /* Alright, we found a lock. Set the return status |
127 | * and wake up the caller | ||
128 | */ | ||
129 | block->b_status = NLM_LCK_GRANTED; | ||
130 | wake_up(&block->b_wait); | ||
131 | res = nlm_granted; | ||
132 | } | ||
105 | } | 133 | } |
106 | 134 | return res; | |
107 | /* Ooops, no blocked request found. */ | ||
108 | if (block == NULL) | ||
109 | return nlm_lck_denied; | ||
110 | |||
111 | /* Alright, we found the lock. Set the return status and | ||
112 | * wake up the caller. | ||
113 | */ | ||
114 | block->b_status = NLM_LCK_GRANTED; | ||
115 | wake_up(&block->b_wait); | ||
116 | |||
117 | return nlm_granted; | ||
118 | } | 135 | } |
119 | 136 | ||
120 | /* | 137 | /* |
diff --git a/fs/lockd/clntproc.c b/fs/lockd/clntproc.c index a4407619b1f1..fd77ed1d710d 100644 --- a/fs/lockd/clntproc.c +++ b/fs/lockd/clntproc.c | |||
@@ -21,6 +21,7 @@ | |||
21 | 21 | ||
22 | #define NLMDBG_FACILITY NLMDBG_CLIENT | 22 | #define NLMDBG_FACILITY NLMDBG_CLIENT |
23 | #define NLMCLNT_GRACE_WAIT (5*HZ) | 23 | #define NLMCLNT_GRACE_WAIT (5*HZ) |
24 | #define NLMCLNT_POLL_TIMEOUT (30*HZ) | ||
24 | 25 | ||
25 | static int nlmclnt_test(struct nlm_rqst *, struct file_lock *); | 26 | static int nlmclnt_test(struct nlm_rqst *, struct file_lock *); |
26 | static int nlmclnt_lock(struct nlm_rqst *, struct file_lock *); | 27 | static int nlmclnt_lock(struct nlm_rqst *, struct file_lock *); |
@@ -553,7 +554,8 @@ nlmclnt_lock(struct nlm_rqst *req, struct file_lock *fl) | |||
553 | { | 554 | { |
554 | struct nlm_host *host = req->a_host; | 555 | struct nlm_host *host = req->a_host; |
555 | struct nlm_res *resp = &req->a_res; | 556 | struct nlm_res *resp = &req->a_res; |
556 | int status; | 557 | long timeout; |
558 | int status; | ||
557 | 559 | ||
558 | if (!host->h_monitored && nsm_monitor(host) < 0) { | 560 | if (!host->h_monitored && nsm_monitor(host) < 0) { |
559 | printk(KERN_NOTICE "lockd: failed to monitor %s\n", | 561 | printk(KERN_NOTICE "lockd: failed to monitor %s\n", |
@@ -562,15 +564,32 @@ nlmclnt_lock(struct nlm_rqst *req, struct file_lock *fl) | |||
562 | goto out; | 564 | goto out; |
563 | } | 565 | } |
564 | 566 | ||
565 | do { | 567 | if (req->a_args.block) { |
566 | if ((status = nlmclnt_call(req, NLMPROC_LOCK)) >= 0) { | 568 | status = nlmclnt_prepare_block(req, host, fl); |
567 | if (resp->status != NLM_LCK_BLOCKED) | ||
568 | break; | ||
569 | status = nlmclnt_block(host, fl, &resp->status); | ||
570 | } | ||
571 | if (status < 0) | 569 | if (status < 0) |
572 | goto out; | 570 | goto out; |
573 | } while (resp->status == NLM_LCK_BLOCKED && req->a_args.block); | 571 | } |
572 | for(;;) { | ||
573 | status = nlmclnt_call(req, NLMPROC_LOCK); | ||
574 | if (status < 0) | ||
575 | goto out_unblock; | ||
576 | if (resp->status != NLM_LCK_BLOCKED) | ||
577 | break; | ||
578 | /* Wait on an NLM blocking lock */ | ||
579 | timeout = nlmclnt_block(req, NLMCLNT_POLL_TIMEOUT); | ||
580 | /* Did a reclaimer thread notify us of a server reboot? */ | ||
581 | if (resp->status == NLM_LCK_DENIED_GRACE_PERIOD) | ||
582 | continue; | ||
583 | if (resp->status != NLM_LCK_BLOCKED) | ||
584 | break; | ||
585 | if (timeout >= 0) | ||
586 | continue; | ||
587 | /* We were interrupted. Send a CANCEL request to the server | ||
588 | * and exit | ||
589 | */ | ||
590 | status = (int)timeout; | ||
591 | goto out_unblock; | ||
592 | } | ||
574 | 593 | ||
575 | if (resp->status == NLM_LCK_GRANTED) { | 594 | if (resp->status == NLM_LCK_GRANTED) { |
576 | fl->fl_u.nfs_fl.state = host->h_state; | 595 | fl->fl_u.nfs_fl.state = host->h_state; |
@@ -579,6 +598,11 @@ nlmclnt_lock(struct nlm_rqst *req, struct file_lock *fl) | |||
579 | do_vfs_lock(fl); | 598 | do_vfs_lock(fl); |
580 | } | 599 | } |
581 | status = nlm_stat_to_errno(resp->status); | 600 | status = nlm_stat_to_errno(resp->status); |
601 | out_unblock: | ||
602 | nlmclnt_finish_block(req); | ||
603 | /* Cancel the blocked request if it is still pending */ | ||
604 | if (resp->status == NLM_LCK_BLOCKED) | ||
605 | nlmclnt_cancel(host, fl); | ||
582 | out: | 606 | out: |
583 | nlmclnt_release_lockargs(req); | 607 | nlmclnt_release_lockargs(req); |
584 | return status; | 608 | return status; |