From 81d38963fcfc32f2202b8b3a6bf4a37a90165f46 Mon Sep 17 00:00:00 2001 From: NeilBrown Date: Fri, 9 Jul 2010 13:37:38 +1000 Subject: [PATCH] Support fully async iget 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 --- inode.c | 158 +++++++++++++++++++++++++++++++++++++++++++++++++------- super.c | 23 +++++++++ 2 files changed, 161 insertions(+), 20 deletions(-) diff --git a/inode.c b/inode.c index 8eb00f9..f5d9893 100644 --- a/inode.c +++ b/inode.c @@ -13,24 +13,64 @@ #include #include + +/* 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 3fa87ae..82fc673 100644 --- 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 */ -- 2.39.5