Skip to contentSkip to conversation

flock() race - fix

Ocfs2-devel New Sunil Mushran 2 participants 6 messages Dec 9, 2008 – Dec 11, 2008 OCFS2

lore
Share & tools
lore
#0 lore raw

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

#1 lore raw

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
index 9add88d..a7ecccc 100644
@@ -140,6 +140,7 @@ struct dlm_ctxt
 	unsigned 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
index 2eff4a4..c2e192e 100644
@@ -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
index a575392..4d493f4 100644
@@ -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
#2 lore raw

PATCH 11/11 ocfs2/dlm: Fix race during lockres mastery

In reply to Sunil Mushran

dlm_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
index 4d493f4..6bda3ab 100644
@@ -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
#3 lore raw

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 is

mastering "

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 set tmpres->owner to DLM_LOCK_RES_OWNER_UNKNOWN before BUG_ON is tested (in theory), especially when dlm->spinlock is 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=0

Call 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_ref at /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->spinlock does not protect res->owner. Furthermore, that BUG_ON is important because that is the case. If mastery is underway, the state will remain IN_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->owner remains unknown eventhough
the state in not in_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
#4 lore raw

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_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 ;)

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 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.

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

#5 lore raw

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_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 8 lines

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.

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 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.

Quoted 2 lines

dlm->node_num can 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