const u64 size,
const u8 comp_type,
char *inline_data,
- struct btrfs_trans_handle **trans_out)
+ struct btrfs_trans_handle **trans_out,
+ bool *copied_to_page)
{
struct btrfs_root *root = inode->root;
struct btrfs_fs_info *fs_info = root->fs_info;
btrfs_delalloc_release_space(inode, data_reserved,
new_key->offset,
fs_info->sectorsize, true);
- } else if (new_key->offset + datal > i_size_read(&inode->vfs_inode)) {
- /*
- * Keep i_size ahead of a folio we dirtied beyond EOF, so that
- * writeback sends it to disk instead of discarding it through
- * folio invalidation (see commit b48c980b6a7e).
- */
- i_size_write(&inode->vfs_inode, new_key->offset + datal);
+ } else {
+ *copied_to_page = true;
+ if (new_key->offset + datal > i_size_read(&inode->vfs_inode)) {
+ /*
+ * Keep i_size ahead of a folio we dirtied beyond EOF,
+ * so that writeback sends it to disk instead of
+ * discarding it through folio invalidation (see commit
+ * b48c980b6a7e).
+ */
+ i_size_write(&inode->vfs_inode, new_key->offset + datal);
+ }
}
btrfs_delalloc_release_extents(inode, fs_info->sectorsize);
u64 datao = 0, datal = 0;
u8 comp;
u64 drop_start;
+ bool copied_to_page = false;
/* Note the key will change type as we walk through the tree */
ret = btrfs_search_slot(NULL, src->root, &key, path, 0, 0);
ret = clone_copy_inline_extent(inode, path, &new_key,
drop_start, datal, size,
- comp, buf, &trans);
+ comp, buf, &trans,
+ &copied_to_page);
if (ret)
goto out;
}
destoff, olen, no_time_update);
if (ret)
goto out;
+ /*
+ * Copying an inline extent's data into a folio left delalloc
+ * inside our caller's locked destination range. The rest of
+ * the clone starts transactions and reserves space, which must
+ * not block while the lock covers dirty data: under the
+ * flushoncommit mount option, a transaction commit (including
+ * one issued to satisfy a metadata reservation ticket) flushes
+ * dirty inodes through the generic writeback path, and its
+ * flusher would block forever on our extent lock while we wait
+ * for that same commit or ticket. Unlock the dirtied block
+ * now: every byte still locked is clean, so writeback never
+ * needs our lock, and the inode's i_rwsem (held for the whole
+ * remap) keeps writers away from the unlocked block until the
+ * clone finishes. The inode item for the copied data was
+ * already updated by clone_finish_inode_update() above.
+ */
+ if (copied_to_page)
+ btrfs_unlock_extent(&inode->io_tree,
+ new_key.offset,
+ ALIGN(new_key.offset + datal,
+ fs_info->sectorsize) - 1,
+ NULL);
if (new_key.offset + datal >= destoff + len)
break;
* we are safe from concurrency with relocation of source extents
* because we have already locked the inode's i_mmap_lock in exclusive
* mode.
+ *
+ * The locked range must also have no delalloc: the clone below starts
+ * transactions and reserves space while holding the lock, and under
+ * the flushoncommit mount option a transaction commit (including one
+ * issued to satisfy a metadata reservation ticket) flushes dirty
+ * inodes through the generic writeback path, whose flusher blocks on
+ * our extent lock while we may be blocked waiting on that very commit
+ * or ticket - a deadlock. The ranges were flushed before locking,
+ * but compressed writeback is asynchronous and may leave delalloc
+ * behind by the time we lock; both inodes' i_rwsem are held, so once
+ * the range is clean and locked nothing can dirty it again (the only
+ * exception, copying an inline extent's data into a folio, unlocks
+ * the dirtied block before the clone continues - see btrfs_clone()).
*/
- btrfs_lock_extent(&dst->io_tree, dst_loff, end, &cached_state);
+ for (int i = 0; ; i++) {
+ btrfs_lock_extent(&dst->io_tree, dst_loff, end, &cached_state);
+ if (!btrfs_test_range_bit_exists(&dst->io_tree, dst_loff, end,
+ EXTENT_DELALLOC))
+ break;
+ btrfs_unlock_extent(&dst->io_tree, dst_loff, end, &cached_state);
+ if (i == 2)
+ return -EAGAIN;
+ ret = btrfs_wait_ordered_range(dst, dst_loff, end + 1 - dst_loff);
+ if (ret)
+ return ret;
+ }
ret = btrfs_clone(src, dst, loff, len, ALIGN(len, bs), dst_loff, true);
btrfs_unlock_extent(&dst->io_tree, dst_loff, end, &cached_state);
* because we have already locked the inode's i_mmap_lock in exclusive
* mode.
*/
+ /*
+ * The locked range must also have no delalloc before the clone starts
+ * transactions or reserves space under the lock - see the comment in
+ * btrfs_extent_same_range() for the flushoncommit deadlock this
+ * prevents.
+ */
end = round_up(destoff + len, bs) - 1;
- btrfs_lock_extent(&inode->io_tree, destoff, end, &cached_state);
+ for (int i = 0; ; i++) {
+ btrfs_lock_extent(&inode->io_tree, destoff, end, &cached_state);
+ if (!btrfs_test_range_bit_exists(&inode->io_tree, destoff, end,
+ EXTENT_DELALLOC))
+ break;
+ btrfs_unlock_extent(&inode->io_tree, destoff, end, &cached_state);
+ if (i == 2)
+ return -EAGAIN;
+ ret = btrfs_wait_ordered_range(inode, destoff, end + 1 - destoff);
+ if (ret)
+ return ret;
+ }
ret = btrfs_clone(src, inode, off, olen, len, destoff, false);
btrfs_unlock_extent(&inode->io_tree, destoff, end, &cached_state);
if (ret < 0)