* @min_type:          The minimum key type to remove. All keys with a type
  *                     greater than this value are removed and all keys with
  *                     this type are removed only if their offset is >= @new_size.
+ * @extents_found:     Output parameter that will contain the number of file
+ *                     extent items that were removed or adjusted to the new
+ *                     inode i_size. The caller is responsible for initializing
+ *                     the counter. Also, it can be NULL if the caller does not
+ *                     need this counter.
  *
  * Remove all keys associated with the inode from the given root that have a key
  * with a type greater than or equals to @min_type. When @min_type has a value of
 int btrfs_truncate_inode_items(struct btrfs_trans_handle *trans,
                               struct btrfs_root *root,
                               struct btrfs_inode *inode,
-                              u64 new_size, u32 min_type)
+                              u64 new_size, u32 min_type,
+                              u64 *extents_found)
 {
        struct btrfs_fs_info *fs_info = root->fs_info;
        struct btrfs_path *path;
                if (found_type != BTRFS_EXTENT_DATA_KEY)
                        goto delete;
 
+               if (extents_found != NULL)
+                       (*extents_found)++;
+
                if (extent_type != BTRFS_FILE_EXTENT_INLINE) {
                        u64 num_dec;
 
                trans->block_rsv = rsv;
 
                ret = btrfs_truncate_inode_items(trans, root, BTRFS_I(inode),
-                                                0, 0);
+                                                0, 0, NULL);
                trans->block_rsv = &fs_info->trans_block_rsv;
                btrfs_end_transaction(trans);
                btrfs_btree_balance_dirty(fs_info);
        struct btrfs_trans_handle *trans;
        u64 mask = fs_info->sectorsize - 1;
        u64 min_size = btrfs_calc_metadata_size(fs_info, 1);
+       u64 extents_found = 0;
 
        if (!skip_writeback) {
                ret = btrfs_wait_ordered_range(inode, inode->i_size & (~mask),
                                      min_size, false);
        BUG_ON(ret);
 
-       /*
-        * So if we truncate and then write and fsync we normally would just
-        * write the extents that changed, which is a problem if we need to
-        * first truncate that entire inode.  So set this flag so we write out
-        * all of the extents in the inode to the sync log so we're completely
-        * safe.
-        */
-       set_bit(BTRFS_INODE_NEEDS_FULL_SYNC, &BTRFS_I(inode)->runtime_flags);
        trans->block_rsv = rsv;
 
        while (1) {
                ret = btrfs_truncate_inode_items(trans, root, BTRFS_I(inode),
                                                 inode->i_size,
-                                                BTRFS_EXTENT_DATA_KEY);
+                                                BTRFS_EXTENT_DATA_KEY,
+                                                &extents_found);
                trans->block_rsv = &fs_info->trans_block_rsv;
                if (ret != -ENOSPC && ret != -EAGAIN)
                        break;
        }
 out:
        btrfs_free_block_rsv(fs_info, rsv);
+       /*
+        * So if we truncate and then write and fsync we normally would just
+        * write the extents that changed, which is a problem if we need to
+        * first truncate that entire inode.  So set this flag so we write out
+        * all of the extents in the inode to the sync log so we're completely
+        * safe.
+        *
+        * If no extents were dropped or trimmed we don't need to force the next
+        * fsync to truncate all the inode's items from the log and re-log them
+        * all. This means the truncate operation did not change the file size,
+        * or changed it to a smaller size but there was only an implicit hole
+        * between the old i_size and the new i_size, and there were no prealloc
+        * extents beyond i_size to drop.
+        */
+       if (extents_found > 0)
+               set_bit(BTRFS_INODE_NEEDS_FULL_SYNC, &BTRFS_I(inode)->runtime_flags);
 
        return ret;
 }