diff mbox

[GFS2,6/7] GFS2: Use non-blocking wait in gfs2_iget

Message ID 1464117159-17874-7-git-send-email-rpeterso@redhat.com (mailing list archive)
State New, archived
Headers show

Commit Message

Bob Peterson May 24, 2016, 7:12 p.m. UTC
From: Andreas Gruenbacher <agruenba@redhat.com>

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 <agruenba@redhat.com>
---
 fs/gfs2/inode.c | 104 ++++++++++++++++++++++++++++++++++++--------------------
 fs/gfs2/inode.h |   5 +++
 fs/inode.c      |   1 +
 3 files changed, 74 insertions(+), 36 deletions(-)
diff mbox

Patch

diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c
index 5dff5da..d1dde39 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 e00fec4..c18ace5 100644
--- a/fs/inode.c
+++ b/fs/inode.c
@@ -388,6 +388,7 @@  void __iget(struct inode *inode)
 {
 	atomic_inc(&inode->i_count);
 }
+EXPORT_SYMBOL(__iget);
 
 /*
  * get additional reference to inode; caller must already hold one.