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.

Every FITRIM discards the balloon again, which costs little: 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]>
---
 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 93943e698eea..b9b5a28b0d96 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
@@ -833,6 +837,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, 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))
+               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.
  *
@@ -905,6 +1043,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