]> git.ipfire.org Git - thirdparty/xfsprogs-dev.git/commitdiff
xfs: add an inode item lock
authorDave Chinner <dchinner@redhat.com>
Fri, 4 Sep 2020 19:56:20 +0000 (15:56 -0400)
committerEric Sandeen <sandeen@sandeen.net>
Fri, 4 Sep 2020 19:56:20 +0000 (15:56 -0400)
Source kernel commit: 1319ebefd6ed7a9988b7b4bc9317fbcf61a28bfc

The inode log item is kind of special in that it can be aggregating
new changes in memory at the same time time existing changes are
being written back to disk. This means there are fields in the log
item that are accessed concurrently from contexts that don't share
any locking at all.

e.g. updating ili_last_fields occurs at flush time under the
ILOCK_EXCL and flush lock at flush time, under the flush lock at IO
completion time, and is read under the ILOCK_EXCL when the inode is
logged.  Hence there is no actual serialisation between reading the
field during logging of the inode in transactions vs clearing the
field in IO completion.

We currently get away with this by the fact that we are only
clearing fields in IO completion, and nothing bad happens if we
accidentally log more of the inode than we actually modify. Worst
case is we consume a tiny bit more memory and log bandwidth.

However, if we want to do more complex state manipulations on the
log item that requires updates at all three of these potential
locations, we need to have some mechanism of serialising those
operations. To do this, introduce a spinlock into the log item to
serialise internal state.

This could be done via the xfs_inode i_flags_lock, but this then
leads to potential lock inversion issues where inode flag updates
need to occur inside locks that best nest inside the inode log item
locks (e.g. marking inodes stale during inode cluster freeing).
Using a separate spinlock avoids these sorts of problems and
simplifies future code.

This does not touch the use of ili_fields in the item formatting
code - that is entirely protected by the ILOCK_EXCL at this point in
time, so it remains untouched.

Signed-off-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Brian Foster <bfoster@redhat.com>
Reviewed-by: Darrick J. Wong <darrick.wong@oracle.com>
Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
Signed-off-by: Eric Sandeen <sandeen@sandeen.net>
libxfs/xfs_trans_inode.c

index 02b8176de11ce6c2a386c0e90781c50492cfbc79..aaa310f783414e55cb027681e8b6c274a58142b2 100644 (file)
@@ -79,16 +79,20 @@ xfs_trans_ichgtime(
  */
 void
 xfs_trans_log_inode(
-       xfs_trans_t     *tp,
-       xfs_inode_t     *ip,
-       uint            flags)
+       struct xfs_trans        *tp,
+       struct xfs_inode        *ip,
+       uint                    flags)
 {
-       struct inode    *inode = VFS_I(ip);
+       struct xfs_inode_log_item *iip = ip->i_itemp;
+       struct inode            *inode = VFS_I(ip);
+       uint                    iversion_flags = 0;
 
-       ASSERT(ip->i_itemp != NULL);
+       ASSERT(iip);
        ASSERT(xfs_isilocked(ip, XFS_ILOCK_EXCL));
        ASSERT(!xfs_iflags_test(ip, XFS_ISTALE));
 
+       tp->t_flags |= XFS_TRANS_DIRTY;
+
        /*
         * Don't bother with i_lock for the I_DIRTY_TIME check here, as races
         * don't matter - we either will need an extra transaction in 24 hours
@@ -101,15 +105,6 @@ xfs_trans_log_inode(
                spin_unlock(&inode->i_lock);
        }
 
-       /*
-        * Record the specific change for fdatasync optimisation. This
-        * allows fdatasync to skip log forces for inodes that are only
-        * timestamp dirty. We do this before the change count so that
-        * the core being logged in this case does not impact on fdatasync
-        * behaviour.
-        */
-       ip->i_itemp->ili_fsync_fields |= flags;
-
        /*
         * First time we log the inode in a transaction, bump the inode change
         * counter if it is configured for this to occur. While we have the
@@ -119,23 +114,28 @@ xfs_trans_log_inode(
         * set however, then go ahead and bump the i_version counter
         * unconditionally.
         */
-       if (!test_and_set_bit(XFS_LI_DIRTY, &ip->i_itemp->ili_item.li_flags) &&
-           IS_I_VERSION(VFS_I(ip))) {
-               if (inode_maybe_inc_iversion(VFS_I(ip), flags & XFS_ILOG_CORE))
-                       flags |= XFS_ILOG_CORE;
+       if (!test_and_set_bit(XFS_LI_DIRTY, &iip->ili_item.li_flags)) {
+               if (IS_I_VERSION(inode) &&
+                   inode_maybe_inc_iversion(inode, flags & XFS_ILOG_CORE))
+                       iversion_flags = XFS_ILOG_CORE;
        }
 
-       tp->t_flags |= XFS_TRANS_DIRTY;
+       /*
+        * Record the specific change for fdatasync optimisation. This allows
+        * fdatasync to skip log forces for inodes that are only timestamp
+        * dirty.
+        */
+       spin_lock(&iip->ili_lock);
+       iip->ili_fsync_fields |= flags;
 
        /*
-        * Always OR in the bits from the ili_last_fields field.
-        * This is to coordinate with the xfs_iflush() and xfs_iflush_done()
-        * routines in the eventual clearing of the ili_fields bits.
-        * See the big comment in xfs_iflush() for an explanation of
-        * this coordination mechanism.
+        * Always OR in the bits from the ili_last_fields field.  This is to
+        * coordinate with the xfs_iflush() and xfs_iflush_done() routines in
+        * the eventual clearing of the ili_fields bits.  See the big comment in
+        * xfs_iflush() for an explanation of this coordination mechanism.
         */
-       flags |= ip->i_itemp->ili_last_fields;
-       ip->i_itemp->ili_fields |= flags;
+       iip->ili_fields |= (flags | iip->ili_last_fields | iversion_flags);
+       spin_unlock(&iip->ili_lock);
 }
 
 int