]> git.proxmox.com Git - mirror_ubuntu-bionic-kernel.git/blobdiff - fs/xfs/xfs_iops.c
xfs: truncate_setsize should be outside transactions
[mirror_ubuntu-bionic-kernel.git] / fs / xfs / xfs_iops.c
index 89b07e43ca28811349db39aa1ab2534de220fd87..7ee5a9d56787d79d2ecaeedc9b1184799b94dbb7 100644 (file)
@@ -808,22 +808,34 @@ xfs_setattr_size(
         */
        inode_dio_wait(inode);
 
+       /*
+        * Do all the page cache truncate work outside the transaction context
+        * as the "lock" order is page lock->log space reservation.  i.e.
+        * locking pages inside the transaction can ABBA deadlock with
+        * writeback. We have to do the VFS inode size update before we truncate
+        * the pagecache, however, to avoid racing with page faults beyond the
+        * new EOF they are not serialised against truncate operations except by
+        * page locks and size updates.
+        *
+        * Hence we are in a situation where a truncate can fail with ENOMEM
+        * from xfs_trans_reserve(), but having already truncated the in-memory
+        * version of the file (i.e. made user visible changes). There's not
+        * much we can do about this, except to hope that the caller sees ENOMEM
+        * and retries the truncate operation.
+        */
        error = -block_truncate_page(inode->i_mapping, newsize, xfs_get_blocks);
        if (error)
                return error;
+       truncate_setsize(inode, newsize);
 
        tp = xfs_trans_alloc(mp, XFS_TRANS_SETATTR_SIZE);
        error = xfs_trans_reserve(tp, &M_RES(mp)->tr_itruncate, 0, 0);
        if (error)
                goto out_trans_cancel;
 
-       truncate_setsize(inode, newsize);
-
        commit_flags = XFS_TRANS_RELEASE_LOG_RES;
        lock_flags |= XFS_ILOCK_EXCL;
-
        xfs_ilock(ip, XFS_ILOCK_EXCL);
-
        xfs_trans_ijoin(tp, ip, 0);
 
        /*