Skip to content

Commit 76a6d5c

Browse files
fdmananakdave
authored andcommitted
btrfs: fix deadlock when cloning inline extents and low on available space
There are a few cases where cloning an inline extent requires copying data into a page of the destination inode. For these cases we are allocating the required data and metadata space while holding a leaf locked. This can result in a deadlock when we are low on available space because allocating the space may flush delalloc and two deadlock scenarios can happen: 1) When starting writeback for an inode with a very small dirty range that fits in an inline extent, we deadlock during the writeback when trying to insert the inline extent, at cow_file_range_inline(), if the extent is going to be located in the leaf for which we are already holding a read lock; 2) After successfully starting writeback, for non-inline extent cases, the async reclaim thread will hang waiting for an ordered extent to complete if the ordered extent completion needs to modify the leaf for which the clone task is holding a read lock (for adding or replacing file extent items). So the cloning task will wait forever on the async reclaim thread to make progress, which in turn is waiting for the ordered extent completion which in turn is waiting to acquire a write lock on the same leaf. So fix this by making sure we release the path (and therefore the leaf) every time we need to copy the inline extent's data into a page of the destination inode, as by that time we do not need to have the leaf locked. Fixes: 05a5a76 ("Btrfs: implement full reflink support for inline extents") CC: stable@vger.kernel.org # 5.10+ Signed-off-by: Filipe Manana <fdmanana@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
1 parent ea7036d commit 76a6d5c

1 file changed

Lines changed: 22 additions & 16 deletions

File tree

fs/btrfs/reflink.c

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -207,10 +207,7 @@ static int clone_copy_inline_extent(struct inode *dst,
207207
* inline extent's data to the page.
208208
*/
209209
ASSERT(key.offset > 0);
210-
ret = copy_inline_to_page(BTRFS_I(dst), new_key->offset,
211-
inline_data, size, datal,
212-
comp_type);
213-
goto out;
210+
goto copy_to_page;
214211
}
215212
} else if (i_size_read(dst) <= datal) {
216213
struct btrfs_file_extent_item *ei;
@@ -226,13 +223,10 @@ static int clone_copy_inline_extent(struct inode *dst,
226223
BTRFS_FILE_EXTENT_INLINE)
227224
goto copy_inline_extent;
228225

229-
ret = copy_inline_to_page(BTRFS_I(dst), new_key->offset,
230-
inline_data, size, datal, comp_type);
231-
goto out;
226+
goto copy_to_page;
232227
}
233228

234229
copy_inline_extent:
235-
ret = 0;
236230
/*
237231
* We have no extent items, or we have an extent at offset 0 which may
238232
* or may not be inlined. All these cases are dealt the same way.
@@ -244,11 +238,13 @@ static int clone_copy_inline_extent(struct inode *dst,
244238
* clone. Deal with all these cases by copying the inline extent
245239
* data into the respective page at the destination inode.
246240
*/
247-
ret = copy_inline_to_page(BTRFS_I(dst), new_key->offset,
248-
inline_data, size, datal, comp_type);
249-
goto out;
241+
goto copy_to_page;
250242
}
251243

244+
/*
245+
* Release path before starting a new transaction so we don't hold locks
246+
* that would confuse lockdep.
247+
*/
252248
btrfs_release_path(path);
253249
/*
254250
* If we end up here it means were copy the inline extent into a leaf
@@ -285,11 +281,6 @@ static int clone_copy_inline_extent(struct inode *dst,
285281
ret = btrfs_inode_set_file_extent_range(BTRFS_I(dst), 0, aligned_end);
286282
out:
287283
if (!ret && !trans) {
288-
/*
289-
* Release path before starting a new transaction so we don't
290-
* hold locks that would confuse lockdep.
291-
*/
292-
btrfs_release_path(path);
293284
/*
294285
* No transaction here means we copied the inline extent into a
295286
* page of the destination inode.
@@ -310,6 +301,21 @@ static int clone_copy_inline_extent(struct inode *dst,
310301
*trans_out = trans;
311302

312303
return ret;
304+
305+
copy_to_page:
306+
/*
307+
* Release our path because we don't need it anymore and also because
308+
* copy_inline_to_page() needs to reserve data and metadata, which may
309+
* need to flush delalloc when we are low on available space and
310+
* therefore cause a deadlock if writeback of an inline extent needs to
311+
* write to the same leaf or an ordered extent completion needs to write
312+
* to the same leaf.
313+
*/
314+
btrfs_release_path(path);
315+
316+
ret = copy_inline_to_page(BTRFS_I(dst), new_key->offset,
317+
inline_data, size, datal, comp_type);
318+
goto out;
313319
}
314320

315321
/**

0 commit comments

Comments
 (0)