Patchwork [21/29] ext4: Split extent conversion lists to reserved & unreserved parts

login
register
mail settings
Submitter Zheng Liu
Date May 8, 2013, 7:03 a.m.
Message ID <20130508070335.GC20599@gmail.com>
Download mbox | patch
Permalink /patch/242506/
State Not Applicable
Headers show

Comments

Zheng Liu - May 8, 2013, 7:03 a.m.
On Mon, Apr 08, 2013 at 11:32:26PM +0200, Jan Kara wrote:
> Now that we have extent conversions with reserved transaction, we have
> to prevent extent conversions without reserved transaction (from DIO
> code) to block these (as that would effectively void any transaction
> reservation we did). So split lists, work items, and work queues to
> reserved and unreserved parts.
> 
> Signed-off-by: Jan Kara <jack@suse.cz>

I got a build error that looks like this.

 fs/ext4/page-io.c: In function ‘ext4_ioend_shutdown’:
 fs/ext4/page-io.c:60: error: ‘struct ext4_inode_info’ has no member
 named ‘i_unwritten_work’

I guess the reason is that when this patch set is sent out,
ext4_io_end_shutdown() hasn't be added.  So please add the code
like this.  Otherwise the patch looks good to me.
Reviewed-by: Zheng Liu <wenqing.lz@taobao.com>

Regards,
                                                - Zheng


> ---
>  fs/ext4/ext4.h    |   25 +++++++++++++++++-----
>  fs/ext4/page-io.c |   59 ++++++++++++++++++++++++++++++++++------------------
>  fs/ext4/super.c   |   38 ++++++++++++++++++++++++---------
>  3 files changed, 84 insertions(+), 38 deletions(-)
> 
> diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
> index 65adf0d..a594a94 100644
> --- a/fs/ext4/ext4.h
> +++ b/fs/ext4/ext4.h
> @@ -889,12 +889,22 @@ struct ext4_inode_info {
>  	qsize_t i_reserved_quota;
>  #endif
>  
> -	/* completed IOs that might need unwritten extents handling */
> -	struct list_head i_completed_io_list;
> +	/* Lock protecting lists below */
>  	spinlock_t i_completed_io_lock;
> +	/*
> +	 * Completed IOs that need unwritten extents handling and have
> +	 * transaction reserved
> +	 */
> +	struct list_head i_rsv_conversion_list;
> +	/*
> +	 * Completed IOs that need unwritten extents handling and don't have
> +	 * transaction reserved
> +	 */
> +	struct list_head i_unrsv_conversion_list;
>  	atomic_t i_ioend_count;	/* Number of outstanding io_end structs */
>  	atomic_t i_unwritten; /* Nr. of inflight conversions pending */
> -	struct work_struct i_unwritten_work;	/* deferred extent conversion */
> +	struct work_struct i_rsv_conversion_work;
> +	struct work_struct i_unrsv_conversion_work;
>  
>  	spinlock_t i_block_reservation_lock;
>  
> @@ -1257,8 +1267,10 @@ struct ext4_sb_info {
>  	struct flex_groups *s_flex_groups;
>  	ext4_group_t s_flex_groups_allocated;
>  
> -	/* workqueue for dio unwritten */
> -	struct workqueue_struct *dio_unwritten_wq;
> +	/* workqueue for unreserved extent convertions (dio) */
> +	struct workqueue_struct *unrsv_conversion_wq;
> +	/* workqueue for reserved extent conversions (buffered io) */
> +	struct workqueue_struct *rsv_conversion_wq;
>  
>  	/* timer for periodic error stats printing */
>  	struct timer_list s_err_report;
> @@ -2599,7 +2611,8 @@ extern int ext4_put_io_end(ext4_io_end_t *io_end);
>  extern void ext4_put_io_end_defer(ext4_io_end_t *io_end);
>  extern void ext4_io_submit_init(struct ext4_io_submit *io,
>  				struct writeback_control *wbc);
> -extern void ext4_end_io_work(struct work_struct *work);
> +extern void ext4_end_io_rsv_work(struct work_struct *work);
> +extern void ext4_end_io_unrsv_work(struct work_struct *work);
>  extern void ext4_io_submit(struct ext4_io_submit *io);
>  extern int ext4_bio_write_page(struct ext4_io_submit *io,
>  			       struct page *page,
> diff --git a/fs/ext4/page-io.c b/fs/ext4/page-io.c
> index e8ee4da..8bff3b3 100644
> --- a/fs/ext4/page-io.c
> +++ b/fs/ext4/page-io.c
> @@ -103,20 +103,17 @@ static int ext4_end_io(ext4_io_end_t *io)
>  	return ret;
>  }
>  
> -static void dump_completed_IO(struct inode *inode)
> +static void dump_completed_IO(struct inode *inode, struct list_head *head)
>  {
>  #ifdef	EXT4FS_DEBUG
>  	struct list_head *cur, *before, *after;
>  	ext4_io_end_t *io, *io0, *io1;
>  
> -	if (list_empty(&EXT4_I(inode)->i_completed_io_list)) {
> -		ext4_debug("inode %lu completed_io list is empty\n",
> -			   inode->i_ino);
> +	if (list_empty(head))
>  		return;
> -	}
>  
> -	ext4_debug("Dump inode %lu completed_io list\n", inode->i_ino);
> -	list_for_each_entry(io, &EXT4_I(inode)->i_completed_io_list, list) {
> +	ext4_debug("Dump inode %lu completed io list\n", inode->i_ino);
> +	list_for_each_entry(io, head, list) {
>  		cur = &io->list;
>  		before = cur->prev;
>  		io0 = container_of(before, ext4_io_end_t, list);
> @@ -137,16 +134,23 @@ static void ext4_add_complete_io(ext4_io_end_t *io_end)
>  	unsigned long flags;
>  
>  	BUG_ON(!(io_end->flag & EXT4_IO_END_UNWRITTEN));
> -	wq = EXT4_SB(io_end->inode->i_sb)->dio_unwritten_wq;
> -
>  	spin_lock_irqsave(&ei->i_completed_io_lock, flags);
> -	if (list_empty(&ei->i_completed_io_list))
> -		queue_work(wq, &ei->i_unwritten_work);
> -	list_add_tail(&io_end->list, &ei->i_completed_io_list);
> +	if (io_end->handle) {
> +		wq = EXT4_SB(io_end->inode->i_sb)->rsv_conversion_wq;
> +		if (list_empty(&ei->i_rsv_conversion_list))
> +			queue_work(wq, &ei->i_rsv_conversion_work);
> +		list_add_tail(&io_end->list, &ei->i_rsv_conversion_list);
> +	} else {
> +		wq = EXT4_SB(io_end->inode->i_sb)->unrsv_conversion_wq;
> +		if (list_empty(&ei->i_unrsv_conversion_list))
> +			queue_work(wq, &ei->i_unrsv_conversion_work);
> +		list_add_tail(&io_end->list, &ei->i_unrsv_conversion_list);
> +	}
>  	spin_unlock_irqrestore(&ei->i_completed_io_lock, flags);
>  }
>  
> -static int ext4_do_flush_completed_IO(struct inode *inode)
> +static int ext4_do_flush_completed_IO(struct inode *inode,
> +				      struct list_head *head)
>  {
>  	ext4_io_end_t *io;
>  	struct list_head unwritten;
> @@ -155,8 +159,8 @@ static int ext4_do_flush_completed_IO(struct inode *inode)
>  	int err, ret = 0;
>  
>  	spin_lock_irqsave(&ei->i_completed_io_lock, flags);
> -	dump_completed_IO(inode);
> -	list_replace_init(&ei->i_completed_io_list, &unwritten);
> +	dump_completed_IO(inode, head);
> +	list_replace_init(head, &unwritten);
>  	spin_unlock_irqrestore(&ei->i_completed_io_lock, flags);
>  
>  	while (!list_empty(&unwritten)) {
> @@ -172,21 +176,34 @@ static int ext4_do_flush_completed_IO(struct inode *inode)
>  }
>  
>  /*
> - * work on completed aio dio IO, to convert unwritten extents to extents
> + * work on completed IO, to convert unwritten extents to extents
>   */
> -void ext4_end_io_work(struct work_struct *work)
> +void ext4_end_io_rsv_work(struct work_struct *work)
>  {
>  	struct ext4_inode_info *ei = container_of(work, struct ext4_inode_info,
> -						  i_unwritten_work);
> -	ext4_do_flush_completed_IO(&ei->vfs_inode);
> +						  i_rsv_conversion_work);
> +	ext4_do_flush_completed_IO(&ei->vfs_inode, &ei->i_rsv_conversion_list);
> +}
> +
> +void ext4_end_io_unrsv_work(struct work_struct *work)
> +{
> +	struct ext4_inode_info *ei = container_of(work, struct ext4_inode_info,
> +						  i_unrsv_conversion_work);
> +	ext4_do_flush_completed_IO(&ei->vfs_inode, &ei->i_unrsv_conversion_list);
>  }
>  
>  int ext4_flush_unwritten_io(struct inode *inode)
>  {
> -	int ret;
> +	int ret, err;
> +
>  	WARN_ON_ONCE(!mutex_is_locked(&inode->i_mutex) &&
>  		     !(inode->i_state & I_FREEING));
> -	ret = ext4_do_flush_completed_IO(inode);
> +	ret = ext4_do_flush_completed_IO(inode,
> +					 &EXT4_I(inode)->i_rsv_conversion_list);
> +	err = ext4_do_flush_completed_IO(inode,
> +					 &EXT4_I(inode)->i_unrsv_conversion_list);
> +	if (!ret)
> +		ret = err;
>  	ext4_unwritten_wait(inode);
>  	return ret;
>  }
> diff --git a/fs/ext4/super.c b/fs/ext4/super.c
> index 09ff724..916c4fb 100644
> --- a/fs/ext4/super.c
> +++ b/fs/ext4/super.c
> @@ -747,8 +747,10 @@ static void ext4_put_super(struct super_block *sb)
>  	ext4_unregister_li_request(sb);
>  	dquot_disable(sb, -1, DQUOT_USAGE_ENABLED | DQUOT_LIMITS_ENABLED);
>  
> -	flush_workqueue(sbi->dio_unwritten_wq);
> -	destroy_workqueue(sbi->dio_unwritten_wq);
> +	flush_workqueue(sbi->unrsv_conversion_wq);
> +	flush_workqueue(sbi->rsv_conversion_wq);
> +	destroy_workqueue(sbi->unrsv_conversion_wq);
> +	destroy_workqueue(sbi->rsv_conversion_wq);
>  
>  	if (sbi->s_journal) {
>  		err = jbd2_journal_destroy(sbi->s_journal);
> @@ -856,13 +858,15 @@ static struct inode *ext4_alloc_inode(struct super_block *sb)
>  	ei->i_reserved_quota = 0;
>  #endif
>  	ei->jinode = NULL;
> -	INIT_LIST_HEAD(&ei->i_completed_io_list);
> +	INIT_LIST_HEAD(&ei->i_rsv_conversion_list);
> +	INIT_LIST_HEAD(&ei->i_unrsv_conversion_list);
>  	spin_lock_init(&ei->i_completed_io_lock);
>  	ei->i_sync_tid = 0;
>  	ei->i_datasync_tid = 0;
>  	atomic_set(&ei->i_ioend_count, 0);
>  	atomic_set(&ei->i_unwritten, 0);
> -	INIT_WORK(&ei->i_unwritten_work, ext4_end_io_work);
> +	INIT_WORK(&ei->i_rsv_conversion_work, ext4_end_io_rsv_work);
> +	INIT_WORK(&ei->i_unrsv_conversion_work, ext4_end_io_unrsv_work);
>  
>  	return &ei->vfs_inode;
>  }
> @@ -3867,12 +3871,20 @@ no_journal:
>  	 * The maximum number of concurrent works can be high and
>  	 * concurrency isn't really necessary.  Limit it to 1.
>  	 */
> -	EXT4_SB(sb)->dio_unwritten_wq =
> -		alloc_workqueue("ext4-dio-unwritten", WQ_MEM_RECLAIM | WQ_UNBOUND, 1);
> -	if (!EXT4_SB(sb)->dio_unwritten_wq) {
> -		printk(KERN_ERR "EXT4-fs: failed to create DIO workqueue\n");
> +	EXT4_SB(sb)->rsv_conversion_wq =
> +		alloc_workqueue("ext4-rsv-conversion", WQ_MEM_RECLAIM | WQ_UNBOUND, 1);
> +	if (!EXT4_SB(sb)->rsv_conversion_wq) {
> +		printk(KERN_ERR "EXT4-fs: failed to create workqueue\n");
>  		ret = -ENOMEM;
> -		goto failed_mount_wq;
> +		goto failed_mount4;
> +	}
> +
> +	EXT4_SB(sb)->unrsv_conversion_wq =
> +		alloc_workqueue("ext4-unrsv-conversion", WQ_MEM_RECLAIM | WQ_UNBOUND, 1);
> +	if (!EXT4_SB(sb)->unrsv_conversion_wq) {
> +		printk(KERN_ERR "EXT4-fs: failed to create workqueue\n");
> +		ret = -ENOMEM;
> +		goto failed_mount4;
>  	}
>  
>  	/*
> @@ -4019,7 +4031,10 @@ failed_mount4a:
>  	sb->s_root = NULL;
>  failed_mount4:
>  	ext4_msg(sb, KERN_ERR, "mount failed");
> -	destroy_workqueue(EXT4_SB(sb)->dio_unwritten_wq);
> +	if (EXT4_SB(sb)->rsv_conversion_wq)
> +		destroy_workqueue(EXT4_SB(sb)->rsv_conversion_wq);
> +	if (EXT4_SB(sb)->unrsv_conversion_wq)
> +		destroy_workqueue(EXT4_SB(sb)->unrsv_conversion_wq);
>  failed_mount_wq:
>  	if (sbi->s_journal) {
>  		jbd2_journal_destroy(sbi->s_journal);
> @@ -4464,7 +4479,8 @@ static int ext4_sync_fs(struct super_block *sb, int wait)
>  	struct ext4_sb_info *sbi = EXT4_SB(sb);
>  
>  	trace_ext4_sync_fs(sb, wait);
> -	flush_workqueue(sbi->dio_unwritten_wq);
> +	flush_workqueue(sbi->rsv_conversion_wq);
> +	flush_workqueue(sbi->unrsv_conversion_wq);
>  	/*
>  	 * Writeback quota in non-journalled quota case - journalled quota has
>  	 * no dirty dquots
> -- 
> 1.7.1
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-ext4" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
--
To unsubscribe from this list: send the line "unsubscribe linux-ext4" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Jan Kara - May 8, 2013, 11:23 a.m.
On Wed 08-05-13 15:03:35, Zheng Liu wrote:
> On Mon, Apr 08, 2013 at 11:32:26PM +0200, Jan Kara wrote:
> > Now that we have extent conversions with reserved transaction, we have
> > to prevent extent conversions without reserved transaction (from DIO
> > code) to block these (as that would effectively void any transaction
> > reservation we did). So split lists, work items, and work queues to
> > reserved and unreserved parts.
> > 
> > Signed-off-by: Jan Kara <jack@suse.cz>
> 
> I got a build error that looks like this.
> 
>  fs/ext4/page-io.c: In function ‘ext4_ioend_shutdown’:
>  fs/ext4/page-io.c:60: error: ‘struct ext4_inode_info’ has no member
>  named ‘i_unwritten_work’
> 
> I guess the reason is that when this patch set is sent out,
> ext4_io_end_shutdown() hasn't be added.  So please add the code
> like this.  Otherwise the patch looks good to me.
> Reviewed-by: Zheng Liu <wenqing.lz@taobao.com>
  Yeah, I've already rebased the series on top of current Linus's tree and
I've notice this problem as well. It should be fixed by now. I didn't post
the rebased series yet because I'm looking into some xfstests failures I
hit when testing it...

								Honza
> 
> Regards,
>                                                 - Zheng
> 
> diff --git a/fs/ext4/page-io.c b/fs/ext4/page-io.c
> index 3fea79e..f9ecc4f 100644
> --- a/fs/ext4/page-io.c
> +++ b/fs/ext4/page-io.c
> @@ -57,8 +57,10 @@ void ext4_ioend_shutdown(struct inode *inode)
>          * We need to make sure the work structure is finished being
>          * used before we let the inode get destroyed.
>          */
> -       if (work_pending(&EXT4_I(inode)->i_unwritten_work))
> -               cancel_work_sync(&EXT4_I(inode)->i_unwritten_work);
> +       if (work_pending(&EXT4_I(inode)->i_rsv_conversion_work))
> +               cancel_work_sync(&EXT4_I(inode)->i_rsv_conversion_work);
> +       if (work_pending(&EXT4_I(inode)->i_unrsv_conversion_work))
> + cancel_work_sync(&EXT4_I(inode)->i_unrsv_conversion_work);
>  }
>  
>  static void ext4_release_io_end(ext4_io_end_t *io_end)
> 
> > ---
> >  fs/ext4/ext4.h    |   25 +++++++++++++++++-----
> >  fs/ext4/page-io.c |   59 ++++++++++++++++++++++++++++++++++------------------
> >  fs/ext4/super.c   |   38 ++++++++++++++++++++++++---------
> >  3 files changed, 84 insertions(+), 38 deletions(-)
> > 
> > diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
> > index 65adf0d..a594a94 100644
> > --- a/fs/ext4/ext4.h
> > +++ b/fs/ext4/ext4.h
> > @@ -889,12 +889,22 @@ struct ext4_inode_info {
> >  	qsize_t i_reserved_quota;
> >  #endif
> >  
> > -	/* completed IOs that might need unwritten extents handling */
> > -	struct list_head i_completed_io_list;
> > +	/* Lock protecting lists below */
> >  	spinlock_t i_completed_io_lock;
> > +	/*
> > +	 * Completed IOs that need unwritten extents handling and have
> > +	 * transaction reserved
> > +	 */
> > +	struct list_head i_rsv_conversion_list;
> > +	/*
> > +	 * Completed IOs that need unwritten extents handling and don't have
> > +	 * transaction reserved
> > +	 */
> > +	struct list_head i_unrsv_conversion_list;
> >  	atomic_t i_ioend_count;	/* Number of outstanding io_end structs */
> >  	atomic_t i_unwritten; /* Nr. of inflight conversions pending */
> > -	struct work_struct i_unwritten_work;	/* deferred extent conversion */
> > +	struct work_struct i_rsv_conversion_work;
> > +	struct work_struct i_unrsv_conversion_work;
> >  
> >  	spinlock_t i_block_reservation_lock;
> >  
> > @@ -1257,8 +1267,10 @@ struct ext4_sb_info {
> >  	struct flex_groups *s_flex_groups;
> >  	ext4_group_t s_flex_groups_allocated;
> >  
> > -	/* workqueue for dio unwritten */
> > -	struct workqueue_struct *dio_unwritten_wq;
> > +	/* workqueue for unreserved extent convertions (dio) */
> > +	struct workqueue_struct *unrsv_conversion_wq;
> > +	/* workqueue for reserved extent conversions (buffered io) */
> > +	struct workqueue_struct *rsv_conversion_wq;
> >  
> >  	/* timer for periodic error stats printing */
> >  	struct timer_list s_err_report;
> > @@ -2599,7 +2611,8 @@ extern int ext4_put_io_end(ext4_io_end_t *io_end);
> >  extern void ext4_put_io_end_defer(ext4_io_end_t *io_end);
> >  extern void ext4_io_submit_init(struct ext4_io_submit *io,
> >  				struct writeback_control *wbc);
> > -extern void ext4_end_io_work(struct work_struct *work);
> > +extern void ext4_end_io_rsv_work(struct work_struct *work);
> > +extern void ext4_end_io_unrsv_work(struct work_struct *work);
> >  extern void ext4_io_submit(struct ext4_io_submit *io);
> >  extern int ext4_bio_write_page(struct ext4_io_submit *io,
> >  			       struct page *page,
> > diff --git a/fs/ext4/page-io.c b/fs/ext4/page-io.c
> > index e8ee4da..8bff3b3 100644
> > --- a/fs/ext4/page-io.c
> > +++ b/fs/ext4/page-io.c
> > @@ -103,20 +103,17 @@ static int ext4_end_io(ext4_io_end_t *io)
> >  	return ret;
> >  }
> >  
> > -static void dump_completed_IO(struct inode *inode)
> > +static void dump_completed_IO(struct inode *inode, struct list_head *head)
> >  {
> >  #ifdef	EXT4FS_DEBUG
> >  	struct list_head *cur, *before, *after;
> >  	ext4_io_end_t *io, *io0, *io1;
> >  
> > -	if (list_empty(&EXT4_I(inode)->i_completed_io_list)) {
> > -		ext4_debug("inode %lu completed_io list is empty\n",
> > -			   inode->i_ino);
> > +	if (list_empty(head))
> >  		return;
> > -	}
> >  
> > -	ext4_debug("Dump inode %lu completed_io list\n", inode->i_ino);
> > -	list_for_each_entry(io, &EXT4_I(inode)->i_completed_io_list, list) {
> > +	ext4_debug("Dump inode %lu completed io list\n", inode->i_ino);
> > +	list_for_each_entry(io, head, list) {
> >  		cur = &io->list;
> >  		before = cur->prev;
> >  		io0 = container_of(before, ext4_io_end_t, list);
> > @@ -137,16 +134,23 @@ static void ext4_add_complete_io(ext4_io_end_t *io_end)
> >  	unsigned long flags;
> >  
> >  	BUG_ON(!(io_end->flag & EXT4_IO_END_UNWRITTEN));
> > -	wq = EXT4_SB(io_end->inode->i_sb)->dio_unwritten_wq;
> > -
> >  	spin_lock_irqsave(&ei->i_completed_io_lock, flags);
> > -	if (list_empty(&ei->i_completed_io_list))
> > -		queue_work(wq, &ei->i_unwritten_work);
> > -	list_add_tail(&io_end->list, &ei->i_completed_io_list);
> > +	if (io_end->handle) {
> > +		wq = EXT4_SB(io_end->inode->i_sb)->rsv_conversion_wq;
> > +		if (list_empty(&ei->i_rsv_conversion_list))
> > +			queue_work(wq, &ei->i_rsv_conversion_work);
> > +		list_add_tail(&io_end->list, &ei->i_rsv_conversion_list);
> > +	} else {
> > +		wq = EXT4_SB(io_end->inode->i_sb)->unrsv_conversion_wq;
> > +		if (list_empty(&ei->i_unrsv_conversion_list))
> > +			queue_work(wq, &ei->i_unrsv_conversion_work);
> > +		list_add_tail(&io_end->list, &ei->i_unrsv_conversion_list);
> > +	}
> >  	spin_unlock_irqrestore(&ei->i_completed_io_lock, flags);
> >  }
> >  
> > -static int ext4_do_flush_completed_IO(struct inode *inode)
> > +static int ext4_do_flush_completed_IO(struct inode *inode,
> > +				      struct list_head *head)
> >  {
> >  	ext4_io_end_t *io;
> >  	struct list_head unwritten;
> > @@ -155,8 +159,8 @@ static int ext4_do_flush_completed_IO(struct inode *inode)
> >  	int err, ret = 0;
> >  
> >  	spin_lock_irqsave(&ei->i_completed_io_lock, flags);
> > -	dump_completed_IO(inode);
> > -	list_replace_init(&ei->i_completed_io_list, &unwritten);
> > +	dump_completed_IO(inode, head);
> > +	list_replace_init(head, &unwritten);
> >  	spin_unlock_irqrestore(&ei->i_completed_io_lock, flags);
> >  
> >  	while (!list_empty(&unwritten)) {
> > @@ -172,21 +176,34 @@ static int ext4_do_flush_completed_IO(struct inode *inode)
> >  }
> >  
> >  /*
> > - * work on completed aio dio IO, to convert unwritten extents to extents
> > + * work on completed IO, to convert unwritten extents to extents
> >   */
> > -void ext4_end_io_work(struct work_struct *work)
> > +void ext4_end_io_rsv_work(struct work_struct *work)
> >  {
> >  	struct ext4_inode_info *ei = container_of(work, struct ext4_inode_info,
> > -						  i_unwritten_work);
> > -	ext4_do_flush_completed_IO(&ei->vfs_inode);
> > +						  i_rsv_conversion_work);
> > +	ext4_do_flush_completed_IO(&ei->vfs_inode, &ei->i_rsv_conversion_list);
> > +}
> > +
> > +void ext4_end_io_unrsv_work(struct work_struct *work)
> > +{
> > +	struct ext4_inode_info *ei = container_of(work, struct ext4_inode_info,
> > +						  i_unrsv_conversion_work);
> > +	ext4_do_flush_completed_IO(&ei->vfs_inode, &ei->i_unrsv_conversion_list);
> >  }
> >  
> >  int ext4_flush_unwritten_io(struct inode *inode)
> >  {
> > -	int ret;
> > +	int ret, err;
> > +
> >  	WARN_ON_ONCE(!mutex_is_locked(&inode->i_mutex) &&
> >  		     !(inode->i_state & I_FREEING));
> > -	ret = ext4_do_flush_completed_IO(inode);
> > +	ret = ext4_do_flush_completed_IO(inode,
> > +					 &EXT4_I(inode)->i_rsv_conversion_list);
> > +	err = ext4_do_flush_completed_IO(inode,
> > +					 &EXT4_I(inode)->i_unrsv_conversion_list);
> > +	if (!ret)
> > +		ret = err;
> >  	ext4_unwritten_wait(inode);
> >  	return ret;
> >  }
> > diff --git a/fs/ext4/super.c b/fs/ext4/super.c
> > index 09ff724..916c4fb 100644
> > --- a/fs/ext4/super.c
> > +++ b/fs/ext4/super.c
> > @@ -747,8 +747,10 @@ static void ext4_put_super(struct super_block *sb)
> >  	ext4_unregister_li_request(sb);
> >  	dquot_disable(sb, -1, DQUOT_USAGE_ENABLED | DQUOT_LIMITS_ENABLED);
> >  
> > -	flush_workqueue(sbi->dio_unwritten_wq);
> > -	destroy_workqueue(sbi->dio_unwritten_wq);
> > +	flush_workqueue(sbi->unrsv_conversion_wq);
> > +	flush_workqueue(sbi->rsv_conversion_wq);
> > +	destroy_workqueue(sbi->unrsv_conversion_wq);
> > +	destroy_workqueue(sbi->rsv_conversion_wq);
> >  
> >  	if (sbi->s_journal) {
> >  		err = jbd2_journal_destroy(sbi->s_journal);
> > @@ -856,13 +858,15 @@ static struct inode *ext4_alloc_inode(struct super_block *sb)
> >  	ei->i_reserved_quota = 0;
> >  #endif
> >  	ei->jinode = NULL;
> > -	INIT_LIST_HEAD(&ei->i_completed_io_list);
> > +	INIT_LIST_HEAD(&ei->i_rsv_conversion_list);
> > +	INIT_LIST_HEAD(&ei->i_unrsv_conversion_list);
> >  	spin_lock_init(&ei->i_completed_io_lock);
> >  	ei->i_sync_tid = 0;
> >  	ei->i_datasync_tid = 0;
> >  	atomic_set(&ei->i_ioend_count, 0);
> >  	atomic_set(&ei->i_unwritten, 0);
> > -	INIT_WORK(&ei->i_unwritten_work, ext4_end_io_work);
> > +	INIT_WORK(&ei->i_rsv_conversion_work, ext4_end_io_rsv_work);
> > +	INIT_WORK(&ei->i_unrsv_conversion_work, ext4_end_io_unrsv_work);
> >  
> >  	return &ei->vfs_inode;
> >  }
> > @@ -3867,12 +3871,20 @@ no_journal:
> >  	 * The maximum number of concurrent works can be high and
> >  	 * concurrency isn't really necessary.  Limit it to 1.
> >  	 */
> > -	EXT4_SB(sb)->dio_unwritten_wq =
> > -		alloc_workqueue("ext4-dio-unwritten", WQ_MEM_RECLAIM | WQ_UNBOUND, 1);
> > -	if (!EXT4_SB(sb)->dio_unwritten_wq) {
> > -		printk(KERN_ERR "EXT4-fs: failed to create DIO workqueue\n");
> > +	EXT4_SB(sb)->rsv_conversion_wq =
> > +		alloc_workqueue("ext4-rsv-conversion", WQ_MEM_RECLAIM | WQ_UNBOUND, 1);
> > +	if (!EXT4_SB(sb)->rsv_conversion_wq) {
> > +		printk(KERN_ERR "EXT4-fs: failed to create workqueue\n");
> >  		ret = -ENOMEM;
> > -		goto failed_mount_wq;
> > +		goto failed_mount4;
> > +	}
> > +
> > +	EXT4_SB(sb)->unrsv_conversion_wq =
> > +		alloc_workqueue("ext4-unrsv-conversion", WQ_MEM_RECLAIM | WQ_UNBOUND, 1);
> > +	if (!EXT4_SB(sb)->unrsv_conversion_wq) {
> > +		printk(KERN_ERR "EXT4-fs: failed to create workqueue\n");
> > +		ret = -ENOMEM;
> > +		goto failed_mount4;
> >  	}
> >  
> >  	/*
> > @@ -4019,7 +4031,10 @@ failed_mount4a:
> >  	sb->s_root = NULL;
> >  failed_mount4:
> >  	ext4_msg(sb, KERN_ERR, "mount failed");
> > -	destroy_workqueue(EXT4_SB(sb)->dio_unwritten_wq);
> > +	if (EXT4_SB(sb)->rsv_conversion_wq)
> > +		destroy_workqueue(EXT4_SB(sb)->rsv_conversion_wq);
> > +	if (EXT4_SB(sb)->unrsv_conversion_wq)
> > +		destroy_workqueue(EXT4_SB(sb)->unrsv_conversion_wq);
> >  failed_mount_wq:
> >  	if (sbi->s_journal) {
> >  		jbd2_journal_destroy(sbi->s_journal);
> > @@ -4464,7 +4479,8 @@ static int ext4_sync_fs(struct super_block *sb, int wait)
> >  	struct ext4_sb_info *sbi = EXT4_SB(sb);
> >  
> >  	trace_ext4_sync_fs(sb, wait);
> > -	flush_workqueue(sbi->dio_unwritten_wq);
> > +	flush_workqueue(sbi->rsv_conversion_wq);
> > +	flush_workqueue(sbi->unrsv_conversion_wq);
> >  	/*
> >  	 * Writeback quota in non-journalled quota case - journalled quota has
> >  	 * no dirty dquots
> > -- 
> > 1.7.1
> > 
> > --
> > To unsubscribe from this list: send the line "unsubscribe linux-ext4" in
> > the body of a message to majordomo@vger.kernel.org
> > More majordomo info at  http://vger.kernel.org/majordomo-info.html
Zheng Liu - May 8, 2013, 11:49 a.m.
On Wed, May 08, 2013 at 01:23:55PM +0200, Jan Kara wrote:
> On Wed 08-05-13 15:03:35, Zheng Liu wrote:
> > On Mon, Apr 08, 2013 at 11:32:26PM +0200, Jan Kara wrote:
> > > Now that we have extent conversions with reserved transaction, we have
> > > to prevent extent conversions without reserved transaction (from DIO
> > > code) to block these (as that would effectively void any transaction
> > > reservation we did). So split lists, work items, and work queues to
> > > reserved and unreserved parts.
> > > 
> > > Signed-off-by: Jan Kara <jack@suse.cz>
> > 
> > I got a build error that looks like this.
> > 
> >  fs/ext4/page-io.c: In function ‘ext4_ioend_shutdown’:
> >  fs/ext4/page-io.c:60: error: ‘struct ext4_inode_info’ has no member
> >  named ‘i_unwritten_work’
> > 
> > I guess the reason is that when this patch set is sent out,
> > ext4_io_end_shutdown() hasn't be added.  So please add the code
> > like this.  Otherwise the patch looks good to me.
> > Reviewed-by: Zheng Liu <wenqing.lz@taobao.com>
>   Yeah, I've already rebased the series on top of current Linus's tree and
> I've notice this problem as well. It should be fixed by now. I didn't post
> the rebased series yet because I'm looking into some xfstests failures I
> hit when testing it...

Thanks for your excellent work.  Yes, I am running xfstests against your
patch set and I got a failure that is #091 test case when dioread_nolock
enables.  That is pretty easy to be triggered.  Just let you know.

Regards,
                                                - Zheng

> > diff --git a/fs/ext4/page-io.c b/fs/ext4/page-io.c
> > index 3fea79e..f9ecc4f 100644
> > --- a/fs/ext4/page-io.c
> > +++ b/fs/ext4/page-io.c
> > @@ -57,8 +57,10 @@ void ext4_ioend_shutdown(struct inode *inode)
> >          * We need to make sure the work structure is finished being
> >          * used before we let the inode get destroyed.
> >          */
> > -       if (work_pending(&EXT4_I(inode)->i_unwritten_work))
> > -               cancel_work_sync(&EXT4_I(inode)->i_unwritten_work);
> > +       if (work_pending(&EXT4_I(inode)->i_rsv_conversion_work))
> > +               cancel_work_sync(&EXT4_I(inode)->i_rsv_conversion_work);
> > +       if (work_pending(&EXT4_I(inode)->i_unrsv_conversion_work))
> > + cancel_work_sync(&EXT4_I(inode)->i_unrsv_conversion_work);
> >  }
> >  
> >  static void ext4_release_io_end(ext4_io_end_t *io_end)
> > 
> > > ---
> > >  fs/ext4/ext4.h    |   25 +++++++++++++++++-----
> > >  fs/ext4/page-io.c |   59 ++++++++++++++++++++++++++++++++++------------------
> > >  fs/ext4/super.c   |   38 ++++++++++++++++++++++++---------
> > >  3 files changed, 84 insertions(+), 38 deletions(-)
> > > 
> > > diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
> > > index 65adf0d..a594a94 100644
> > > --- a/fs/ext4/ext4.h
> > > +++ b/fs/ext4/ext4.h
> > > @@ -889,12 +889,22 @@ struct ext4_inode_info {
> > >  	qsize_t i_reserved_quota;
> > >  #endif
> > >  
> > > -	/* completed IOs that might need unwritten extents handling */
> > > -	struct list_head i_completed_io_list;
> > > +	/* Lock protecting lists below */
> > >  	spinlock_t i_completed_io_lock;
> > > +	/*
> > > +	 * Completed IOs that need unwritten extents handling and have
> > > +	 * transaction reserved
> > > +	 */
> > > +	struct list_head i_rsv_conversion_list;
> > > +	/*
> > > +	 * Completed IOs that need unwritten extents handling and don't have
> > > +	 * transaction reserved
> > > +	 */
> > > +	struct list_head i_unrsv_conversion_list;
> > >  	atomic_t i_ioend_count;	/* Number of outstanding io_end structs */
> > >  	atomic_t i_unwritten; /* Nr. of inflight conversions pending */
> > > -	struct work_struct i_unwritten_work;	/* deferred extent conversion */
> > > +	struct work_struct i_rsv_conversion_work;
> > > +	struct work_struct i_unrsv_conversion_work;
> > >  
> > >  	spinlock_t i_block_reservation_lock;
> > >  
> > > @@ -1257,8 +1267,10 @@ struct ext4_sb_info {
> > >  	struct flex_groups *s_flex_groups;
> > >  	ext4_group_t s_flex_groups_allocated;
> > >  
> > > -	/* workqueue for dio unwritten */
> > > -	struct workqueue_struct *dio_unwritten_wq;
> > > +	/* workqueue for unreserved extent convertions (dio) */
> > > +	struct workqueue_struct *unrsv_conversion_wq;
> > > +	/* workqueue for reserved extent conversions (buffered io) */
> > > +	struct workqueue_struct *rsv_conversion_wq;
> > >  
> > >  	/* timer for periodic error stats printing */
> > >  	struct timer_list s_err_report;
> > > @@ -2599,7 +2611,8 @@ extern int ext4_put_io_end(ext4_io_end_t *io_end);
> > >  extern void ext4_put_io_end_defer(ext4_io_end_t *io_end);
> > >  extern void ext4_io_submit_init(struct ext4_io_submit *io,
> > >  				struct writeback_control *wbc);
> > > -extern void ext4_end_io_work(struct work_struct *work);
> > > +extern void ext4_end_io_rsv_work(struct work_struct *work);
> > > +extern void ext4_end_io_unrsv_work(struct work_struct *work);
> > >  extern void ext4_io_submit(struct ext4_io_submit *io);
> > >  extern int ext4_bio_write_page(struct ext4_io_submit *io,
> > >  			       struct page *page,
> > > diff --git a/fs/ext4/page-io.c b/fs/ext4/page-io.c
> > > index e8ee4da..8bff3b3 100644
> > > --- a/fs/ext4/page-io.c
> > > +++ b/fs/ext4/page-io.c
> > > @@ -103,20 +103,17 @@ static int ext4_end_io(ext4_io_end_t *io)
> > >  	return ret;
> > >  }
> > >  
> > > -static void dump_completed_IO(struct inode *inode)
> > > +static void dump_completed_IO(struct inode *inode, struct list_head *head)
> > >  {
> > >  #ifdef	EXT4FS_DEBUG
> > >  	struct list_head *cur, *before, *after;
> > >  	ext4_io_end_t *io, *io0, *io1;
> > >  
> > > -	if (list_empty(&EXT4_I(inode)->i_completed_io_list)) {
> > > -		ext4_debug("inode %lu completed_io list is empty\n",
> > > -			   inode->i_ino);
> > > +	if (list_empty(head))
> > >  		return;
> > > -	}
> > >  
> > > -	ext4_debug("Dump inode %lu completed_io list\n", inode->i_ino);
> > > -	list_for_each_entry(io, &EXT4_I(inode)->i_completed_io_list, list) {
> > > +	ext4_debug("Dump inode %lu completed io list\n", inode->i_ino);
> > > +	list_for_each_entry(io, head, list) {
> > >  		cur = &io->list;
> > >  		before = cur->prev;
> > >  		io0 = container_of(before, ext4_io_end_t, list);
> > > @@ -137,16 +134,23 @@ static void ext4_add_complete_io(ext4_io_end_t *io_end)
> > >  	unsigned long flags;
> > >  
> > >  	BUG_ON(!(io_end->flag & EXT4_IO_END_UNWRITTEN));
> > > -	wq = EXT4_SB(io_end->inode->i_sb)->dio_unwritten_wq;
> > > -
> > >  	spin_lock_irqsave(&ei->i_completed_io_lock, flags);
> > > -	if (list_empty(&ei->i_completed_io_list))
> > > -		queue_work(wq, &ei->i_unwritten_work);
> > > -	list_add_tail(&io_end->list, &ei->i_completed_io_list);
> > > +	if (io_end->handle) {
> > > +		wq = EXT4_SB(io_end->inode->i_sb)->rsv_conversion_wq;
> > > +		if (list_empty(&ei->i_rsv_conversion_list))
> > > +			queue_work(wq, &ei->i_rsv_conversion_work);
> > > +		list_add_tail(&io_end->list, &ei->i_rsv_conversion_list);
> > > +	} else {
> > > +		wq = EXT4_SB(io_end->inode->i_sb)->unrsv_conversion_wq;
> > > +		if (list_empty(&ei->i_unrsv_conversion_list))
> > > +			queue_work(wq, &ei->i_unrsv_conversion_work);
> > > +		list_add_tail(&io_end->list, &ei->i_unrsv_conversion_list);
> > > +	}
> > >  	spin_unlock_irqrestore(&ei->i_completed_io_lock, flags);
> > >  }
> > >  
> > > -static int ext4_do_flush_completed_IO(struct inode *inode)
> > > +static int ext4_do_flush_completed_IO(struct inode *inode,
> > > +				      struct list_head *head)
> > >  {
> > >  	ext4_io_end_t *io;
> > >  	struct list_head unwritten;
> > > @@ -155,8 +159,8 @@ static int ext4_do_flush_completed_IO(struct inode *inode)
> > >  	int err, ret = 0;
> > >  
> > >  	spin_lock_irqsave(&ei->i_completed_io_lock, flags);
> > > -	dump_completed_IO(inode);
> > > -	list_replace_init(&ei->i_completed_io_list, &unwritten);
> > > +	dump_completed_IO(inode, head);
> > > +	list_replace_init(head, &unwritten);
> > >  	spin_unlock_irqrestore(&ei->i_completed_io_lock, flags);
> > >  
> > >  	while (!list_empty(&unwritten)) {
> > > @@ -172,21 +176,34 @@ static int ext4_do_flush_completed_IO(struct inode *inode)
> > >  }
> > >  
> > >  /*
> > > - * work on completed aio dio IO, to convert unwritten extents to extents
> > > + * work on completed IO, to convert unwritten extents to extents
> > >   */
> > > -void ext4_end_io_work(struct work_struct *work)
> > > +void ext4_end_io_rsv_work(struct work_struct *work)
> > >  {
> > >  	struct ext4_inode_info *ei = container_of(work, struct ext4_inode_info,
> > > -						  i_unwritten_work);
> > > -	ext4_do_flush_completed_IO(&ei->vfs_inode);
> > > +						  i_rsv_conversion_work);
> > > +	ext4_do_flush_completed_IO(&ei->vfs_inode, &ei->i_rsv_conversion_list);
> > > +}
> > > +
> > > +void ext4_end_io_unrsv_work(struct work_struct *work)
> > > +{
> > > +	struct ext4_inode_info *ei = container_of(work, struct ext4_inode_info,
> > > +						  i_unrsv_conversion_work);
> > > +	ext4_do_flush_completed_IO(&ei->vfs_inode, &ei->i_unrsv_conversion_list);
> > >  }
> > >  
> > >  int ext4_flush_unwritten_io(struct inode *inode)
> > >  {
> > > -	int ret;
> > > +	int ret, err;
> > > +
> > >  	WARN_ON_ONCE(!mutex_is_locked(&inode->i_mutex) &&
> > >  		     !(inode->i_state & I_FREEING));
> > > -	ret = ext4_do_flush_completed_IO(inode);
> > > +	ret = ext4_do_flush_completed_IO(inode,
> > > +					 &EXT4_I(inode)->i_rsv_conversion_list);
> > > +	err = ext4_do_flush_completed_IO(inode,
> > > +					 &EXT4_I(inode)->i_unrsv_conversion_list);
> > > +	if (!ret)
> > > +		ret = err;
> > >  	ext4_unwritten_wait(inode);
> > >  	return ret;
> > >  }
> > > diff --git a/fs/ext4/super.c b/fs/ext4/super.c
> > > index 09ff724..916c4fb 100644
> > > --- a/fs/ext4/super.c
> > > +++ b/fs/ext4/super.c
> > > @@ -747,8 +747,10 @@ static void ext4_put_super(struct super_block *sb)
> > >  	ext4_unregister_li_request(sb);
> > >  	dquot_disable(sb, -1, DQUOT_USAGE_ENABLED | DQUOT_LIMITS_ENABLED);
> > >  
> > > -	flush_workqueue(sbi->dio_unwritten_wq);
> > > -	destroy_workqueue(sbi->dio_unwritten_wq);
> > > +	flush_workqueue(sbi->unrsv_conversion_wq);
> > > +	flush_workqueue(sbi->rsv_conversion_wq);
> > > +	destroy_workqueue(sbi->unrsv_conversion_wq);
> > > +	destroy_workqueue(sbi->rsv_conversion_wq);
> > >  
> > >  	if (sbi->s_journal) {
> > >  		err = jbd2_journal_destroy(sbi->s_journal);
> > > @@ -856,13 +858,15 @@ static struct inode *ext4_alloc_inode(struct super_block *sb)
> > >  	ei->i_reserved_quota = 0;
> > >  #endif
> > >  	ei->jinode = NULL;
> > > -	INIT_LIST_HEAD(&ei->i_completed_io_list);
> > > +	INIT_LIST_HEAD(&ei->i_rsv_conversion_list);
> > > +	INIT_LIST_HEAD(&ei->i_unrsv_conversion_list);
> > >  	spin_lock_init(&ei->i_completed_io_lock);
> > >  	ei->i_sync_tid = 0;
> > >  	ei->i_datasync_tid = 0;
> > >  	atomic_set(&ei->i_ioend_count, 0);
> > >  	atomic_set(&ei->i_unwritten, 0);
> > > -	INIT_WORK(&ei->i_unwritten_work, ext4_end_io_work);
> > > +	INIT_WORK(&ei->i_rsv_conversion_work, ext4_end_io_rsv_work);
> > > +	INIT_WORK(&ei->i_unrsv_conversion_work, ext4_end_io_unrsv_work);
> > >  
> > >  	return &ei->vfs_inode;
> > >  }
> > > @@ -3867,12 +3871,20 @@ no_journal:
> > >  	 * The maximum number of concurrent works can be high and
> > >  	 * concurrency isn't really necessary.  Limit it to 1.
> > >  	 */
> > > -	EXT4_SB(sb)->dio_unwritten_wq =
> > > -		alloc_workqueue("ext4-dio-unwritten", WQ_MEM_RECLAIM | WQ_UNBOUND, 1);
> > > -	if (!EXT4_SB(sb)->dio_unwritten_wq) {
> > > -		printk(KERN_ERR "EXT4-fs: failed to create DIO workqueue\n");
> > > +	EXT4_SB(sb)->rsv_conversion_wq =
> > > +		alloc_workqueue("ext4-rsv-conversion", WQ_MEM_RECLAIM | WQ_UNBOUND, 1);
> > > +	if (!EXT4_SB(sb)->rsv_conversion_wq) {
> > > +		printk(KERN_ERR "EXT4-fs: failed to create workqueue\n");
> > >  		ret = -ENOMEM;
> > > -		goto failed_mount_wq;
> > > +		goto failed_mount4;
> > > +	}
> > > +
> > > +	EXT4_SB(sb)->unrsv_conversion_wq =
> > > +		alloc_workqueue("ext4-unrsv-conversion", WQ_MEM_RECLAIM | WQ_UNBOUND, 1);
> > > +	if (!EXT4_SB(sb)->unrsv_conversion_wq) {
> > > +		printk(KERN_ERR "EXT4-fs: failed to create workqueue\n");
> > > +		ret = -ENOMEM;
> > > +		goto failed_mount4;
> > >  	}
> > >  
> > >  	/*
> > > @@ -4019,7 +4031,10 @@ failed_mount4a:
> > >  	sb->s_root = NULL;
> > >  failed_mount4:
> > >  	ext4_msg(sb, KERN_ERR, "mount failed");
> > > -	destroy_workqueue(EXT4_SB(sb)->dio_unwritten_wq);
> > > +	if (EXT4_SB(sb)->rsv_conversion_wq)
> > > +		destroy_workqueue(EXT4_SB(sb)->rsv_conversion_wq);
> > > +	if (EXT4_SB(sb)->unrsv_conversion_wq)
> > > +		destroy_workqueue(EXT4_SB(sb)->unrsv_conversion_wq);
> > >  failed_mount_wq:
> > >  	if (sbi->s_journal) {
> > >  		jbd2_journal_destroy(sbi->s_journal);
> > > @@ -4464,7 +4479,8 @@ static int ext4_sync_fs(struct super_block *sb, int wait)
> > >  	struct ext4_sb_info *sbi = EXT4_SB(sb);
> > >  
> > >  	trace_ext4_sync_fs(sb, wait);
> > > -	flush_workqueue(sbi->dio_unwritten_wq);
> > > +	flush_workqueue(sbi->rsv_conversion_wq);
> > > +	flush_workqueue(sbi->unrsv_conversion_wq);
> > >  	/*
> > >  	 * Writeback quota in non-journalled quota case - journalled quota has
> > >  	 * no dirty dquots
> > > -- 
> > > 1.7.1
> > > 
> > > --
> > > To unsubscribe from this list: send the line "unsubscribe linux-ext4" in
> > > the body of a message to majordomo@vger.kernel.org
> > > More majordomo info at  http://vger.kernel.org/majordomo-info.html
> -- 
> Jan Kara <jack@suse.cz>
> SUSE Labs, CR
--
To unsubscribe from this list: send the line "unsubscribe linux-ext4" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Patch

diff --git a/fs/ext4/page-io.c b/fs/ext4/page-io.c
index 3fea79e..f9ecc4f 100644
--- a/fs/ext4/page-io.c
+++ b/fs/ext4/page-io.c
@@ -57,8 +57,10 @@  void ext4_ioend_shutdown(struct inode *inode)
         * We need to make sure the work structure is finished being
         * used before we let the inode get destroyed.
         */
-       if (work_pending(&EXT4_I(inode)->i_unwritten_work))
-               cancel_work_sync(&EXT4_I(inode)->i_unwritten_work);
+       if (work_pending(&EXT4_I(inode)->i_rsv_conversion_work))
+               cancel_work_sync(&EXT4_I(inode)->i_rsv_conversion_work);
+       if (work_pending(&EXT4_I(inode)->i_unrsv_conversion_work))
+ cancel_work_sync(&EXT4_I(inode)->i_unrsv_conversion_work);
 }
 
 static void ext4_release_io_end(ext4_io_end_t *io_end)