From: Denis V. Lunev <[email protected]>

The balloon holds file system space that never carries data, but its
blocks are allocated, so the free space walk of xfs_ioc_trim() never
discards them, and inflating the balloon is a plain fallocate() that
does not discard either. Once the balloon is inflated over blocks that
held data, e.g. on an online shrink after files were removed, the
device below keeps them allocated: a ploop or qcow2 image keeps host
disk space the container can never use, and compaction, which is
FITRIM, does not give it back.

Walk the balloon after the free space of the data device and discard
its unwritten extents that fall into the requested range, merging
physically adjacent ones into one discard. The walk goes to the end of
the file offset space, as fallocate() with FALLOC_FL_KEEP_SIZE can
leave extents past i_size. A merged run is discarded in chunks, with a
check for fatal signals and freezing between them.

The walk holds the IOLOCK and the MMAPLOCK exclusively, after waiting
for direct I/O and flushing the page cache of the balloon. No block of
the balloon can be freed and reused under the discard, and nothing is
written to it meanwhile, so an extent that is still unwritten reads as
zeroes before and after the discard. The balloon is hidden from lookup
and opened only through XFS_IOC_OPEN_BALLOON, which takes
CAP_SYS_ADMIN, so the locks only wait for ploop inflating or shrinking
it.

An empty balloon is skipped before taking the locks. Any other one is
walked whole on every FITRIM, whatever range it asks for, as its
extent map is indexed by file offset; FITRIM is rare enough for that.
Discarding the balloon again each time is cheap: dm-ploop completes
the discard of an unmapped cluster at once.

A discard error or a failure to read the balloon inode fails FITRIM;
a balloon that is no longer a regular mutable file is skipped.

A balloon inflated before this change is released by the next FITRIM.

https://virtuozzo.atlassian.net/browse/VSTOR-153590

Feature: fs/xfs: fast online shrink support
Signed-off-by: Denis V. Lunev <[email protected]>
---

Notes:
    - The balloon is locked, flushed and walked whole on every FITRIM,
      whatever the range. This is deliberate: ploop runs FITRIM on
      compaction, about once a day. Measured on a 4-CPU VM with a balloon of
      84-99G over all the free space of a 100G image: a FITRIM after the
      first one spends 0.3-0.7s in the balloon, on ploop and qcow2, ext4 and
      xfs, for a contiguous balloon and for one in up to about 10000 pieces.
      The time goes mostly into discarding clusters that are already unmapped
      and grows with the size of the balloon and the number of its pieces.
      The locks held meanwhile only keep ploop from resizing the balloon.
      Narrowing the walk to the range would need a reverse map from blocks to
      file offsets. An empty balloon, the common case, is skipped before the
      locks are taken.
    - XFS_FSB_TO_BB(mp, minlen) could not wrap in 32 bits: xfs_ioc_trim()
      rejects a minlen above the AG size, and an AG is at most 1 TiB, 2^31
      sectors. The u64 cast keeps the check from depending on that.

 fs/xfs/xfs_discard.c | 144 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 144 insertions(+)

diff --git a/fs/xfs/xfs_discard.c b/fs/xfs/xfs_discard.c
index a6c43e4fdec3..72c6d1bb3fba 100644
--- a/fs/xfs/xfs_discard.c
+++ b/fs/xfs/xfs_discard.c
@@ -22,6 +22,10 @@
 #include "xfs_health.h"
 #include "xfs_rtbitmap.h"
 #include "xfs_rtgroup.h"
+#include "xfs_bit.h"
+#include "xfs_inode.h"
+#include "xfs_bmap.h"
+#include "xfs_super.h"
 
 /*
  * Notes on an efficient, low latency fstrim algorithm
@@ -843,6 +847,140 @@ xfs_trim_rtdev_extents(
 # define xfs_trim_rtdev_extents(...)   (-EOPNOTSUPP)
 #endif /* CONFIG_XFS_RT */
 
+#define XFS_BALLOON_DISCARD_CHUNK_BB   ((128U << 20) >> BBSHIFT)
+
+static int
+xfs_trim_balloon_run(
+       struct xfs_mount        *mp,
+       xfs_daddr_t             daddr,
+       uint64_t                len,
+       xfs_daddr_t             start,
+       xfs_daddr_t             end,
+       xfs_extlen_t            minlen)
+{
+       struct block_device     *bdev = mp->m_ddev_targp->bt_bdev;
+       unsigned int            gran = max(1U,
+                               bdev_discard_granularity(bdev) >> BBSHIFT);
+       unsigned int            chunk = roundup(XFS_BALLOON_DISCARD_CHUNK_BB, 
gran);
+       xfs_daddr_t             last = daddr + len - 1, next;
+       int                     error;
+
+       if (!len)
+               return 0;
+       if (daddr < start)
+               daddr = start;
+       if (last > end)
+               last = end;
+       if (last < daddr || last - daddr + 1 < XFS_FSB_TO_BB(mp, (u64)minlen))
+               return 0;
+
+       /*
+        * Chunks keep a long run interruptible. They end on multiples of the
+        * discard granularity, so no cluster below is split between two.
+        */
+       while (daddr <= last) {
+               u64     rem = daddr;
+
+               next = daddr + chunk - do_div(rem, chunk);
+               if (next > last + 1)
+                       next = last + 1;
+               error = blkdev_issue_discard(bdev, daddr, next - daddr,
+                               GFP_KERNEL);
+               if (error)
+                       return error;
+               daddr = next;
+               if (xfs_trim_should_stop())
+                       break;
+               cond_resched();
+       }
+
+       return 0;
+}
+
+/*
+ * Blocks of the balloon never hold data, but they are allocated, so the
+ * free space btree walk never discards them. Discard its unwritten
+ * extents, merging physically adjacent ones, so that the device below
+ * can release the space as it does for free blocks.
+ */
+static int
+xfs_trim_balloon(
+       struct xfs_mount        *mp,
+       xfs_daddr_t             start,
+       xfs_daddr_t             end,
+       xfs_extlen_t            minlen)
+{
+       u64                     balloon_ino = READ_ONCE(mp->m_balloon_ino);
+       xfs_daddr_t             run_daddr = 0;
+       uint64_t                run_len = 0;
+       struct xfs_bmbt_irec    map;
+       struct xfs_inode        *ip;
+       xfs_fileoff_t           off = 0;
+       unsigned int            lock_mode;
+       bool                    unwritten;
+       int                     nmaps, error;
+
+       if (!balloon_ino)
+               return 0;
+       ip = xfs_balloon_get(mp, balloon_ino, 0);
+       if (IS_ERR(ip))
+               return PTR_ERR(ip) == -EINVAL ? 0 : PTR_ERR(ip);
+       error = 0;
+       if (XFS_IS_REALTIME_INODE(ip) || !ip->i_nblocks)
+               goto out_rele;
+
+       /*
+        * Nobody may write the balloon meanwhile: data written into an
+        * unwritten extent would be discarded with it.
+        */
+       xfs_ilock(ip, XFS_IOLOCK_EXCL | XFS_MMAPLOCK_EXCL);
+       inode_dio_wait(VFS_I(ip));
+       error = filemap_write_and_wait(VFS_I(ip)->i_mapping);
+       if (error)
+               goto out_unlock;
+
+       while (off < XFS_MAX_FILEOFF) {
+               if (xfs_trim_should_stop())
+                       break;
+               cond_resched();
+
+               nmaps = 1;
+               lock_mode = xfs_ilock_data_map_shared(ip);
+               error = xfs_bmapi_read(ip, off, XFS_MAX_FILEOFF - off, &map,
+                               &nmaps, 0);
+               xfs_iunlock(ip, lock_mode);
+               if (error || !nmaps || !map.br_blockcount)
+                       break;
+               off = map.br_startoff + map.br_blockcount;
+
+               unwritten = xfs_bmap_is_real_extent(&map) &&
+                           map.br_state == XFS_EXT_UNWRITTEN;
+               if (unwritten && run_len && run_daddr + run_len ==
+                               XFS_FSB_TO_DADDR(mp, map.br_startblock)) {
+                       run_len += XFS_FSB_TO_BB(mp, map.br_blockcount);
+                       continue;
+               }
+
+               error = xfs_trim_balloon_run(mp, run_daddr, run_len, start,
+                               end, minlen);
+               if (error)
+                       break;
+               run_len = 0;
+               if (unwritten) {
+                       run_daddr = XFS_FSB_TO_DADDR(mp, map.br_startblock);
+                       run_len = XFS_FSB_TO_BB(mp, map.br_blockcount);
+               }
+       }
+       if (!error && !xfs_trim_should_stop())
+               error = xfs_trim_balloon_run(mp, run_daddr, run_len, start,
+                               end, minlen);
+out_unlock:
+       xfs_iunlock(ip, XFS_IOLOCK_EXCL | XFS_MMAPLOCK_EXCL);
+out_rele:
+       xfs_irele(ip);
+       return error;
+}
+
 /*
  * trim a range of the filesystem.
  *
@@ -915,6 +1053,12 @@ xfs_ioc_trim(
                error = xfs_trim_datadev_extents(mp, start, end, minlen);
                if (error)
                        last_error = error;
+
+               if (!xfs_trim_should_stop()) {
+                       error = xfs_trim_balloon(mp, start, end, minlen);
+                       if (error)
+                               last_error = error;
+               }
        }
 
        if (rt_bdev && !xfs_trim_should_stop()) {
-- 
2.53.0

_______________________________________________
Devel mailing list
[email protected]
https://lists.openvz.org/mailman/listinfo/devel

Reply via email to