Change gfs2_iget to use non-blocking lookups internally.  This will be
used to prevent glock deadlocks.

(Requires exporting __iget from fs/inode.c.)

Signed-off-by: Andreas Gruenbacher <agrue...@redhat.com>
---
 fs/gfs2/inode.c | 104 ++++++++++++++++++++++++++++++++++++--------------------
 fs/gfs2/inode.h |   5 +++
 fs/inode.c      |   1 +
 3 files changed, 74 insertions(+), 36 deletions(-)

diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c
index 3440219..1fa4799 100644
--- a/fs/gfs2/inode.c
+++ b/fs/gfs2/inode.c
@@ -20,6 +20,7 @@
 #include <linux/fiemap.h>
 #include <linux/security.h>
 #include <asm/uaccess.h>
+#include <linux/writeback.h>
 
 #include "gfs2.h"
 #include "incore.h"
@@ -37,61 +38,92 @@
 #include "super.h"
 #include "glops.h"
 
-struct gfs2_skip_data {
+struct gfs2_match {
        u64 no_addr;
-       int skipped;
-       int non_block;
+       struct gfs2_freeing_inode *freeing;
 };
 
-static int iget_test(struct inode *inode, void *opaque)
+/* gfs2_match_inode - Inode matching function
+ * @inode: inode pointer
+ * @l: hash value (unused, since it may not be able to hold our no_addr)
+ * @opaque: points to a gfs2_freeing_inode structure
+ */
+static int gfs2_match_inode(struct inode *inode, unsigned long l, void *opaque)
 {
-       struct gfs2_inode *ip = GFS2_I(inode);
-       struct gfs2_skip_data *data = opaque;
+       struct gfs2_match *match = opaque;
+       struct gfs2_freeing_inode *freeing = match->freeing;
 
-       if (ip->i_no_addr == data->no_addr) {
-               if (data->non_block &&
-                   inode->i_state & (I_FREEING|I_CLEAR|I_WILL_FREE)) {
-                       data->skipped = 1;
-                       return 0;
-               }
-               return 1;
+       if (GFS2_I(inode)->i_no_addr != match->no_addr)
+               return 0;
+
+       spin_lock(&inode->i_lock);
+       if (inode->i_state & (I_FREEING|I_WILL_FREE)) {
+               freeing->wq = prepare_wait_on_freeing_inode(inode,
+                       &freeing->bit_wait);
+               spin_unlock(&inode->i_lock);
+               return -1;
        }
-       return 0;
+       __iget(inode);
+       spin_unlock(&inode->i_lock);
+       return 1;
 }
 
-static int iget_set(struct inode *inode, void *opaque)
+static int gfs2_test_inode(struct inode *inode, void *opaque)
 {
-       struct gfs2_inode *ip = GFS2_I(inode);
-       struct gfs2_skip_data *data = opaque;
+       struct gfs2_match *match = opaque;
 
-       if (data->skipped)
-               return -ENOENT;
-       inode->i_ino = (unsigned long)(data->no_addr);
-       ip->i_no_addr = data->no_addr;
-       return 0;
+       return GFS2_I(inode)->i_no_addr == match->no_addr;
 }
 
 struct inode *gfs2_ilookup(struct super_block *sb, u64 no_addr)
 {
-       unsigned long hash = (unsigned long)no_addr;
-       struct gfs2_skip_data data;
+       struct gfs2_match match = {
+               .no_addr = no_addr,
+       };
 
-       data.no_addr = no_addr;
-       data.skipped = 0;
-       data.non_block = 0;
-       return ilookup5(sb, hash, iget_test, &data);
+       return ilookup5(sb, no_addr, gfs2_test_inode, &match);
 }
 
 static struct inode *gfs2_iget(struct super_block *sb, u64 no_addr,
                               int non_block)
 {
-       struct gfs2_skip_data data;
-       unsigned long hash = (unsigned long)no_addr;
+       struct gfs2_freeing_inode freeing;
+       struct gfs2_match match = {
+               .no_addr = no_addr,
+               .freeing = &freeing,
+       };
+       struct inode *inode;
 
-       data.no_addr = no_addr;
-       data.skipped = 0;
-       data.non_block = non_block;
-       return iget5_locked(sb, hash, iget_test, iget_set, &data);
+       while (1) {
+               freeing.wq = NULL;
+               inode = find_inode_nowait(sb, no_addr,
+                                         gfs2_match_inode, &match);
+               if (inode) {
+                       wait_on_inode(inode);
+                       return inode;
+               }
+               if (freeing.wq) {
+                       if (non_block) {
+                               finish_wait(freeing.wq, &freeing.bit_wait.wait);
+                               return ERR_PTR(-EAGAIN);
+                       }
+                       schedule();
+                       finish_wait(freeing.wq, &freeing.bit_wait.wait);
+                       continue;
+               }
+
+               inode = new_inode(sb);
+               if (!inode)
+                       return ERR_PTR(-ENOMEM);
+               inode->i_ino = no_addr;
+               GFS2_I(inode)->i_no_addr = no_addr;
+               if (insert_inode_locked4(inode, no_addr,
+                                        gfs2_test_inode, &match) < 0) {
+                       iput(inode);
+                       continue;
+               }
+               return inode;
+       }
 }
 
 /**
@@ -146,8 +178,8 @@ struct inode *gfs2_inode_lookup(struct super_block *sb, 
unsigned int type,
        int error;
 
        inode = gfs2_iget(sb, no_addr, non_block);
-       if (!inode)
-               return ERR_PTR(-ENOMEM);
+       if (IS_ERR(inode))
+               return inode;
        ip = GFS2_I(inode);
 
        if (inode->i_state & I_NEW) {
diff --git a/fs/gfs2/inode.h b/fs/gfs2/inode.h
index 22c27a8..4863513 100644
--- a/fs/gfs2/inode.h
+++ b/fs/gfs2/inode.h
@@ -93,6 +93,11 @@ err:
        return -EIO;
 }
 
+struct gfs2_freeing_inode {
+       wait_queue_head_t *wq;
+       struct wait_bit_queue bit_wait;
+};
+
 extern struct inode *gfs2_inode_lookup(struct super_block *sb, unsigned type, 
                                       u64 no_addr, u64 no_formal_ino,
                                       int non_block);
diff --git a/fs/inode.c b/fs/inode.c
index 2979a7f..2cb18bd 100644
--- a/fs/inode.c
+++ b/fs/inode.c
@@ -387,6 +387,7 @@ void __iget(struct inode *inode)
 {
        atomic_inc(&inode->i_count);
 }
+EXPORT_SYMBOL(__iget);
 
 /*
  * get additional reference to inode; caller must already hold one.
-- 
2.5.5

Reply via email to