Skip to contentSkip to conversation

ocfs2: fix DLM_BADARGS error in concurrent file locking

PATCH New Coly Li 3 participants 7 messages Dec 2, 2008 – Dec 11, 2008 1 message not in the archive OCFS2

lore
Share & tools
lore

Coly Li

author
#0 lore raw

PATCH 1/1 ocfs2: fix DLM_BADARGS error in concurrent file locking

When calling flock(2) by multiple processes concurrently, it is reported to be very easy resulting DLM_BADARGS error. In dmesg the similar lines can be found:

Sep  2 11:17:32 acct20 kernel: (6935,1):ocfs2_lock_create:901 ERROR: Dlm error "DLM_BADARGS" while
calling dlmlock on resource
F00000000000000005d1cfe83ab8dad: bad api args
Sep  2 11:17:32 acct20 kernel: (6935,1):ocfs2_file_lock:1486 ERROR: status = -22

Here is the analysis how this error comes, in condition that 2 process concurrently call flock(2) on same file:

1) call sequence of process 1 is (first listed first called):
[ocfs2_do_flock] [ocfs2_file_lock] [ocfs2_lock_create] [dlmlock] [dlm_get_lock_resource]
[dlm_do_master_request] [o2net_send_message_vec]
In o2net_send_message_vec(), at line 1049:

        wait_event(nsw.ns_wq, o2net_nsw_completed(nn, &nsw));

Here process 1 is scheduled out, and process 2 is scheduled in to ran. At this moment, res->owner (ownership of the locking file resource) is set to
DLM_LOCK_RES_OWNER_UNKNOWN.

2) call sequence of process 2 is (first listed first called):
[ocfs2_do_flock] [ocfs2_file_lock] [ocfs2_lock_create] [dlmlock] [dlmlock_remote]
[o2net_send_message_vec]
in dlmlock(), at line 725:

                if (res->owner == dlm->node_num)
                        status = dlmlock_master(dlm, res, lock, flags);
                else
                        status = dlmlock_remote(dlm, res, lock, flags);
    Since res->owner is still set to DLM_LOCK_RES_OWNER_UNKNOWN and not decided in process 1 yet,
process 2 calls dlmlock_remote() here. But inside dlmlock_remote(), at line 247:
        __dlm_wait_on_lockres(res);
    After this function returned, res->owner is set correctly to 0 (node 0). Then at line 257:
        status = dlm_send_remote_lock_request(dlm, res, lock, flags);
    dlm_send_remote_lock_request() calls o2net_send_message() at line 330, as bellowed:
        tmpret = o2net_send_message(DLM_CREATE_LOCK_MSG, dlm->key, &create,
                                    sizeof(create), res->owner, &status);
    o2net_send_message() calls o2net_send_message_vec() and at line 987, the code is:
        if (target_node == o2nm_this_node()) {
                ret = -ELOOP;
                goto out;
        }

Here target_node is res->owner which is already set to 0. But before calling dlmlock_remote(),
res->owner is not set properly (still is DLM_LOCK_RES_OWNER_UNKNOWN). After entering dlmlock_remote(), and returning from __dlm_wait_on_lockres(), though res->owner is correct, the execution flow is in mistaken location.

3) The result is, process 1 can make the flock, and process 2 generates BADARGS error. 20 processes condition also got tested, only first process can success the flock, and other processes all got BADARGS.

With Sunil and Mark's suggestion, the solution is,
1) If a tmpres returned from hashed resources, check its owner.
2) if tmpres->owner is DLM_LOCK_RES_OWNER_UNKNOWN, it means another process owns the resource but dose not finish its initiation. We should wait on DLM_LOCK_RES_IN_PROGRESS until the initiation accomplished.
3) Once the initiation done, re-lookup the resource.

After basic testing on 2 nodes (each node forks 200,000 processes calling flock(2)) and 8 nodes (each node forks 1000 processes calling flock(2)), it works fine so far.

Cc Sunil Mushran <[email protected]>
Cc Mark Fasheh <[email protected]>
Cc Jeff Mahoney <[email protected]>
---
 fs/ocfs2/dlm/dlmmaster.c |   13 +++++++++++++
 1 files changed, 13 insertions(+), 0 deletions(-)
fs/ocfs2/dlm/dlmmaster.c +13 -0
index 44f87ca..c777844 100644
@@ -742,6 +742,19 @@ lookup:
 			goto lookup;
 		}
 
+		/* tmpres might be initiating by another process, in this case
+		 * we should wait until the initiation done  */
+		spin_lock(&tmpres->spinlock);
+		if (tmpres->owner == DLM_LOCK_RES_OWNER_UNKNOWN ||
+		    (tmpres->state & DLM_LOCK_RES_IN_PROGRESS)) {
+			__dlm_wait_on_lockres_flags(tmpres, DLM_LOCK_RES_IN_PROGRESS);
+			spin_unlock(&tmpres->spinlock);
+			dlm_lockres_put(tmpres);
+			tmpres = NULL;
+			goto lookup;
+		}
+		spin_unlock(&tmpres->spinlock);
+
 		mlog(0, "found in hash!\n");
 		if (res)
 			dlm_lockres_put(res);
Signature · 2 lines
Coly Li
SuSE PRC Labs
#1 lore raw

PATCH 1/1 ocfs2: fix DLM_BADARGS error in concurrent file locking

Quoted 14 lines · Coly Li
fs/ocfs2/dlm/dlmmaster.c +6 -0
index 44f87ca..c777844 100644
@@ -742,6 +742,19 @@ lookup:
 			goto lookup;
 		}
 
+		/* tmpres might be initiating by another process, in this case
+		 * we should wait until the initiation done  */
+		spin_lock(&tmpres->spinlock);
+		if (tmpres->owner == DLM_LOCK_RES_OWNER_UNKNOWN ||
+		    (tmpres->state & DLM_LOCK_RES_IN_PROGRESS)) {
+			__dlm_wait_on_lockres_flags(tmpres, DLM_LOCK_RES_IN_PROGRESS);

fs/ocfs2/dlm/dlmmaster.c lookup: · line 760

Quoted 10 lines
fs/ocfs2/dlm/dlmmaster.c +7 -0
+			spin_unlock(&tmpres->spinlock);
+			dlm_lockres_put(tmpres);
+			tmpres = NULL;
+			goto lookup;
+		}
+		spin_unlock(&tmpres->spinlock);
+
 		mlog(0, "found in hash!\n");
 		if (res)
 			dlm_lockres_put(res);

This will work but we are wasting cycles as we are looking up the same lockres again. How about the following? The mlog is for testing.

fs/ocfs2/dlm/dlmmaster.c +1 -0
@@ -742,6 +742,17 @@ lookup:
                        goto lookup;
                }
 
+               /* wait if lock resource is being mastered by another 

thread */
+ spin_lock(&tmpres->spinlock);
+ if (tmpres->owner == DLM_LOCK_RES_OWNER_UNKNOWN) {
+ mlog(ML_NOTICE, "%s:%.*s Another thread is
mastering "
+ "this resource\n", dlm->name, namelen, lockid);
+ __dlm_wait_on_lockres_flags(tmpres,
+
DLM_LOCK_RES_IN_PROGRESS);
+ BUG_ON(tmpres->owner == DLM_LOCK_RES_OWNER_UNKNOWN);
+ }
+ spin_unlock(&tmpres->spinlock);
+

                mlog(0, "found in hash!\n");
                if (res)
                        dlm_lockres_put(res);

Secondly, the patch comments are more than required. We should prune it down a bit. Like:

=========================================================================
ocfs2/dlm: Fix lockres mastery race

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, one or more threads are likely to return a lockres without a proper master, if the lock mastery is underway.

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 limited to users using the flock() syscall. For all other fs operations, dlmglue ensures that only one thread is performing dlm operations
for a given lockid at any one time.

Signed-off-by: Coly Li <...>
=========================================================================

Corrections welcome.

Lastly, test the patch not only with the flock() test but also otherwise. Say, with recovery. Part of fixing bugs is ensuring the fix does not break existing functionality. Run all multinode tests in ocfs2-test.

Thanks
Sunil

author
#2 lore raw

PATCH 1/1 ocfs2: fix DLM_BADARGS error in concurrent file locking

Sunil Mushran Wrote:

Quoted 49 lines
Quoted 24 lines · Coly Li
fs/ocfs2/dlm/dlmmaster.c +13 -0
index 44f87ca..c777844 100644
@@ -742,6 +742,19 @@ lookup:
             goto lookup;
         }
 
+        /* tmpres might be initiating by another process, in this case
+         * we should wait until the initiation done  */
+        spin_lock(&tmpres->spinlock);
+        if (tmpres->owner == DLM_LOCK_RES_OWNER_UNKNOWN ||
+            (tmpres->state & DLM_LOCK_RES_IN_PROGRESS)) {
+            __dlm_wait_on_lockres_flags(tmpres,
 DLM_LOCK_RES_IN_PROGRESS);
+            spin_unlock(&tmpres->spinlock);
+            dlm_lockres_put(tmpres);
+            tmpres = NULL;
+            goto lookup;
+        }
+        spin_unlock(&tmpres->spinlock);
+
         mlog(0, "found in hash!\n");
         if (res)
Quoted 1 line
             dlm_lockres_put(res);

This will work but we are wasting cycles as we are looking up the same lockres again. How about the following? The mlog is for testing.

fs/ocfs2/dlm/dlmmaster.c +1 -0
@@ -742,6 +742,17 @@ lookup:
                       goto lookup;
               }
 
+               /* wait if lock resource is being mastered by another

thread */

fs/ocfs2/dlm/dlmmaster.c +3 -0
+               spin_lock(&tmpres->spinlock);
+               if (tmpres->owner == DLM_LOCK_RES_OWNER_UNKNOWN) {
+                       mlog(ML_NOTICE, "%s:%.*s Another thread is

mastering "
+ "this resource\n", dlm->name, namelen,
lockid);

fs/ocfs2/dlm/dlmmaster.c +2 -0
+                       __dlm_wait_on_lockres_flags(tmpres,
+                                                  

DLM_LOCK_RES_IN_PROGRESS);
+ BUG_ON(tmpres->owner ==
DLM_LOCK_RES_OWNER_UNKNOWN);

fs/ocfs2/dlm/dlmmaster.c

Quoted 2 lines
fs/ocfs2/dlm/dlmmaster.c +2 -0
+               }
+               spin_unlock(&tmpres->spinlock);

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.

Quoted 10 lines

+

               mlog(0, "found in hash!\n");
               if (res)
                       dlm_lockres_put(res);

Secondly, the patch comments are more than required. We should prune it down a bit. Like:

=========================================================================
ocfs2/dlm: Fix lockres mastery race

Cool. Since I was not sure this was a race error, you can see, I did not mention race anywhere. I like this title more ;-)

Quoted 16 lines

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, one or more threads are likely to return a lockres without a proper master, if the lock mastery is underway.

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 limited to users using the flock() syscall. For all other fs operations, dlmglue ensures that only one thread is performing dlm operations
for a given lockid at any one time.

Quoted 4 lines

Signed-off-by: Coly Li <...>
=========================================================================

Corrections welcome.

The comment is perfect IMHO.

Quoted 4 lines

Lastly, test the patch not only with the flock() test but also otherwise. Say, with recovery. Part of fixing bugs is ensuring the fix does not break existing functionality. Run all multinode tests in ocfs2-test.

Sure, thanks for the suggestion. I will test the patch will all multinode tests, and submit a patch to ocfs2 test-suite for this fix.

Signature · 2 lines
Coly Li
SuSE PRC Labs
#3 lore raw

PATCH 1/1 ocfs2: fix DLM_BADARGS error in concurrent file locking

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 +0 -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",
@@ -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 "
+ "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

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.

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.

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

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.

Sunil

author
#4 lore raw

PATCH 1/1 ocfs2: fix DLM_BADARGS error in concurrent file locking

In reply to Sunil Mushran

Sunil Mushran Wrote:
[snip]

Quoted 5 lines

Corrections welcome.

Lastly, test the patch not only with the flock() test but also otherwise. Say, with recovery. Part of fixing bugs is ensuring the fix does not break existing functionality. Run all multinode tests in ocfs2-test.

On a 8 nodes cluster setup by xen, I tested these test cases,
- run_buildkernel.py
- cross_delete.py
- dlmstress1.sh
- run_extend_and_write.py
- run_lvb_torture.py
- mmap_test
- test_netfail.py

Not tested cases are run_create_racer.py and open_delete.py, cause I encountered some error when setup them.

When I ran dlmsress1.sh and test_netfail.py,
- Kernel BUG at fs/ocfs2/dlm/dlmast.c:334 was triggered 3 times.
- Kernel BUG at fs/ocfs2/dlm/dlmmaster.c:1694 was triggered once.
Right now, there is not obvious clue they are directly related to this patch. They might stay there already, I will do more testing to confirm.

Thanks.

Signature · 2 lines
Coly Li
SuSE PRC Labs
#5 lore raw

PATCH 1/1 ocfs2: fix DLM_BADARGS error in concurrent file locking

Quoted 18 lines · Coly Li

Sunil Mushran Wrote:
[snip]

Quoted 5 lines

Corrections welcome.

Lastly, test the patch not only with the flock() test but also otherwise. Say, with recovery. Part of fixing bugs is ensuring the fix does not break existing functionality. Run all multinode tests in ocfs2-test.

On a 8 nodes cluster setup by xen, I tested these test cases,
- run_buildkernel.py
- cross_delete.py
- dlmstress1.sh
- run_extend_and_write.py
- run_lvb_torture.py
- mmap_test
- test_netfail.py

Quoted 2 lines

Not tested cases are run_create_racer.py and open_delete.py, cause I encountered some error when setup them.

Hi Coly,

How did you setup the env for testing? are you using the latest testsuite from ocfs2-test.git? As for the cluster testing, we need to install openmpi first, and setup passwordless ssh&rsh connection among all nodes.

Besides, all testing binaries need to be run from a installed BINDIR after a successful building&installation.

Regards,
Tristan

Quoted 8 lines

When I ran dlmsress1.sh and test_netfail.py,
- Kernel BUG at fs/ocfs2/dlm/dlmast.c:334 was triggered 3 times.
- Kernel BUG at fs/ocfs2/dlm/dlmmaster.c:1694 was triggered once.
Right now, there is not obvious clue they are directly related to this patch. They might stay there already, I will do more testing to confirm.

Thanks.

A message that is not in the archive (sent off-list or not yet received): [email protected]

#7 lore raw

PATCH 1/1 ocfs2: fix DLM_BADARGS error in concurrent file locking

Quoted 22 lines · Coly Li

tristan.ye Wrote:

Quoted 20 lines · Coly Li
Quoted 19 lines

Sunil Mushran Wrote:
[snip]

Quoted 5 lines

Corrections welcome.

Lastly, test the patch not only with the flock() test but also otherwise. Say, with recovery. Part of fixing bugs is ensuring the fix does not break existing functionality. Run all multinode tests in ocfs2-test.

On a 8 nodes cluster setup by xen, I tested these test cases,
- run_buildkernel.py
- cross_delete.py
- dlmstress1.sh
- run_extend_and_write.py
- run_lvb_torture.py
- mmap_test
- test_netfail.py

Not tested cases are run_create_racer.py and open_delete.py, cause I encountered some error when setup them.

Quoted 7 lines
Quoted 4 lines

Hi Coly,

How did you setup the env for testing? are you using the latest testsuite from ocfs2-test.git? As for the cluster testing, we need to

I download the code by svn, here is my command,
svn co http://oss.oracle.com/projects/ocfs2-test/src ocfs2-test Is it out of date ?

Actually we've moved to git repository for ocfs2-test src management, so you'd better turn to our git repo for pulling latest test sources. Such as,

1. git clone git://oss.oracle.com/git/ocfs2-test ocfs2-test

2. ./autogen.sh

3. make && make install DESTDIR=/your/dest/path

All testing binaries should be fairly ready for test if you've already configed your openmpi and ssh&rsh connection well.

Just feel free to let Marcos and me know if you're being hindered by any problem during the testing:)

Regards,
Tristan

Quoted 13 lines
Quoted 4 lines

install openmpi first, and setup passwordless ssh&rsh connection among all nodes.

Yeah, I did that. There is very helpful document on ocfs2 wiki, thanks to the author :)

Quoted 2 lines

Besides, all testing binaries need to be run from a installed BINDIR after a successful building&installation.

When I tried run_create_racer.py and open_delete.py, maybe I didn't input correct/proper parameters to the scripts, I will start another thread to ask for help :)
[snip]

Thanks for your feedback. Since I switched my email address, my reply comes late ...