]> git.neil.brown.name Git - LaFS.git/commitdiff
Support fully async iget
authorNeilBrown <neilb@suse.de>
Fri, 9 Jul 2010 03:37:38 +0000 (13:37 +1000)
committerNeilBrown <neilb@suse.de>
Fri, 9 Jul 2010 04:36:56 +0000 (14:36 +1000)
iget can block if the inode is being initialised or freed
by a different thread.  It is not acceptable for the cleaner
to block in these cases as the other thread my need to trigger
a checkpoint.

So use special match/set functions to ensure we never
block, and use B_Async to check if we need to wake the
cleaner when done with an inode.

Signed-off-by: NeilBrown <neilb@suse.de>
inode.c
super.c

diff --git a/inode.c b/inode.c
index 8eb00f9647c7c9d36033815f65fb1fd10589bb67..f5d9893bf728de61b8dea80927c7431cc8428903 100644 (file)
--- a/inode.c
+++ b/inode.c
 #include <linux/random.h>
 #include <linux/delay.h>
 
+
+/* Supporting an async 'iget' - as required by the cleaner -
+ * is slightly non-trivial.
+ * iget*_locked will normally wait for any inode with one
+ * of the flags I_FREEING I_CLEAR I_WILL_FREE I_NEW
+ * to either be unhashed or has the flag cleared.
+ * We cannot afford that wait in the cleaner as we could deadlock.
+ * So we use iget5_locked and provide a test function that fails
+ * if it finds the inode with any of those flags set.
+ * If it does see the inode like that it clear the inum
+ * that is passed in (by reference) so that it knows to continue
+ * failing (for consistency) and so that the 'set' function
+ * we provide can know to fail the 'set'.
+ * The result of this is that if iget finds an inode it would
+ * have to wait on, the inum is cleared and NULL is returned.
+ * An unfortunate side effect is that an inode will be allocated
+ * and then destroyed to no avail.
+ * This is avoided by calling ilookup5 first.  This also allows
+ * us to only allocate/load the data block if there really seems
+ * to be a need.
+ */
+#define NO_INO (~(ino_t)0)
+static int async_itest(struct inode *inode, void *data)
+{
+       ino_t *inump = data;
+       ino_t inum = *inump;
+
+       if (inum == NO_INO)
+               /* found and is freeing */
+               return 0;
+       if (inode->i_ino != inum)
+               return 0;
+       if (inode->i_state & (I_FREEING|I_CLEAR|I_WILL_FREE|I_NEW)) {
+               *inump = NO_INO;
+               return 0;
+       }
+       return 1;
+}
+
+static int async_iset(struct inode *inode, void *data)
+{
+       ino_t *inump = data;
+       if (!*inump)
+               return -EBUSY;
+       inode->i_ino = *inump;
+       return 0;
+}
+
 struct inode *
 lafs_iget(struct super_block *sb, ino_t inum, int async)
 {
        /* find, and load if needed, this inum */
-       struct inode *ino = iget_locked(sb, inum);
-       struct datablock *b;
+       struct inode *ino = NULL;
+       struct datablock *b = NULL;
        struct inode *inodefile;
-       int err;
-
-       if (!(ino->i_state & I_NEW)) {
-               if (ino->i_mode)
-                       return ino;
-               iput(ino);
-               return ERR_PTR(-ENOENT);
-       }
+       int err = 0;
 
-       /* Need to load block 'inum' from an inode file...
-        */
+       BUG_ON(inum == NO_INO);
 
        if (sb->s_root)
                inodefile = LAFSI(sb->s_root->d_inode)->filesys;
@@ -42,11 +82,83 @@ lafs_iget(struct super_block *sb, ino_t inum, int async)
                inodefile = fs->ss[0].root;
        }
 
-       b = lafs_get_block(inodefile, inum, NULL, GFP_KERNEL, MKREF(iget));
-       if (async)
-               err = lafs_read_block_async(b);
-       else
-               err = lafs_read_block(b);
+       if (async) {
+               /* We cannot afford to block on 'freeing_inode'
+                * So use iget5_locked and refuse to match such
+                * inodes.
+                * If the inode is 'freeing', inum gets set to NO_INO.
+                * ilookup5 is used first to avoid an unnecessary
+                * alloc/free if the inode is locked in some way.
+                */
+               while (!ino) {
+                       ino_t inum2 = inum;
+                       err = 0;
+                       ino = ilookup5(sb, inum, async_itest, &inum2);
+                       if (ino)
+                               break;
+
+                       if (inum2 == NO_INO)
+                               err = -EAGAIN;
+
+                       /* For async we will always want the dblock loaded,
+                        * and we need to load it first as we cannot afford
+                        * to fail -EAGAIN once we have an I_NEW inode.
+                        */
+                       if (!b)
+                               b = lafs_get_block(inodefile, inum, NULL,
+                                                  GFP_NOFS, MKREF(iget));
+                       if (!b)
+                               return ERR_PTR(-ENOMEM);
+
+                       if (!err)
+                               err = lafs_read_block_async(b);
+
+                       if (!err) {
+                               /* Have the block, so safe to iget */
+                               inum2 = inum;
+                               ino = iget5_locked(sb, inum,
+                                                  async_itest, async_iset,
+                                                  &inum2);
+                               if (!ino) {
+                                       if (inum2 == NO_INO)
+                                               err = -EAGAIN;
+                                       else
+                                               err = -ENOMEM;
+                               }
+                       }
+                       if (err) {
+                               if (test_and_set_bit(B_Async, &b->b.flags)) {
+                                       putdref(b, MKREF(iget));
+                                       return ERR_PTR(err);
+                               }
+                               getdref(b, MKREF(async));
+                       }
+               }
+       } else
+               ino = iget_locked(sb, inum);
+
+       if (!ino) {
+               putdref(b, MKREF(iget));
+               return ERR_PTR(-ENOMEM);
+       }
+
+       if (!(ino->i_state & I_NEW)) {
+               putdref(b, MKREF(iget));
+               if (ino->i_mode)
+                       return ino;
+               iput(ino);
+               return ERR_PTR(-ENOENT);
+       }
+
+       /* Need to load block 'inum' from an inode file...
+        */
+       if (!b) {
+               b = lafs_get_block(inodefile, inum, NULL, GFP_KERNEL, MKREF(iget));
+               if (!b)
+                       err = -ENOMEM;
+               else
+                       err = lafs_read_block(b);
+       }
        if (err)
                goto err;
 
@@ -67,15 +179,21 @@ lafs_iget(struct super_block *sb, ino_t inum, int async)
                        printk("lafs_import_inode failed %d\n", err);
                goto err;
        }
-       putdref(b, MKREF(iget));
        unlock_new_inode(ino);
+out:
+       if (b && test_and_clear_bit(B_Async, &b->b.flags)) {
+               struct fs *fs =  sb->s_fs_info;
+               putdref(b, MKREF(async));
+               lafs_wake_cleaner(fs);
+       }
+       putdref(b, MKREF(iget));
        return ino;
 err:
        ino->i_nlink = 0;
        unlock_new_inode(ino);
-       putdref(b, MKREF(iget));
        iput(ino);
-       return ERR_PTR(err);
+       ino = ERR_PTR(err);
+       goto out;
 }
 
 struct inode *
diff --git a/super.c b/super.c
index 3fa87ae4d43d3c2cf8cff260cc340b2623a6da91..82fc6736c28549768cc93bf949e51d7edb216a8b 100644 (file)
--- a/super.c
+++ b/super.c
@@ -1032,6 +1032,28 @@ static int lafs_statfs(struct dentry *de, struct kstatfs *buf)
        return 0;
 }
 
+static void lafs_drop_inode(struct inode *inode)
+{
+       struct fs *fs = fs_from_inode(inode);
+       struct datablock *db = NULL;
+
+       /* This lock that we now hold on the inode could prevent
+        * the cleaner from getting the inode.  So after
+        * the complete the drop we might need to wake the cleaner.
+        */
+       
+       spin_lock(&inode->i_data.private_lock);
+       if (LAFSI(inode)->dblock)
+               db = getdref_locked(LAFSI(inode)->dblock, MKREF(drop));
+       spin_unlock(&inode->i_data.private_lock);
+
+       generic_drop_inode(inode);
+       if (db && test_bit(B_Async, &db->b.flags))
+               lafs_wake_cleaner(fs);
+       if (db)
+               putdref(db, MKREF(drop));
+}
+
 static struct super_operations lafs_sops = {
        .alloc_inode    = lafs_alloc_inode,
        .destroy_inode  = lafs_destroy_inode,  /* Inverse of 'alloc_inode' */
@@ -1039,6 +1061,7 @@ static struct super_operations lafs_sops = {
        .dirty_inode    = lafs_dirty_inode,
        .write_inode    = lafs_write_inode,
        /* put_inode ?? */
+       .drop_inode     = lafs_drop_inode,
        /* drop_inode ?? */                     /* default will call delete or forget
                                                 * where 'forget' flushes and clears
                                                 */