if (err < 0)
                                goto out;
 
-               } else if (sbi->s_cluster_ratio > 1 && end >= ex_end) {
+               } else if (sbi->s_cluster_ratio > 1 && end >= ex_end &&
+                          partial.state == initial) {
                        /*
-                        * If there's an extent to the right its first cluster
-                        * contains the immediate right boundary of the
-                        * truncated/punched region.  Set partial_cluster to
-                        * its negative value so it won't be freed if shared
-                        * with the current extent.  The end < ee_block case
-                        * is handled in ext4_ext_rm_leaf().
+                        * If we're punching, there's an extent to the right.
+                        * If the partial cluster hasn't been set, set it to
+                        * that extent's first cluster and its state to nofree
+                        * so it won't be freed should it contain blocks to be
+                        * removed. If it's already set (tofree/nofree), we're
+                        * retrying and keep the original partial cluster info
+                        * so a cluster marked tofree as a result of earlier
+                        * extent removal is not lost.
                         */
                        lblk = ex_end + 1;
                        err = ext4_ext_search_right(inode, path, &lblk, &pblk,