flock() race - fix
So these patches are against the ocfs2-1.4 tree. I am sending them as-is as the bug has been filed against 1.4. Will email the patches for the mainline kernel later.
Sunil
PATCH 10/11 ocfs2/dlm: Fix race in adding/removing lockres' to/from the tracking list
This patch adds a new lock, dlm->tracking_lock, to protect adding/removing lockres' to/from the dlm->tracking_list. We were previously using dlm->spinlock for the same, but that proved inadequate as we could be freeing a lockres from a context that did not hold that lock. As the new lock only protects this list, we can explicitly take it when removing the lockres from the tracking list.
This bug was exposed when testing multiple processes concurrently flock() the same file.
--- fs/ocfs2/dlm/dlmcommon.h | 3 ++ fs/ocfs2/dlm/dlmdebug.c | 53 ++++++++++++++++++++------------------------- fs/ocfs2/dlm/dlmdomain.c | 1 + fs/ocfs2/dlm/dlmmaster.c | 10 ++++++++ 4 files changed, 38 insertions(+), 29 deletions(-)
fs/ocfs2/dlm/dlmcommon.h +3 -0
@@ -140,6 +140,7 @@ struct dlm_ctxtunsigned int purge_count;spinlock_t spinlock;spinlock_t ast_lock; +spinlock_t track_lock;char *name;u8 node_num;u32 key;
@@ -316,6 +317,8 @@ struct dlm_lock_resource* put on a list for the dlm thread to run. */ unsigned long last_used; +struct dlm_ctxt *dlm; + unsigned migration_pending:1;atomic_t asts_reserved;spinlock_t spinlock;
fs/ocfs2/dlm/dlmdebug.c +24 -29
67 lines not on this page · raw message
fs/ocfs2/dlm/dlmdomain.c +1 -0
@@ -1550,6 +1550,7 @@ static struct dlm_ctxt *dlm_alloc_ctxt(const char *domain,spin_lock_init (&dlm->spinlock);spin_lock_init (&dlm->master_lock);spin_lock_init (&dlm->ast_lock); +spin_lock_init (&dlm->track_lock);INIT_LIST_HEAD (&dlm->list);INIT_LIST_HEAD (&dlm->dirty_list);INIT_LIST_HEAD (&dlm->reco.resources);
fs/ocfs2/dlm/dlmmaster.c +10 -0
@@ -509,8 +509,10 @@ void dlm_change_lockres_owner(struct dlm_ctxt *dlm,static void dlm_lockres_release (struct kref *kref) {struct dlm_lock_resource *res; +struct dlm_ctxt *dlm; res = container_of(kref, struct dlm_lock_resource, refs); + dlm = res->dlm;/* This should not happen -- all lockres' have a name * associated with them at init time. */
@@ -519,6 +521,7 @@ static void dlm_lockres_release(struct kref *kref)mlog (0 ,"destroying lockres %.*s\n" , res->lockname.len, res->lockname.name); +spin_lock (&dlm->track_lock);if (!list_empty (&res->tracking))list_del_init (&res->tracking);else {
@@ -526,6 +529,9 @@ static void dlm_lockres_release(struct kref *kref) res->lockname.len, res->lockname.name);dlm_print_one_lock_resource (res); } +spin_unlock (&dlm->track_lock); + +dlm_put (dlm); if (!hlist_unhashed(&res->hash_node) || !list_empty(&res->granted) ||
@@ -599,6 +605,10 @@ static void dlm_init_lockres(struct dlm_ctxt *dlm, res->migration_pending =0 ; res->inflight_locks =0 ; +/* put in dlm_lockres_release */ +dlm_grab (dlm); + res->dlm = dlm; +kref_init (&res->refs);/* just for consistency */
Signature · 1 line
1.5.6.3
PATCH 11/11 ocfs2/dlm: Fix race during lockres mastery
In reply to Sunil Mushrandlm_get_lock_resource() is supposed to return a lock resource with a proper master. If multiple concurrent threads attempt to lookup the lockres for the same lockid while the lock mastery in underway, one or more threads are likely to return a lockres without a proper master.
This patch makes the threads wait in dlm_get_lock_resource() while the mastery is underway, ensuring all threads return the lockres with a proper master.
This issue is known to be limited to users using the flock() syscall. For all other fs operations, the dlmglue layer serializes the dlm op for each lockid.
Patch fixes Novell bz#425491
https://bugzilla.novell.com/show_bug.cgi?id=425491
Users encountering this bug will see flock() return EINVAL and dmesg have the following error:
ERROR: Dlm error "DLM_BADARGS" while calling dlmlock on resource <LOCKID>: bad api args
--- fs/ocfs2/dlm/dlmmaster.c | 9 ++++++++- 1 files changed, 8 insertions(+), 1 deletions(-)
fs/ocfs2/dlm/dlmmaster.c +8 -1
@@ -736,14 +736,21 @@ lookup:if (tmpres) {int dropping_ref =0 ; +spin_unlock (&dlm->spinlock); +spin_lock (&tmpres->spinlock); +/* We wait for the other thread that is mastering the resource */ +if (tmpres->owner ==DLM_LOCK_RES_OWNER_UNKNOWN ) { +__dlm_wait_on_lockres (tmpres); +BUG_ON (tmpres->owner ==DLM_LOCK_RES_OWNER_UNKNOWN ); + } +if (tmpres->owner == dlm->node_num) {BUG_ON (tmpres->state &DLM_LOCK_RES_DROPPING_REF );dlm_lockres_grab_inflight_ref (dlm, tmpres); }else if (tmpres->state &DLM_LOCK_RES_DROPPING_REF ) dropping_ref =1 ;spin_unlock (&tmpres->spinlock); -spin_unlock (&dlm->spinlock);/* wait until done messaging the master, drop our ref to allow * the lockres to be purged, start over. */
Signature · 1 line
1.5.6.3
PATCH 1/1 ocfs2: fix DLM_BADARGS error in concurrent file locking
Sunil,
Sorry for my late replying and thanks for your patient explaining.
Sunil Mushran Wrote:
Quoted 74 lines
So I have a new patch. It has debug code too. I am still testing this. Don't upload it to bz.
fs/ocfs2/dlm/dlmmaster.c +6
-0@@ -672,6 +672,12 @@ void __dlm_lockres_drop_inflight_ref(struct dlm_ctxt *dlm, {assert_spin_locked (&res->spinlock); +if (res->inflight_locks ==0 ) { +mlog (ML_ERROR ,"%s:%.*s: inflight=0\n" , + dlm->name, res->lockname.len, res->lockname.name); +__dlm_print_one_lock_resource (res); +dump_stack (); + }BUG_ON (res->inflight_locks ==0 ); res->inflight_locks--;mlog(0, "%s:%.*s: inflight--: now %u\n",fs/ocfs2/dlm/dlmmaster.c +4
-0@@ -726,14 +732,22 @@ lookup:if (tmpres) {int dropping_ref =0 ; +spin_unlock (&dlm->spinlock); +spin_lock (&tmpres->spinlock); +if (tmpres->owner ==DLM_LOCK_RES_OWNER_UNKNOWN ) { + mlog(ML_NOTICE, "%s:%.*s Another thread ismastering "
fs/ocfs2/dlm/dlmmaster.c +5
-1+ "this resource\n", dlm->name, namelen, lockid); +__dlm_wait_on_lockres (tmpres); +BUG_ON (tmpres->owner ==DLM_LOCK_RES_OWNER_UNKNOWN ); + } +if (tmpres->owner == dlm->node_num) {BUG_ON (tmpres->state &DLM_LOCK_RES_DROPPING_REF );dlm_lockres_grab_inflight_ref (dlm, tmpres); }else if (tmpres->state &DLM_LOCK_RES_DROPPING_REF ) dropping_ref =1 ;spin_unlock (&tmpres->spinlock); -spin_unlock (&dlm->spinlock);/* wait until done messaging the master, drop our ref to allow
* the lockres to be purged, start over. */Quoted 9 lines · Coly Li
This is the way I tried firstly, didn't not work well. When I tested with more than 20 forks on 1 node, user space process got segmentation fault and kernel got oops. The kernel oops was triggered on
BUG_ON(res->inflight_locks == 0)in__dlm_lockres_drop_inflight_ref()IIRC.Also,
BUG_ON(tmpres->owner == DLM_LOCK_RES_OWNER_UNKNOWN)might not always be correct IMHO. Even we return from__dlm_wait_on_lockres_flags(), there still is chance to settmpres->ownertoDLM_LOCK_RES_OWNER_UNKNOWNbeforeBUG_ONis tested (in theory), especially whendlm->spinlockis not held.Well, if you get an oops, get the stack. If you had, it would have looked like this.
(19990,0):__dlm_lockres_drop_inflight_ref:677
ERROR:
1B57953A48324D258163FE31C04C4B11:F00000000000000000801f61f38b60c: inflight=0Call Trace: .show_stack+0x68/0x1b0 (unreliable) .__dlm_lockres_drop_inflight_ref+0x98/0x184 [ocfs2_dlm] .dlmlock_master+0x41c/0x4f8 [ocfs2_dlm] .dlmlock+0x8b0/0x11a0 [ocfs2_dlm] .ocfs2_lock_create+0x174/0x3a8 [ocfs2] .ocfs2_file_lock+0x158/0x6d8 [ocfs2] .ocfs2_flock+0x18c/0x278 [ocfs2] .sys_flock+0x170/0x1d4 syscall_exit+0x0/0x40
Quoted 5 lines
kernel BUG in
__dlm_lockres_drop_inflight_refat /rpmbuild/smushran/BUILD/ocfs2-1.4.1/fs/ocfs2/dlm/dlmmaster.c:680!This tells you what the issue is. We are forgetting to take an inflight ref. The new patch moves the code up so that we take the reference.
Because I am not familiar with the code yet, I though this is an oops triggered by my first modification. Therefore, I choose to use a loop which did not trigger the oops.
From your reply, it seems kernel BUG in __dlm_lockres_drop_inflight_ref at dlmmaster.c:680 is another bug ? I saw a patch named "ocfs2/dlm: Fix race in adding/removing lockres' to/from the tracking list", is it the fix for this bug ? If yes, I should learn how you resolve it ;)
Quoted 5 lines
Secondly,
dlm->spinlockdoes not protectres->owner. Furthermore, thatBUG_ONis important because that is the case. If mastery is underway, the state will remainIN_PROGRESS. I am curious as to why you feel otherwise.
Here is how I thought, please comments on my mistake,
The dlm associated with lockres is projected by dlm->spinlock, if we only protect lockres by lockres->spinlock, there *might* be possibility to modify dlm->node_num somewhere. Since we have quite a few places to compare lockres->owner with dlm->node_num, I suspect that manipulating on lockres->owner without protecting dlm->owner might be problematic.
I might be wrong, and happy to see your correction :)
Quoted 17 lines
Lastly, the perl script can be improved by enclosing the 20 fork loop into another loop that changes the filename. To startwith, touch 5000 files (test.flock%d). umount and mount again. This will drop the caches. Now before running any ls, run flock.pl. It will spawn 20 processes to
flock()each file and wait till the 20 forks have exited before letting loose another
20 for the next file. (Makesure you have another node or two or more that have
just mounted that volume.)I ran this with 5000 files successfully. I did encounter a problem in the last
run, not where I expected it. Still a work in progress. I can't email you the
perl script as my box will remain in the debugger for the night. ;)
Quoted 5 lines
My problem with your patch is not that it is wrong, but it is punting the issue. Say we do have a case in which the
res->ownerremains unknown eventhough
the state in notin_prog. In that case, the process will keep looping. By oopsing, we catch the problem upfront.
Thanks for the comments, I do agree with you. Your comments help me to understand more to the code :-)
Signature · 2 lines
Coly Li SuSE PRC Labs
PATCH 1/1 ocfs2: fix DLM_BADARGS error in concurrent file locking
Quoted 6 lines · Coly Li
Because I am not familiar with the code yet, I though this is an oops triggered by my first modification. Therefore, I choose to use a loop which did not trigger the oops.
From your reply, it seems kernel BUG in
__dlm_lockres_drop_inflight_refat dlmmaster.c:680 is another bug ? I saw a patch named "ocfs2/dlm: Fix race in adding/removing lockres' to/from the tracking list", is it the fix for this bug ? If yes, I should learn how you resolve it ;)
The oops in __dlm_lockres_drop_inflight_ref() is different that the tracking list oops. No relationship.
The inflight_ref oops is because the "fix" was not taking the ref. Hence it was zero during the drop. And that was because the "patch fix" was at the wrong location. See the diff between my first patch and the final one and see where the inflight ref is taken.
The tracking list bug has always been there. It was exposed during forked flock() testing as explained in the patch.
Quoted 5 lines
Here is how I thought, please comments on my mistake,
The dlm associated with lockres is projected bydlm->spinlock, if we only protect lockres bylockres->spinlock, there *might* be possibility to modifydlm->node_numsomewhere. Since we have quite a few places to comparelockres->ownerwithdlm->node_num, I suspect that manipulating onlockres->ownerwithout protectingdlm->ownermight be problematic.
dlm->node_num can never be modified. It is the node number which is fixed for the life of the dlm domain (and more).
Sunil
PATCH 1/1 ocfs2: fix DLM_BADARGS error in concurrent file locking
Sunil Mushran Wrote:
Quoted 13 lines
Quoted 11 lines · Coly Li
Because I am not familiar with the code yet, I though this is an oops triggered by my first
modification. Therefore, I choose to use a loop which did not trigger the oops.From your reply, it seems kernel BUG in
__dlm_lockres_drop_inflight_refat dlmmaster.c:680 is another bug ? I saw a patch named "ocfs2/dlm: Fix race in adding/removing lockres' to/from the
tracking list", is it the fix for this bug ? If yes, I should learn how you resolve it ;)
Quoted 8 lines
The oops in
__dlm_lockres_drop_inflight_ref()is different that the tracking list oops. No relationship.The
inflight_refoops is because the "fix" was not taking the ref. Hence it was zero during the drop. And that was because the "patch fix" was at the wrong location. See the diff between my first patch and the final one and see where the inflight ref is taken.
Aha, Understand.
The previous patch did not grab an inflight_ref after waiting on __dlm_wait_on_lockres_flags(). A lockres with inflight_ref (=0) is possible returned.
Yes, this was what I did not understand before. Perfect point :)
Quoted 12 lines
The tracking list bug has always been there. It was exposed during forked
flock()testing as explained in the patch.Quoted 8 lines
Here is how I thought, please comments on my mistake,
The dlm associated with lockres is projected bydlm->spinlock, if we only protect lockres bylockres->spinlock, there *might* be possibility to modifydlm->node_numsomewhere. Since we have
quite a few places to comparelockres->ownerwithdlm->node_num, I suspect that manipulating onlockres->ownerwithout protectingdlm->ownermight be problematic.
Quoted 2 lines
dlm->node_numcan never be modified. It is the node number which is fixed for the life of the dlm domain (and more).
Got it, and remember it :-)
Thanks for your patient and accurate explaining again :-)
Signature · 2 lines
Coly Li SuSE PRC Labs