Re: [PATCH v3] 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 Thu, May 5, 2022 at 3:38 AM Jan Kara <jack@xxxxxxx> wrote:
>
> On Wed 04-05-22 11:25:14, 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.
> >
> > Fixes: b35250c0816c ("writeback: Protect inode->i_io_list with inode->i_lock")
> > Signed-off-by: Jchao Sun <sunjunchao2870@xxxxxxxxx>
>
> Almost there :). A few comments below:
>
> > @@ -2402,6 +2404,9 @@ void __mark_inode_dirty(struct inode *inode, int flags)
> >                       inode->i_state &= ~I_DIRTY_TIME;
> >               inode->i_state |= flags;
> >
> > +             wb = locked_inode_to_wb_and_lock_list(inode);
> > +             spin_lock(&inode->i_lock);
> > +
>
>
> > We don't want to lock wb->list_lock if the inode was already dirty (which
> > is a common path). So you want something like:
> >
> >                 if (was_dirty)
> >                         wb = locked_inode_to_wb_and_lock_list(inode);
> >
> > (and initialize wb to NULL to make sure it does not contain stale value).

I'm a little confused about here. The logic of the current source tree
is like this:
                       if (!was_dirty) {
                               struct bdi_writeback *wb;
                               wb =  locked_inode_to_wb_and_lock_list(inode);
                               ...
                               dirty_list = &wb-> b_dirty_time;
                               assert_spin_locked(&wb->list_lock);
                       }
The logic is the opposite of the logic in the comments, and it seems
like that wb will
absolutely not be NULL.
Why is this? What is the difference between them?

If run with the logic in the comments, wb will only be initialized
when was_dirty != 0,
suppose was_dirty is 0, wb will not be initialized, and continue
running, will hit
                      if (!was_dirty) {
                              dirty_list = &wb->b_dirty_time;
                      }
will hit NULL pointer.
Is there something I have missed?

>
> > @@ -2409,7 +2414,7 @@ void __mark_inode_dirty(struct inode *inode, int flags)
> >                * list, based upon its state.
> >                */
> >               if (inode->i_state & I_SYNC_QUEUED)
> > -                     goto out_unlock_inode;
> > +                     goto out_unlock;
> >
> >               /*
> >                * Only add valid (hashed) inodes to the superblock's
> > @@ -2417,22 +2422,19 @@ void __mark_inode_dirty(struct inode *inode, int flags)
> >                */
> >               if (!S_ISBLK(inode->i_mode)) {
> >                       if (inode_unhashed(inode))
> > -                             goto out_unlock_inode;
> > +                             goto out_unlock;
> >               }
> >               if (inode->i_state & I_FREEING)
> > -                     goto out_unlock_inode;
> > +                     goto out_unlock;
> >
> >               /*
> >                * If the inode was already on b_dirty/b_io/b_more_io, don't
> >                * reposition it (that would break b_dirty time-ordering).
> >                */
> >               if (!was_dirty) {
> > -                     struct bdi_writeback *wb;
> >                       struct list_head *dirty_list;
> >                       bool wakeup_bdi = false;
> >
> > -                     wb = locked_inode_to_wb_and_lock_list(inode);
> > -
> >                       inode->dirtied_when = jiffies;
> >                       if (dirtytime)
> >                               inode->dirtied_time_when = jiffies;
> > @@ -2446,6 +2448,7 @@ void __mark_inode_dirty(struct inode *inode, int flags)
> >                                                              dirty_list);
> >
> >                       spin_unlock(&wb->list_lock);
> > +                     spin_unlock(&inode->i_lock);
> >                       trace_writeback_dirty_inode_enqueue(inode);
> >
> >                       /*
> > @@ -2460,6 +2463,8 @@ void __mark_inode_dirty(struct inode *inode, int flags)
> >                       return;
> >               }
> >       }
>
> > > +out_unlock:
> > > +     spin_unlock(&wb->list_lock);

Ouch, this is so obvious now that you mention it. Really stupid
mistake on my side. It surprised me that local compile do not have warnings..
>
> wb->list lock will not be locked in some cases here. So you have to be more
> careful about when you need to unlock it. Probably something like:
>
>         if (wb)
>                 spin_unlock(&wb->list_lock);
>
> and you can put this at the end inside the block "if ((inode->i_state &
> flags) != flags)".
>
>
> > Also I'd note it is good to test your changes (it would likely uncover the
> > locking problem). For these filesystem related things xfstests are useful:
> >
> > https://git.kernel.org/pub/scm/fs/xfs/xfstests-dev.git/

Thanks a lot! I'll test this patch for ext4 fs using this tool.
>
>                                                                 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