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

The balloon holds file system space that never carries data, but its
blocks are allocated, so ext4_trim_all_free() 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 in ext4_trim_fs() and discard its
unwritten extents that fall into the requested range. The walk goes to
the end of the extent tree, as fallocate() with FALLOC_FL_KEEP_SIZE can
leave extents past i_size. Physically adjacent extents are merged into
one discard, otherwise every cut at the unwritten extent length limit
leaves a partly covered cluster of the image below. A merged run is
discarded in chunks, with a check for fatal signals and freezing
between them.

The walk holds i_rwsem and the invalidate lock exclusively, after
waiting for direct I/O and flushing the page cache of the balloon, as
punch hole does. 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
EXT4_IOC_OPEN_BALLOON, which takes CAP_SYS_ADMIN, so the lock only
waits 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. The balloon is
not counted in the trimmed length reported to userspace, and a discard
error fails FITRIM.

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

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

Feature: fs/ext4: fast online shrink support
Signed-off-by: Denis V. Lunev <[email protected]>
---
 fs/ext4/mballoc.c | 126 ++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 126 insertions(+)

diff --git a/fs/ext4/mballoc.c b/fs/ext4/mballoc.c
index aaf2c24c514c..5fb87753975d 100644
--- a/fs/ext4/mballoc.c
+++ b/fs/ext4/mballoc.c
@@ -7009,6 +7009,126 @@ ext4_trim_all_free(struct super_block *sb, ext4_group_t 
group,
        return ret;
 }
 
+#define EXT4_BALLOON_DISCARD_CHUNK     (128U << 20)
+
+static int ext4_trim_balloon_run(struct super_block *sb, ext4_fsblk_t pblk,
+                                ext4_fsblk_t len, ext4_fsblk_t start,
+                                ext4_fsblk_t end, ext4_fsblk_t minblocks)
+{
+       unsigned int gran = max_t(unsigned int, 1,
+                       bdev_discard_granularity(sb->s_bdev) >> 
sb->s_blocksize_bits);
+       unsigned int chunk = roundup(EXT4_BALLOON_DISCARD_CHUNK >>
+                                    sb->s_blocksize_bits, gran);
+       ext4_fsblk_t last = pblk + len - 1, next;
+       int ret;
+
+       if (!len)
+               return 0;
+       if (pblk < start)
+               pblk = start;
+       if (last > end)
+               last = end;
+       if (last < pblk || last - pblk + 1 < minblocks)
+               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 (pblk <= last) {
+               u64 rem = pblk;
+
+               next = pblk + chunk - do_div(rem, chunk);
+               if (next > last + 1)
+                       next = last + 1;
+               trace_ext4_discard_blocks(sb, pblk, next - pblk);
+               ret = sb_issue_discard(sb, pblk, next - pblk, GFP_NOFS, 0);
+               if (ret)
+                       return ret;
+               pblk = next;
+               if (ext4_trim_interrupted())
+                       break;
+               cond_resched();
+       }
+
+       return 0;
+}
+
+/*
+ * Blocks of the balloon never hold data, but they are allocated, so
+ * ext4_trim_all_free() 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 ext4_trim_balloon(struct super_block *sb, ext4_fsblk_t start,
+                            ext4_fsblk_t end, ext4_fsblk_t minblocks)
+{
+       struct ext4_sb_info *sbi = EXT4_SB(sb);
+       ext4_fsblk_t run_pblk = 0, run_len = 0;
+       struct ext4_map_blocks map;
+       struct inode *inode = NULL;
+       ext4_lblk_t lblk;
+       bool unwritten;
+       int ret, err;
+
+       /* ext4_load_balloon() replaces s_balloon_ino under s_umount */
+       down_read(&sb->s_umount);
+       if (sbi->s_balloon_ino)
+               inode = igrab(sbi->s_balloon_ino);
+       up_read(&sb->s_umount);
+       if (!inode)
+               return 0;
+
+       /*
+        * Nobody may write the balloon meanwhile: data written into an
+        * unwritten extent would be discarded with it.
+        */
+       inode_lock(inode);
+       inode_dio_wait(inode);
+       filemap_invalidate_lock(inode->i_mapping);
+       ret = filemap_write_and_wait(inode->i_mapping);
+       if (ret)
+               goto out;
+
+       for (lblk = 0; lblk < EXT_MAX_BLOCKS; lblk += map.m_len) {
+               if (ext4_trim_interrupted())
+                       break;
+               cond_resched();
+
+               map.m_lblk = lblk;
+               map.m_len = EXT_MAX_BLOCKS - lblk;
+               err = ext4_map_blocks(NULL, inode, &map, 0);
+               if (err < 0) {
+                       ret = err;
+                       break;
+               }
+               if (!map.m_len)
+                       break;
+
+               unwritten = err > 0 && (map.m_flags & EXT4_MAP_UNWRITTEN);
+               if (unwritten && run_len && run_pblk + run_len == map.m_pblk) {
+                       run_len += map.m_len;
+                       continue;
+               }
+
+               ret = ext4_trim_balloon_run(sb, run_pblk, run_len, start, end,
+                                           minblocks);
+               if (ret)
+                       break;
+               run_pblk = map.m_pblk;
+               run_len = unwritten ? map.m_len : 0;
+       }
+       if (!ret && !ext4_trim_interrupted())
+               ret = ext4_trim_balloon_run(sb, run_pblk, run_len, start, end,
+                                           minblocks);
+out:
+       filemap_invalidate_unlock(inode->i_mapping);
+       inode_unlock(inode);
+       iput(inode);
+
+       return ret;
+}
+
 /**
  * ext4_trim_fs() -- trim ioctl handle function
  * @sb:                        superblock for filesystem
@@ -7028,6 +7148,7 @@ int ext4_trim_fs(struct super_block *sb, struct 
fstrim_range *range)
        ext4_group_t group, first_group, last_group;
        ext4_grpblk_t cnt = 0, first_cluster, last_cluster;
        uint64_t start, end, minlen, trimmed = 0;
+       ext4_fsblk_t last_blk;
        ext4_fsblk_t first_data_blk =
                        le32_to_cpu(EXT4_SB(sb)->s_es->s_first_data_block);
        ext4_fsblk_t max_blks = ext4_blocks_count(EXT4_SB(sb)->s_es);
@@ -7061,6 +7182,7 @@ int ext4_trim_fs(struct super_block *sb, struct 
fstrim_range *range)
                                     &first_group, &first_cluster);
        ext4_get_group_no_and_offset(sb, (ext4_fsblk_t) end,
                                     &last_group, &last_cluster);
+       last_blk = end;
 
        /* end now represents the last cluster to discard in this group */
        end = EXT4_CLUSTERS_PER_GROUP(sb) - 1;
@@ -7103,6 +7225,10 @@ int ext4_trim_fs(struct super_block *sb, struct 
fstrim_range *range)
                first_cluster = 0;
        }
 
+       if (!ret && !ext4_trim_interrupted())
+               ret = ext4_trim_balloon(sb, start, last_blk,
+                                       EXT4_C2B(EXT4_SB(sb), minlen));
+
        if (!ret)
                EXT4_SB(sb)->s_last_trim_minblks = minlen;
 
-- 
2.53.0

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

Reply via email to