On 06/06/2014 12:11 AM, Anand Avati wrote:

On Thu, Jun 5, 2014 at 10:46 AM, Krutika Dhananjay <kdhan...@redhat.com <mailto:kdhan...@redhat.com>> wrote:

        To summarize, the real "problems" are:

        - Deref of pl_inode->refkeeper as an inode_t in the cleanup
        logger. It
        is an internal artifact of pl_update_refkeeper() working and
        nobody else
        is supposed to "touch" it.

    For this, the solution I have in mind is to
    a. have a placeholder for gfid in pl_inode_t object,
    b. remember the gfid of the inode at the time of pl_inode_t
    creation in pl_ctx_get(), and
    c. print pl_inode->gfid in the log message in
    pl_{inodelk,entrylk}_log_cleanup().


OK.

        - Missing call to pl_update_refkeeper() in
        client_disconnect_cbk(). This
        can result in a potential leak of inode ref (not a leak if the
        same
        inode gets any locking activity by another client in the future).

    As far as updates to refkeeper is concerned during DISCONNECT is
    concerned, Pranith and I did have discussions at length but could
    not find a single safe place in cleanup functions to call
    pl_update_refkeeper() that does not lead to illegal memory access,
    whether before or after the call to __pl_inodelk_unref() in the
    third for loop.

    Case #1 explained:
    =============

    <snip>
    list_for_each_entry_safe() {
    ...
    ...
    pl_update_refkeeper(inode);
    pthread_mutex_lock (&pl_inode->mutex);
         __pl_inodelk_unref(l);
    pthread_mutex_unlock (&pl_inode->mutex);
    </snip>

    This causes the last unref in pl_update_refkeeper() to implicitly
    call pl_forget() where pl_inode_t object is destroyed while it is
    still needed in terms of having to obtain pl_inode->mutex before
    unrefing the lock object.

    Case 2 explained:
    =============
    <snip>
    list_for_each_entry_safe() {
    ...
    ...
    pthread_mutex_lock (&pl_inode->mutex);
         __pl_inodelk_unref(l);
    pthread_mutex_unlock (&pl_inode->mutex);
    pl_update_refkeeper(inode);
    </snip>

    After the first for loop is processed, the domain's lists could
    have been emptied. And at this point, there could well come a
    second thread that could update refkeeper,
    triggering last unref on the inode leading to a call to
    pl_forget() (where pl_inode_t is freed), after which the
    DISCONNECT thread would be trying to perform illegal
    memory access in its call to pl_update_refkeeper() during its turn.

    So the solution Pranith and I came up with involves making sure
    that the inode_t object is alive for as long as there is atleast
    one lock on it:

    1. refkeeper will not be used for inodelks and entrylks (but will
    continue to be used in fcntl locks).
    2. Each pl_inode_t object will also host an inode_t member which
    is initialised during a call to pl_inode_get() in
    pl_common_{inodelk, entrylks}().
    3. Everytime a lock is granted/blocked, pl_inode->inode is ref'd
    (in pl_inode_setlk() after successful locking.
    4. Everytime a lock is unlocked, pl_inode->inode is unref'd.
    5. In disconnect codepath, as part of "released" list processing,
    the inodes associated with the client are unref'd in the same loop
    right after every lock object is unref'd.

    With all this, there is one problem with the lock object that is
    found to be in both blocked and granted list in the transient
    state, when there's a race between the unlocking thread
    and the disconnecting thread. This can be best explained with the
    following example:
    <example>
    Consider an inode I on which there's a granted lock G and a
    blocked lock B, from clients C1 and C2 respectively. With this
    approach, at this point the number of refs taken
    by the locks xlator on I would be 2.
    C1 unlocks B, at which point I's refcount becomes 1.
    Now as part of granting other blocked locks in unlock codepath, B
    is put in granted list.
    Now B's client C2 sends a DISCONNECT event, which would cause I to
    be unref'd again. This being the last unref, pl_forget() is called
    on I causing its pl_inode to be destroyed
    At this point, the unlocking thread could try to do a mutex lock
    on pl_inode->mutex in grant_blocked_{inode,entry}_locks() (whereas
    pl_inode is now already destroyed) leading to a crash.
    </example>

    The problem described in the example can be fixed by making sure
    grant_blocked_{inode,entry}_locks() refs the inode first thing and
    then unrefs it just before returning.
    This would fix illegal memory access.


This sounds a bit complicated. I think there is a much simpler solution:

- First, make update_refkeeper() check for blocked locks (which I mentioned as "optional" previously)

- Make grant_blocked_locks() double up and do the job of update_refkeeper() internally.

Something which looks like this:

diff --git a/xlators/features/locks/src/common.c b/xlators/features/locks/src/common.c
index f6c71c1..38df385 100644
--- a/xlators/features/locks/src/common.c
+++ b/xlators/features/locks/src/common.c
@@ -126,8 +126,14 @@ __pl_inode_is_empty (pl_inode_t *pl_inode)
                 if (!list_empty (&dom->entrylk_list))
                         is_empty = 0;
+                if (!list_empty (&dom->blocked_entrylks))
+                        is_empty = 0;
+
                 if (!list_empty (&dom->inodelk_list))
                         is_empty = 0;
+
+                if (!list_empty (&dom->blocked_inodelks))
+                        is_empty = 0;
         }
         return is_empty;
@@ -944,12 +950,18 @@ grant_blocked_locks (xlator_t *this, pl_inode_t *pl_inode)
         struct list_head granted_list;
         posix_lock_t     *tmp = NULL;
         posix_lock_t     *lock = NULL;
+       inode_t *unref = NULL;
         INIT_LIST_HEAD (&granted_list);
         pthread_mutex_lock (&pl_inode->mutex);
         {
                 __grant_blocked_locks (this, pl_inode, &granted_list);
+
+ if (__pl_inode_is_empty (pl_inode) && pl_inode->refkeeper) {
+                       unref = pl_inode->refkeeper;
+                       pl_inode->refkeeper = NULL;
+               }
         }
         pthread_mutex_unlock (&pl_inode->mutex);
@@ -965,6 +977,9 @@ grant_blocked_locks (xlator_t *this, pl_inode_t *pl_inode)
                 GF_FREE (lock);
         }
+       if (unref)
+               inode_unref (unref);
+
         return;
 }


This should make pl_disconnect_cbk() pretty much race free w.r.t refkpeer. Thoughts?
Lets say C1 is doing pl_inodelk_client_cleanup. After the second for-loop(All granted and blocked locks are out of the domain) if an unlock on the final granted lock on that inode from client C2 completes, refkeeper would be set to NULL and unrefed leading to zero refs on that inode i.e. pl_forget will also happen. In 3rd for-loop pl_inode is already freed and leads to free'd memory access and will crash.

Pranith



_______________________________________________
Gluster-devel mailing list
Gluster-devel@gluster.org
http://supercolony.gluster.org/mailman/listinfo/gluster-devel

_______________________________________________
Gluster-devel mailing list
Gluster-devel@gluster.org
http://supercolony.gluster.org/mailman/listinfo/gluster-devel

Reply via email to