Re: [PATCH v4] writeback: Fix inode->i_io_list not be protected by inode->i_lock error

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Wed 11-05-22 07:15:18, Jchao Sun wrote:
> Commit b35250c0816c ("writeback: Protect inode->i_io_list with
> inode->i_lock") made inode->i_io_list not only protected by
> wb->list_lock but also inode->i_lock, but inode_io_list_move_locked()
> was missed. Add lock there and also update comment describing
> things protected by inode->i_lock.

Please expand the changelog a bit to mention to important fix in
__mark_inode_dirty(). Like:

This also fixes a race where __mark_inode_dirty() could move inode under
flush worker's hands and thus sync(2) could miss writing some inodes.

> Fixes: b35250c0816c ("writeback: Protect inode->i_io_list with inode->i_lock")
> Signed-off-by: Jchao Sun <sunjunchao2870@xxxxxxxxx>

...

> @@ -1365,9 +1366,9 @@ static int move_expired_inodes(struct list_head *delaying_queue,
>  		inode = wb_inode(delaying_queue->prev);
>  		if (inode_dirtied_after(inode, dirtied_before))
>  			break;
> +		spin_lock(&inode->i_lock);
>  		list_move(&inode->i_io_list, &tmp);
>  		moved++;
> -		spin_lock(&inode->i_lock);
>  		inode->i_state |= I_SYNC_QUEUED;
>  		spin_unlock(&inode->i_lock);
>  		if (sb_is_blkdev_sb(inode->i_sb))
> @@ -1383,6 +1384,7 @@ static int move_expired_inodes(struct list_head *delaying_queue,
>  		goto out;
>  	}
>  
> +	spin_lock(&inode->i_lock);

This is definitely wrong. 'inode' here is just something random left over
in the variable from the previous loop. As I wrote in my previous review, I
don't think taking 'i_lock' in the loop below is needed at all, although it
probably deserves a comment like:

	/*
	 * Although inode's i_io_list is moved from 'tmp' to
	 * 'dispatch_queue', we don't take inode->i_lock here because it is
	 * just a pointless overhead. Inode is already marked as
	 * I_SYNC_QUEUED so writeback list handling is fully under our
	 * control.
	 */

>  	/* Move inodes from one superblock together */
>  	while (!list_empty(&tmp)) {
>  		sb = wb_inode(tmp.prev)->i_sb;
> @@ -1392,6 +1394,7 @@ static int move_expired_inodes(struct list_head *delaying_queue,
>  				list_move(&inode->i_io_list, dispatch_queue);
>  		}
>  	}
> +	spin_unlock(&inode->i_lock);
>  out:
>  	return moved;
>  }

...

> @@ -2402,6 +2406,11 @@ void __mark_inode_dirty(struct inode *inode, int flags)
>  			inode->i_state &= ~I_DIRTY_TIME;
>  		inode->i_state |= flags;
>  

Perhaps add a comment here like:

	/*
	 * Grab inode's wb early because it requires dropping i_lock and we
	 * need to make sure following checks happen atomically with dirty
	 * list handling so that we don't move inodes under flush worker's
	 * hands.
	 */

> +		if (!was_dirty) {
> +			wb = locked_inode_to_wb_and_lock_list(inode);
> +			spin_lock(&inode->i_lock);
> +		}
> +
>  		/*
>  		 * If the inode is queued for writeback by flush worker, just
>  		 * update its dirty state. Once the flush worker is done with


								Honza
-- 
Jan Kara <jack@xxxxxxxx>
SUSE Labs, CR



[Index of Archives]     [Linux Ext4 Filesystem]     [Union Filesystem]     [Filesystem Testing]     [Ceph Users]     [Ecryptfs]     [NTFS 3]     [AutoFS]     [Kernel Newbies]     [Share Photos]     [Security]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux Cachefs]     [Reiser Filesystem]     [Linux RAID]     [NTFS 3]     [Samba]     [Device Mapper]     [CEPH Development]

  Powered by Linux