ext4: fix reserved cluster accounting at page invalidation time
authorEric Whitney <enwlinux@gmail.com>
Mon, 1 Oct 2018 18:33:24 +0000 (14:33 -0400)
committerTheodore Ts'o <tytso@mit.edu>
Mon, 1 Oct 2018 18:33:24 +0000 (14:33 -0400)
Add new code to count canceled pending cluster reservations on bigalloc
file systems and to reduce the cluster reservation count on all file
systems using delayed allocation.  This replaces old code in
ext4_da_page_release_reservations that was incorrect.

Signed-off-by: Eric Whitney <enwlinux@gmail.com>
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
fs/ext4/ext4.h
fs/ext4/extents_status.c
fs/ext4/inode.c

index 0bdbbd151d2ce480cd5c76e93e7822e52e419b07..57cbc98d730f8c72429b9c0d637d6efb28cde377 100644 (file)
@@ -2491,6 +2491,7 @@ extern int ext4_page_mkwrite(struct vm_fault *vmf);
 extern int ext4_filemap_fault(struct vm_fault *vmf);
 extern qsize_t *ext4_get_reserved_space(struct inode *inode);
 extern int ext4_get_projid(struct inode *inode, kprojid_t *projid);
+extern void ext4_da_release_space(struct inode *inode, int to_free);
 extern void ext4_da_update_reserve_space(struct inode *inode,
                                        int used, int quota_claim);
 extern int ext4_issue_zeroout(struct inode *inode, ext4_lblk_t lblk,
index c92fbf444d0878110dce8ea200251172b56701f9..2b439afafe136d4668846fb761fa599c2f4ee984 100644 (file)
@@ -1780,3 +1780,93 @@ static void __revise_pending(struct inode *inode, ext4_lblk_t lblk,
                        __remove_pending(inode, last);
        }
 }
+
+/*
+ * ext4_es_remove_blks - remove block range from extents status tree and
+ *                       reduce reservation count or cancel pending
+ *                       reservation as needed
+ *
+ * @inode - file containing range
+ * @lblk - first block in range
+ * @len - number of blocks to remove
+ *
+ */
+void ext4_es_remove_blks(struct inode *inode, ext4_lblk_t lblk,
+                        ext4_lblk_t len)
+{
+       struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb);
+       unsigned int clu_size, reserved = 0;
+       ext4_lblk_t last_lclu, first, length, remainder, last;
+       bool delonly;
+       int err = 0;
+       struct pending_reservation *pr;
+       struct ext4_pending_tree *tree;
+
+       /*
+        * Process cluster by cluster for bigalloc - there may be up to
+        * two clusters in a 4k page with a 1k block size and two blocks
+        * per cluster.  Also necessary for systems with larger page sizes
+        * and potentially larger block sizes.
+        */
+       clu_size = sbi->s_cluster_ratio;
+       last_lclu = EXT4_B2C(sbi, lblk + len - 1);
+
+       write_lock(&EXT4_I(inode)->i_es_lock);
+
+       for (first = lblk, remainder = len;
+            remainder > 0;
+            first += length, remainder -= length) {
+
+               if (EXT4_B2C(sbi, first) == last_lclu)
+                       length = remainder;
+               else
+                       length = clu_size - EXT4_LBLK_COFF(sbi, first);
+
+               /*
+                * The BH_Delay flag, which triggers calls to this function,
+                * and the contents of the extents status tree can be
+                * inconsistent due to writepages activity. So, note whether
+                * the blocks to be removed actually belong to an extent with
+                * delayed only status.
+                */
+               delonly = __es_scan_clu(inode, &ext4_es_is_delonly, first);
+
+               /*
+                * because of the writepages effect, written and unwritten
+                * blocks could be removed here
+                */
+               last = first + length - 1;
+               err = __es_remove_extent(inode, first, last);
+               if (err)
+                       ext4_warning(inode->i_sb,
+                                    "%s: couldn't remove page (err = %d)",
+                                    __func__, err);
+
+               /* non-bigalloc case: simply count the cluster for release */
+               if (sbi->s_cluster_ratio == 1 && delonly) {
+                       reserved++;
+                       continue;
+               }
+
+               /*
+                * bigalloc case: if all delayed allocated only blocks have
+                * just been removed from a cluster, either cancel a pending
+                * reservation if it exists or count a cluster for release
+                */
+               if (delonly &&
+                   !__es_scan_clu(inode, &ext4_es_is_delonly, first)) {
+                       pr = __get_pending(inode, EXT4_B2C(sbi, first));
+                       if (pr != NULL) {
+                               tree = &EXT4_I(inode)->i_pending_tree;
+                               rb_erase(&pr->rb_node, &tree->root);
+                               kmem_cache_free(ext4_pending_cachep, pr);
+                       } else {
+                               reserved++;
+                       }
+               }
+       }
+
+       write_unlock(&EXT4_I(inode)->i_es_lock);
+
+       ext4_da_release_space(inode, reserved);
+}
index 57c6dd38f0717e2c3a7ab15bc9c64058186911d2..9b69f88bdacc09b9938ac0c7b8c021ed96a32613 100644 (file)
@@ -1595,7 +1595,7 @@ static int ext4_da_reserve_space(struct inode *inode)
        return 0;       /* success */
 }
 
-static void ext4_da_release_space(struct inode *inode, int to_free)
+void ext4_da_release_space(struct inode *inode, int to_free)
 {
        struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb);
        struct ext4_inode_info *ei = EXT4_I(inode);
@@ -1634,13 +1634,11 @@ static void ext4_da_page_release_reservation(struct page *page,
                                             unsigned int offset,
                                             unsigned int length)
 {
-       int to_release = 0, contiguous_blks = 0;
+       int contiguous_blks = 0;
        struct buffer_head *head, *bh;
        unsigned int curr_off = 0;
        struct inode *inode = page->mapping->host;
-       struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb);
        unsigned int stop = offset + length;
-       int num_clusters;
        ext4_fsblk_t lblk;
 
        BUG_ON(stop > PAGE_SIZE || stop < length);
@@ -1654,7 +1652,6 @@ static void ext4_da_page_release_reservation(struct page *page,
                        break;
 
                if ((offset <= curr_off) && (buffer_delay(bh))) {
-                       to_release++;
                        contiguous_blks++;
                        clear_buffer_delay(bh);
                } else if (contiguous_blks) {
@@ -1662,7 +1659,7 @@ static void ext4_da_page_release_reservation(struct page *page,
                               (PAGE_SHIFT - inode->i_blkbits);
                        lblk += (curr_off >> inode->i_blkbits) -
                                contiguous_blks;
-                       ext4_es_remove_extent(inode, lblk, contiguous_blks);
+                       ext4_es_remove_blks(inode, lblk, contiguous_blks);
                        contiguous_blks = 0;
                }
                curr_off = next_off;
@@ -1671,21 +1668,9 @@ static void ext4_da_page_release_reservation(struct page *page,
        if (contiguous_blks) {
                lblk = page->index << (PAGE_SHIFT - inode->i_blkbits);
                lblk += (curr_off >> inode->i_blkbits) - contiguous_blks;
-               ext4_es_remove_extent(inode, lblk, contiguous_blks);
+               ext4_es_remove_blks(inode, lblk, contiguous_blks);
        }
 
-       /* If we have released all the blocks belonging to a cluster, then we
-        * need to release the reserved space for that cluster. */
-       num_clusters = EXT4_NUM_B2C(sbi, to_release);
-       while (num_clusters > 0) {
-               lblk = (page->index << (PAGE_SHIFT - inode->i_blkbits)) +
-                       ((num_clusters - 1) << sbi->s_cluster_bits);
-               if (sbi->s_cluster_ratio == 1 ||
-                   !ext4_es_scan_clu(inode, &ext4_es_is_delayed, lblk))
-                       ext4_da_release_space(inode, 1);
-
-               num_clusters--;
-       }
 }
 
 /*