Skip to contentSkip to conversation

xfs: recheck reflink / dirty page status before freeing CoW reservations

PATCH Superseded Darrick J. Wong 3 participants 11 messages Jan 10, 2018 – Jan 17, 2018 XFS filesystem

lore
Share & tools
lore
#0 lore raw

PATCH 1/1 xfs: recheck reflink / dirty page status before freeing CoW reservations

From: Darrick J. Wong <[email protected]>

Eryu Guan reported seeing occasional hangs when running generic/269 with a new fsstress that supports clonerange/deduperange. The cause of this hang is an infinite loop when we convert the CoW fork extents from unwritten to real just prior to writing the pages out; the infinite loop happens because there's nothing in the CoW fork to convert, and so it spins forever.

The underlying issue here is that when we go to perform these CoW fork conversions, we're supposed to have an extent waiting for us, but the low space CoW reaper has snuck in and blown them away! There are four conditions that can dissuade the reaper from touching our file -- no reflink iflag; dirty page cache; writeback in progress; or directio in progress. We check the four conditions prior to taking the locks, but we neglect to recheck them once we have the locks, which is how we end up whacking the writeback that's in progress.

Therefore, refactor the four checks into a helper function and call it once again once we have the locks to make sure we really want to reap the inode. While we're at it, add an ASSERT for this weird condition so that we'll fail noisily if we ever screw this up again.

---
 fs/xfs/libxfs/xfs_bmap.c |    7 +++++
 fs/xfs/xfs_icache.c      |   61 +++++++++++++++++++++++++++++-----------------
 2 files changed, 46 insertions(+), 22 deletions(-)
fs/xfs/libxfs/xfs_bmap.c +7 -0
index a01cef4..7bd933f 100644
@@ -4311,6 +4311,13 @@ xfs_bmapi_write(
 	while (bno < end && n < *nmap) {
 		bool			need_alloc = false, wasdelay = false;
 
+		/*
+		 * CoW fork conversions should /never/ hit EOF.  There should
+		 * always be something for us to work on.
+		 */
+		ASSERT(!eof || !(flags & XFS_BMAPI_CONVERT) ||
+			       !(flags & XFS_BMAPI_COWFORK));
+
 		/* in hole or beyoned EOF? */
 		if (eof || bma.got.br_startoff > bno) {
 			if (flags & XFS_BMAPI_DELALLOC) {
fs/xfs/xfs_icache.c +39 -22

81 lines not on this page · raw message

Tested-by on v1 1/1
#1 lore raw
Quoted 18 lines · Darrick J. Wong

From: Darrick J. Wong <[email protected]>

Eryu Guan reported seeing occasional hangs when running generic/269 with a new fsstress that supports clonerange/deduperange. The cause of this hang is an infinite loop when we convert the CoW fork extents from unwritten to real just prior to writing the pages out; the infinite loop happens because there's nothing in the CoW fork to convert, and so it spins forever.

The underlying issue here is that when we go to perform these CoW fork conversions, we're supposed to have an extent waiting for us, but the low space CoW reaper has snuck in and blown them away! There are four conditions that can dissuade the reaper from touching our file -- no reflink iflag; dirty page cache; writeback in progress; or directio in progress. We check the four conditions prior to taking the locks, but we neglect to recheck them once we have the locks, which is how we end up whacking the writeback that's in progress.

Quoted 7 lines

Therefore, refactor the four checks into a helper function and call it once again once we have the locks to make sure we really want to reap the inode. While we're at it, add an ASSERT for this weird condition so that we'll fail noisily if we ever screw this up again.

I applied this patch on top of v4.15-rc5 kernel, and ran generic/083 generic/269 and generic/270 (where I hit the soft lockup and hang before) multiple times and tests all passed. I also ran all tests in 'enospc' group on 1k/2k/4k XFS with reflink enabled, tests passed too. So

Thanks!

Eryu

#2 lore raw
Quoted 27 lines · Eryu Guan
Quoted 18 lines · Darrick J. Wong

From: Darrick J. Wong <[email protected]>

Eryu Guan reported seeing occasional hangs when running generic/269 with a new fsstress that supports clonerange/deduperange. The cause of this hang is an infinite loop when we convert the CoW fork extents from unwritten to real just prior to writing the pages out; the infinite loop happens because there's nothing in the CoW fork to convert, and so it spins forever.

The underlying issue here is that when we go to perform these CoW fork conversions, we're supposed to have an extent waiting for us, but the low space CoW reaper has snuck in and blown them away! There are four conditions that can dissuade the reaper from touching our file -- no reflink iflag; dirty page cache; writeback in progress; or directio in progress. We check the four conditions prior to taking the locks, but we neglect to recheck them once we have the locks, which is how we end up whacking the writeback that's in progress.

Quoted 7 lines

Therefore, refactor the four checks into a helper function and call it once again once we have the locks to make sure we really want to reap the inode. While we're at it, add an ASSERT for this weird condition so that we'll fail noisily if we ever screw this up again.

Quoted 6 lines

I applied this patch on top of v4.15-rc5 kernel, and ran generic/083 generic/269 and generic/270 (where I hit the soft lockup and hang before) multiple times and tests all passed. I also ran all tests in 'enospc' group on 1k/2k/4k XFS with reflink enabled, tests passed too. So

Sorry, I have to withdraw this tag for now.. I'm seeing soft lockup again in generic/269 run with the patched kernel. I'll do more testings to confirm, paste the soft lockup info here for now:

[596580.126008] watchdog: BUG: soft lockup - CPU#3 stuck for 22s! [fsstress:30037]
[596580.126008] Modules linked in: xfs dm_delay btrfs xor zstd_compress raid6_pq zstd_decompress xxhash dm_thin_pool dm_persistent_data dm_bio_prison dm_flakey loop ip6t_rpfilter ip6t_REJECT nf_reject_ipv6 xt_conntrack ip_set nfnetlink ebtable_nat ebtable_broute bridge stp llc ip6table_nat nf_conntrack_ipv6 nf_defrag_ipv6 nf_nat_ipv6 ip6table_mangle ip6table_raw ip6table_security iptable_nat nf_conntrack_ipv4 nf_defrag_ipv4 nf_nat_ipv4 nf_nat nf_conntrack libcrc32c iptable_mangle iptable_raw iptable_security ebtable_filter ebtables ip6table_filter ip6_tables sunrpc joydev i2c_piix4 8139too virtio_balloon pcspkr 8139cp mii virtio_pci virtio_ring virtio serio_raw floppy ata_generic pata_acpi [last unloaded: scsi_debug]
[596580.129050] irq event stamp: 174005460
[596580.129050] hardirqs last  enabled at (174005459): [<000000004aebc6cd>] restore_regs_and_return_to_kernel+0x0/0x2e
[596580.129050] hardirqs last disabled at (174005460): [<0000000084598378>] apic_timer_interrupt+0xa7/0xc0
[596580.132071] softirqs last  enabled at (79644030): [<000000009174d1b7>] __do_softirq+0x392/0x502
[596580.133052] softirqs last disabled at (79644019): [<000000002b9518d7>] irq_exit+0x102/0x110
[596580.133052] CPU: 3 PID: 30037 Comm: fsstress Tainted: G        W  OEL   4.15.0-rc5 #10
[596580.133052] Hardware name: Red Hat KVM, BIOS 0.5.1 01/01/2007
[596580.133052] RIP: 0010:xfs_bmapi_convert_unwritten+0xb/0x1b0 [xfs]
[596580.133052] RSP: 0000:ffffbb8643d538d0 EFLAGS: 00000287 ORIG_RAX: ffffffffffffff11
[596580.133052] RAX: 000ffffffffe0000 RBX: 0000000000000a40 RCX: 0000000000000a40
[596580.136071] RDX: 0000000000000080 RSI: ffffbb8643d53aa8 RDI: ffffbb8643d53980
[596580.136071] RBP: ffffbb8643d53a68 R08: 0000000000000080 R09: 0000000000000000
[596580.137053] R10: 0000000000000000 R11: fed20f5f8482504e R12: ffffbb8643d53980
[596580.137053] R13: ffffbb8643d539c0 R14: 0000000000000080 R15: 0000000000000001
[596580.137053] FS:  00007fca843b1b80(0000) GS:ffff9c29d7400000(0000) knlGS:0000000000000000
[596580.137053] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[596580.137053] CR2: 00007fca843a8000 CR3: 00000001070f5000 CR4: 00000000000006e0
[596580.137053] Call Trace:
[596580.137053]  xfs_bmapi_write+0x301/0xcc0 [xfs]
[596580.140071]  ? sched_clock+0x5/0x10
[596580.140071]  xfs_reflink_convert_cow+0x8c/0xc0 [xfs]
[596580.140071]  ? __test_set_page_writeback+0x18b/0x3c0
[596580.141051]  xfs_submit_ioend+0x18f/0x1f0 [xfs]
[596580.141051]  xfs_do_writepage+0x39d/0x7e0 [xfs]
[596580.141051]  write_cache_pages+0x1d0/0x550
[596580.141051]  ? xfs_vm_readpage+0x130/0x130 [xfs]
[596580.141051]  xfs_vm_writepages+0xb1/0xd0 [xfs]
[596580.141051]  do_writepages+0x48/0xf0
[596580.141051]  ? __filemap_fdatawrite_range+0xb4/0x100
[596580.141051]  ? __filemap_fdatawrite_range+0xc1/0x100
[596580.141051]  __filemap_fdatawrite_range+0xc1/0x100
[596580.144072]  xfs_release+0x11c/0x160 [xfs]
[596580.144072]  __fput+0xe6/0x1f0
[596580.144072]  task_work_run+0x82/0xb0
[596580.145050]  exit_to_usermode_loop+0xa8/0xb0
[596580.145050]  syscall_return_slowpath+0x153/0x160
[596580.145050]  entry_SYSCALL_64_fastpath+0x94/0x96
[596580.145050] RIP: 0033:0x7fca83b87cb1
[596580.145050] RSP: 002b:00007ffdd89b8368 EFLAGS: 00000246 ORIG_RAX: 0000000000000003
[596580.145050] RAX: 0000000000000000 RBX: 000000000000031a RCX: 00007fca83b87cb1
[596580.145050] RDX: 0000000001a675f0 RSI: 0000000001a55010 RDI: 0000000000000003
[596580.145050] RBP: 000000000001146a R08: 0000000000000006 R09: 00007fca83b71d00
[596580.148070] R10: 0000000001a55010 R11: 0000000000000246 R12: 0000000000000003
[596580.148070] R13: 0000000000174143 R14: 0000000001aad800 R15: 0000000000000000
[596580.149050] Code: e9 58 f4 ff ff e8 c6 a8 a9 c9 4c 8b 4d 08 41 b8 99 09 00 00 e9 44 f4 ff ff 0f 1f 80 00 00 00 00 0f 1f 44 00 00 41 57 41 56 41 55 <41> 54 49 89 d5 55 53 89 cd 48 89 fb 49 89 f4 48 83 ec 10 48 8b

Thanks,
Eryu

#3 lore raw
Quoted 33 lines · Eryu Guan
Quoted 32 lines · Eryu Guan
Quoted 18 lines · Darrick J. Wong

From: Darrick J. Wong <[email protected]>

Eryu Guan reported seeing occasional hangs when running generic/269 with a new fsstress that supports clonerange/deduperange. The cause of this hang is an infinite loop when we convert the CoW fork extents from unwritten to real just prior to writing the pages out; the infinite loop happens because there's nothing in the CoW fork to convert, and so it spins forever.

The underlying issue here is that when we go to perform these CoW fork conversions, we're supposed to have an extent waiting for us, but the low space CoW reaper has snuck in and blown them away! There are four conditions that can dissuade the reaper from touching our file -- no reflink iflag; dirty page cache; writeback in progress; or directio in progress. We check the four conditions prior to taking the locks, but we neglect to recheck them once we have the locks, which is how we end up whacking the writeback that's in progress.

Quoted 7 lines

Therefore, refactor the four checks into a helper function and call it once again once we have the locks to make sure we really want to reap the inode. While we're at it, add an ASSERT for this weird condition so that we'll fail noisily if we ever screw this up again.

I applied this patch on top of v4.15-rc5 kernel, and ran generic/083 generic/269 and generic/270 (where I hit the soft lockup and hang before) multiple times and tests all passed. I also ran all tests in 'enospc' group on 1k/2k/4k XFS with reflink enabled, tests passed too. So

Quoted 5 lines
Quoted 1 line

Sorry, I have to withdraw this tag for now.. I'm seeing soft lockup again in generic/269 run with the patched kernel. I'll do more testings to confirm, paste the soft lockup info here for now:

I ran generic/269 for over 4000 iterations and didn't hit soft lockup, I suspect that previously I tested on wrong/unpatched xfs module..

But occationally I saw fs inconsistency in generic/269, it's hard to reproduce (need 100-200 iterations) but I did see it several times. But it seems like another problem.

_check_xfs_filesystem: filesystem on /dev/sda6 is inconsistent (r) *** xfs_repair -n output ***
Phase 1 - find and verify superblock...
Phase 2 - using internal log
- zero log...
- scan filesystem freespace and inode maps...
sb_fdblocks 8178, counted 8188
- found root inode chunk
Phase 3 - for each AG...
- scan (but don't clear) agi unlinked lists...
- process known inodes and perform inode discovery...
- agno = 0
- agno = 1
- agno = 2
- agno = 3
- process newly discovered inodes...
Phase 4 - check for duplicate blocks...
- setting up duplicate extent list...
- check for inodes claiming duplicate blocks...
- agno = 1
- agno = 3
- agno = 2
- agno = 0
No modify flag set, skipping phase 5
Phase 6 - check inode connectivity...
- traversing filesystem ...
- traversal finished ...
- moving disconnected inodes to lost+found ...
Phase 7 - verify link counts...
No modify flag set, skipping filesystem flush and exiting. *** end xfs_repair output

Thanks,
Eryu

#4 lore raw
Quoted 40 lines · Eryu Guan
Quoted 33 lines · Eryu Guan
Quoted 32 lines · Eryu Guan
Quoted 18 lines · Darrick J. Wong

From: Darrick J. Wong <[email protected]>

Eryu Guan reported seeing occasional hangs when running generic/269 with a new fsstress that supports clonerange/deduperange. The cause of this hang is an infinite loop when we convert the CoW fork extents from unwritten to real just prior to writing the pages out; the infinite loop happens because there's nothing in the CoW fork to convert, and so it spins forever.

The underlying issue here is that when we go to perform these CoW fork conversions, we're supposed to have an extent waiting for us, but the low space CoW reaper has snuck in and blown them away! There are four conditions that can dissuade the reaper from touching our file -- no reflink iflag; dirty page cache; writeback in progress; or directio in progress. We check the four conditions prior to taking the locks, but we neglect to recheck them once we have the locks, which is how we end up whacking the writeback that's in progress.

Quoted 7 lines

Therefore, refactor the four checks into a helper function and call it once again once we have the locks to make sure we really want to reap the inode. While we're at it, add an ASSERT for this weird condition so that we'll fail noisily if we ever screw this up again.

I applied this patch on top of v4.15-rc5 kernel, and ran generic/083 generic/269 and generic/270 (where I hit the soft lockup and hang before) multiple times and tests all passed. I also ran all tests in 'enospc' group on 1k/2k/4k XFS with reflink enabled, tests passed too. So

Quoted 5 lines
Quoted 1 line

Sorry, I have to withdraw this tag for now.. I'm seeing soft lockup again in generic/269 run with the patched kernel. I'll do more testings to confirm, paste the soft lockup info here for now:

Quoted 6 lines

I ran generic/269 for over 4000 iterations and didn't hit soft lockup, I suspect that previously I tested on wrong/unpatched xfs module..

But occationally I saw fs inconsistency in generic/269, it's hard to reproduce (need 100-200 iterations) but I did see it several times. But it seems like another problem.

I've seen that one go by occasionally too; will see if I can figure out what's going on, though I don't think it's related to this hang fix.

--D

Quoted 38 lines

_check_xfs_filesystem: filesystem on /dev/sda6 is inconsistent (r) *** xfs_repair -n output ***
Phase 1 - find and verify superblock...
Phase 2 - using internal log
- zero log...
- scan filesystem freespace and inode maps...
sb_fdblocks 8178, counted 8188
- found root inode chunk
Phase 3 - for each AG...
- scan (but don't clear) agi unlinked lists...
- process known inodes and perform inode discovery...
- agno = 0
- agno = 1
- agno = 2
- agno = 3
- process newly discovered inodes...
Phase 4 - check for duplicate blocks...
- setting up duplicate extent list...
- check for inodes claiming duplicate blocks...
- agno = 1
- agno = 3
- agno = 2
- agno = 0
No modify flag set, skipping phase 5
Phase 6 - check inode connectivity...
- traversing filesystem ...
- traversal finished ...
- moving disconnected inodes to lost+found ...
Phase 7 - verify link counts...
No modify flag set, skipping filesystem flush and exiting. *** end xfs_repair output

Thanks,
Eryu
--
To unsubscribe from this list: send the line "unsubscribe linux-xfs" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html

#5 lore raw
In reply to Darrick J. Wong
Quoted 35 lines · Darrick J. Wong

From: Darrick J. Wong <[email protected]>

Eryu Guan reported seeing occasional hangs when running generic/269 with a new fsstress that supports clonerange/deduperange. The cause of this hang is an infinite loop when we convert the CoW fork extents from unwritten to real just prior to writing the pages out; the infinite loop happens because there's nothing in the CoW fork to convert, and so it spins forever.

The underlying issue here is that when we go to perform these CoW fork conversions, we're supposed to have an extent waiting for us, but the low space CoW reaper has snuck in and blown them away! There are four conditions that can dissuade the reaper from touching our file -- no reflink iflag; dirty page cache; writeback in progress; or directio in progress. We check the four conditions prior to taking the locks, but we neglect to recheck them once we have the locks, which is how we end up whacking the writeback that's in progress.

Therefore, refactor the four checks into a helper function and call it once again once we have the locks to make sure we really want to reap the inode. While we're at it, add an ASSERT for this weird condition so that we'll fail noisily if we ever screw this up again.

---
 fs/xfs/libxfs/xfs_bmap.c |    7 +++++
 fs/xfs/xfs_icache.c      |   61 +++++++++++++++++++++++++++++-----------------
 2 files changed, 46 insertions(+), 22 deletions(-)
fs/xfs/libxfs/xfs_bmap.c +0 -0
index a01cef4..7bd933f 100644

fs/xfs/libxfs/xfs_bmap.c xfs_bmapi_write( · line 4320

Quoted 10 lines
fs/xfs/libxfs/xfs_bmap.c +7 -0
@@ -4311,6 +4311,13 @@ xfs_bmapi_write(
 	while (bno < end && n < *nmap) {
 		bool			need_alloc = false, wasdelay = false;
 
+		/*
+		 * CoW fork conversions should /never/ hit EOF.  There should
+		 * always be something for us to work on.
+		 */
+		ASSERT(!eof || !(flags & XFS_BMAPI_CONVERT) ||
+			       !(flags & XFS_BMAPI_COWFORK));
+

The hunk just below asserts for BMAPI_COWFORK in a case that explicitly considers eof. That makes the logic confusing to follow IMO, but I'm more wondering whether pushing something like ASSERT(!((flags & CONVERT) && (flags & COWFORK))) down into that hunk is effectively the same thing..? I.e., is it also true that we should not find a hole in the (CONVERT & COW) case?

Quoted 15 lines
fs/xfs/libxfs/xfs_bmap.c +0 -0
 		/* in hole or beyoned EOF? */
 		if (eof || bma.got.br_startoff > bno) {
 			if (flags & XFS_BMAPI_DELALLOC) {
fs/xfs/xfs_icache.c +4 -0
index 1f84562..3fbcc03 100644
@@ -1654,6 +1654,35 @@ xfs_inode_clear_eofblocks_tag(
 			trace_xfs_perag_clear_eofblocks, XFS_ICI_EOFBLOCKS_TAG);
 }
 
+/* Is this a good time to reap the CoW reservations for this file? */
+static bool
+xfs_can_free_cowblocks(
+	struct xfs_inode	*ip,

fs/xfs/xfs_icache.c xfs_inode_clear_eofblocks_tag( · line 1670

Quoted 10 lines
fs/xfs/xfs_icache.c +10 -0
+	struct xfs_ifork	*ifp)
+{
+	/*
+	 * Just clear the tag if we have an empty cow fork or none at all. It's
+	 * possible the inode was fully unshared since it was originally tagged.
+	 */
+	if (!xfs_is_reflink_inode(ip) || !ifp->if_bytes) {
+		trace_xfs_inode_free_cowblocks_invalid(ip);
+		xfs_inode_clear_cowblocks_tag(ip);
+		return false;

I think the flag update and tracepoint should probably remain in the caller. They're somewhat misplaced for a "xfs_can_do_something()" helper, particularly if it's ever exported and used in other contexts in the future. Otherwise seems fine.

Brian

Quoted 70 lines
fs/xfs/xfs_icache.c +25 -23
+	}
+
+	/*
+	 * If the mapping is dirty or under writeback we cannot touch the
+	 * CoW fork.  Leave it alone if we're in the midst of a directio.
+	 */
+	if ((VFS_I(ip)->i_state & I_DIRTY_PAGES) ||
+	    mapping_tagged(VFS_I(ip)->i_mapping, PAGECACHE_TAG_DIRTY) ||
+	    mapping_tagged(VFS_I(ip)->i_mapping, PAGECACHE_TAG_WRITEBACK) ||
+	    atomic_read(&VFS_I(ip)->i_dio_count))
+		return false;
+
+	return true;
+}
+
 /*
  * Automatic CoW Reservation Freeing
  *
@@ -1672,29 +1701,12 @@ xfs_inode_free_cowblocks(
 	int			flags,
 	void			*args)
 {
-	int ret;
-	struct xfs_eofblocks *eofb = args;
-	int match;
+	struct xfs_eofblocks	*eofb = args;
 	struct xfs_ifork	*ifp = XFS_IFORK_PTR(ip, XFS_COW_FORK);
+	int			match;
+	int			ret = 0;
 
-	/*
-	 * Just clear the tag if we have an empty cow fork or none at all. It's
-	 * possible the inode was fully unshared since it was originally tagged.
-	 */
-	if (!xfs_is_reflink_inode(ip) || !ifp->if_bytes) {
-		trace_xfs_inode_free_cowblocks_invalid(ip);
-		xfs_inode_clear_cowblocks_tag(ip);
-		return 0;
-	}
-
-	/*
-	 * If the mapping is dirty or under writeback we cannot touch the
-	 * CoW fork.  Leave it alone if we're in the midst of a directio.
-	 */
-	if ((VFS_I(ip)->i_state & I_DIRTY_PAGES) ||
-	    mapping_tagged(VFS_I(ip)->i_mapping, PAGECACHE_TAG_DIRTY) ||
-	    mapping_tagged(VFS_I(ip)->i_mapping, PAGECACHE_TAG_WRITEBACK) ||
-	    atomic_read(&VFS_I(ip)->i_dio_count))
+	if (!xfs_can_free_cowblocks(ip, ifp))
 		return 0;
 
 	if (eofb) {
@@ -1715,7 +1727,12 @@ xfs_inode_free_cowblocks(
 	xfs_ilock(ip, XFS_IOLOCK_EXCL);
 	xfs_ilock(ip, XFS_MMAPLOCK_EXCL);
 
-	ret = xfs_reflink_cancel_cow_range(ip, 0, NULLFILEOFF, false);
+	/*
+	 * Check again, nobody else should be able to dirty blocks or change
+	 * the reflink iflag now that we have the first two locks held.
+	 */
+	if (xfs_can_free_cowblocks(ip, ifp))
+		ret = xfs_reflink_cancel_cow_range(ip, 0, NULLFILEOFF, false);
 
 	xfs_iunlock(ip, XFS_MMAPLOCK_EXCL);
 	xfs_iunlock(ip, XFS_IOLOCK_EXCL);
--

To unsubscribe from this list: send the line "unsubscribe linux-xfs" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html

#6 lore raw
Quoted 39 lines · Brian Foster
Quoted 38 lines · Darrick J. Wong

From: Darrick J. Wong <[email protected]>

Eryu Guan reported seeing occasional hangs when running generic/269 with a new fsstress that supports clonerange/deduperange. The cause of this hang is an infinite loop when we convert the CoW fork extents from unwritten to real just prior to writing the pages out; the infinite loop happens because there's nothing in the CoW fork to convert, and so it spins forever.

The underlying issue here is that when we go to perform these CoW fork conversions, we're supposed to have an extent waiting for us, but the low space CoW reaper has snuck in and blown them away! There are four conditions that can dissuade the reaper from touching our file -- no reflink iflag; dirty page cache; writeback in progress; or directio in progress. We check the four conditions prior to taking the locks, but we neglect to recheck them once we have the locks, which is how we end up whacking the writeback that's in progress.

Therefore, refactor the four checks into a helper function and call it once again once we have the locks to make sure we really want to reap the inode. While we're at it, add an ASSERT for this weird condition so that we'll fail noisily if we ever screw this up again.

---
 fs/xfs/libxfs/xfs_bmap.c |    7 +++++
 fs/xfs/xfs_icache.c      |   61 +++++++++++++++++++++++++++++-----------------
 2 files changed, 46 insertions(+), 22 deletions(-)
fs/xfs/libxfs/xfs_bmap.c +0 -0
index a01cef4..7bd933f 100644
@@ -4311,6 +4311,13 @@ xfs_bmapi_write(
 	while (bno < end && n < *nmap) {
 		bool			need_alloc = false, wasdelay = false;
 
Quoted 14 lines

fs/xfs/libxfs/xfs_bmap.c xfs_bmapi_write( · line 4320

Quoted 7 lines
fs/xfs/libxfs/xfs_bmap.c +7 -0
+		/*
+		 * CoW fork conversions should /never/ hit EOF.  There should
+		 * always be something for us to work on.
+		 */
+		ASSERT(!eof || !(flags & XFS_BMAPI_CONVERT) ||
+			       !(flags & XFS_BMAPI_COWFORK));
+

The hunk just below asserts for BMAPI_COWFORK in a case that explicitly considers eof. That makes the logic confusing to follow IMO, but I'm more wondering whether pushing something like ASSERT(!((flags & CONVERT) && (flags & COWFORK))) down into that hunk is effectively the same thing..? I.e., is it also true that we should not find a hole in the (CONVERT & COW) case?

Yes.

Quoted 22 lines
Quoted 22 lines
fs/xfs/libxfs/xfs_bmap.c +0 -0
 		/* in hole or beyoned EOF? */
 		if (eof || bma.got.br_startoff > bno) {
 			if (flags & XFS_BMAPI_DELALLOC) {
fs/xfs/xfs_icache.c +11 -0
index 1f84562..3fbcc03 100644
@@ -1654,6 +1654,35 @@ xfs_inode_clear_eofblocks_tag(
 			trace_xfs_perag_clear_eofblocks, XFS_ICI_EOFBLOCKS_TAG);
 }
 
+/* Is this a good time to reap the CoW reservations for this file? */
+static bool
+xfs_can_free_cowblocks(
+	struct xfs_inode	*ip,
+	struct xfs_ifork	*ifp)
+{
+	/*
+	 * Just clear the tag if we have an empty cow fork or none at all. It's
+	 * possible the inode was fully unshared since it was originally tagged.
+	 */
+	if (!xfs_is_reflink_inode(ip) || !ifp->if_bytes) {
Quoted 8 lines

fs/xfs/xfs_icache.c xfs_inode_clear_eofblocks_tag( · line 1670

Quoted 3 lines
fs/xfs/xfs_icache.c +3 -0
+		trace_xfs_inode_free_cowblocks_invalid(ip);
+		xfs_inode_clear_cowblocks_tag(ip);
+		return false;

I think the flag update and tracepoint should probably remain in the caller. They're somewhat misplaced for a "xfs_can_do_something()" helper, particularly if it's ever exported and used in other contexts in the future. Otherwise seems fine.

Hmm. There's a subtlety to step around here, which is that this predicate can return false to mean "nothing here to see" or to mean "cannot clear anything at this time". We want the trace+clear for the first case, but not the second.

I suppose this function could return the regular error code int and the caller can figure out what that means to it, but then all the post-check stuff ends up duplicated in the callers... so maybe I should just rename it xfs_prep_free_cowblocks().

And change the comment to:

/*
* Set ourselves up to free CoW blocks from this file. If it's already
* clean then we can bail out quickly, but otherwise we must back off if
* the file is undergoing some kind of write.
*/

--D

Quoted 76 lines

Brian

Quoted 67 lines
fs/xfs/xfs_icache.c +25 -23
+	}
+
+	/*
+	 * If the mapping is dirty or under writeback we cannot touch the
+	 * CoW fork.  Leave it alone if we're in the midst of a directio.
+	 */
+	if ((VFS_I(ip)->i_state & I_DIRTY_PAGES) ||
+	    mapping_tagged(VFS_I(ip)->i_mapping, PAGECACHE_TAG_DIRTY) ||
+	    mapping_tagged(VFS_I(ip)->i_mapping, PAGECACHE_TAG_WRITEBACK) ||
+	    atomic_read(&VFS_I(ip)->i_dio_count))
+		return false;
+
+	return true;
+}
+
 /*
  * Automatic CoW Reservation Freeing
  *
@@ -1672,29 +1701,12 @@ xfs_inode_free_cowblocks(
 	int			flags,
 	void			*args)
 {
-	int ret;
-	struct xfs_eofblocks *eofb = args;
-	int match;
+	struct xfs_eofblocks	*eofb = args;
 	struct xfs_ifork	*ifp = XFS_IFORK_PTR(ip, XFS_COW_FORK);
+	int			match;
+	int			ret = 0;
 
-	/*
-	 * Just clear the tag if we have an empty cow fork or none at all. It's
-	 * possible the inode was fully unshared since it was originally tagged.
-	 */
-	if (!xfs_is_reflink_inode(ip) || !ifp->if_bytes) {
-		trace_xfs_inode_free_cowblocks_invalid(ip);
-		xfs_inode_clear_cowblocks_tag(ip);
-		return 0;
-	}
-
-	/*
-	 * If the mapping is dirty or under writeback we cannot touch the
-	 * CoW fork.  Leave it alone if we're in the midst of a directio.
-	 */
-	if ((VFS_I(ip)->i_state & I_DIRTY_PAGES) ||
-	    mapping_tagged(VFS_I(ip)->i_mapping, PAGECACHE_TAG_DIRTY) ||
-	    mapping_tagged(VFS_I(ip)->i_mapping, PAGECACHE_TAG_WRITEBACK) ||
-	    atomic_read(&VFS_I(ip)->i_dio_count))
+	if (!xfs_can_free_cowblocks(ip, ifp))
 		return 0;
 
 	if (eofb) {
@@ -1715,7 +1727,12 @@ xfs_inode_free_cowblocks(
 	xfs_ilock(ip, XFS_IOLOCK_EXCL);
 	xfs_ilock(ip, XFS_MMAPLOCK_EXCL);
 
-	ret = xfs_reflink_cancel_cow_range(ip, 0, NULLFILEOFF, false);
+	/*
+	 * Check again, nobody else should be able to dirty blocks or change
+	 * the reflink iflag now that we have the first two locks held.
+	 */
+	if (xfs_can_free_cowblocks(ip, ifp))
+		ret = xfs_reflink_cancel_cow_range(ip, 0, NULLFILEOFF, false);
 
 	xfs_iunlock(ip, XFS_MMAPLOCK_EXCL);
 	xfs_iunlock(ip, XFS_IOLOCK_EXCL);
--
Quoted 3 lines

To unsubscribe from this list: send the line "unsubscribe linux-xfs" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html

--
To unsubscribe from this list: send the line "unsubscribe linux-xfs" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html

#7 lore raw
Quoted 93 lines · Darrick J. Wong
Quoted 39 lines · Brian Foster
Quoted 38 lines · Darrick J. Wong

From: Darrick J. Wong <[email protected]>

Eryu Guan reported seeing occasional hangs when running generic/269 with a new fsstress that supports clonerange/deduperange. The cause of this hang is an infinite loop when we convert the CoW fork extents from unwritten to real just prior to writing the pages out; the infinite loop happens because there's nothing in the CoW fork to convert, and so it spins forever.

The underlying issue here is that when we go to perform these CoW fork conversions, we're supposed to have an extent waiting for us, but the low space CoW reaper has snuck in and blown them away! There are four conditions that can dissuade the reaper from touching our file -- no reflink iflag; dirty page cache; writeback in progress; or directio in progress. We check the four conditions prior to taking the locks, but we neglect to recheck them once we have the locks, which is how we end up whacking the writeback that's in progress.

Therefore, refactor the four checks into a helper function and call it once again once we have the locks to make sure we really want to reap the inode. While we're at it, add an ASSERT for this weird condition so that we'll fail noisily if we ever screw this up again.

---
 fs/xfs/libxfs/xfs_bmap.c |    7 +++++
 fs/xfs/xfs_icache.c      |   61 +++++++++++++++++++++++++++++-----------------
 2 files changed, 46 insertions(+), 22 deletions(-)
fs/xfs/libxfs/xfs_bmap.c +0 -0
index a01cef4..7bd933f 100644
@@ -4311,6 +4311,13 @@ xfs_bmapi_write(
 	while (bno < end && n < *nmap) {
 		bool			need_alloc = false, wasdelay = false;
 
Quoted 14 lines

fs/xfs/libxfs/xfs_bmap.c xfs_bmapi_write( · line 4320

Quoted 7 lines
fs/xfs/libxfs/xfs_bmap.c +7 -0
+		/*
+		 * CoW fork conversions should /never/ hit EOF.  There should
+		 * always be something for us to work on.
+		 */
+		ASSERT(!eof || !(flags & XFS_BMAPI_CONVERT) ||
+			       !(flags & XFS_BMAPI_COWFORK));
+

The hunk just below asserts for BMAPI_COWFORK in a case that explicitly considers eof. That makes the logic confusing to follow IMO, but I'm more wondering whether pushing something like ASSERT(!((flags & CONVERT) && (flags & COWFORK))) down into that hunk is effectively the same thing..? I.e., is it also true that we should not find a hole in the (CONVERT & COW) case?

Yes.

Quoted 22 lines
Quoted 22 lines
fs/xfs/libxfs/xfs_bmap.c +0 -0
 		/* in hole or beyoned EOF? */
 		if (eof || bma.got.br_startoff > bno) {
 			if (flags & XFS_BMAPI_DELALLOC) {
fs/xfs/xfs_icache.c +11 -0
index 1f84562..3fbcc03 100644
@@ -1654,6 +1654,35 @@ xfs_inode_clear_eofblocks_tag(
 			trace_xfs_perag_clear_eofblocks, XFS_ICI_EOFBLOCKS_TAG);
 }
 
+/* Is this a good time to reap the CoW reservations for this file? */
+static bool
+xfs_can_free_cowblocks(
+	struct xfs_inode	*ip,
+	struct xfs_ifork	*ifp)
+{
+	/*
+	 * Just clear the tag if we have an empty cow fork or none at all. It's
+	 * possible the inode was fully unshared since it was originally tagged.
+	 */
+	if (!xfs_is_reflink_inode(ip) || !ifp->if_bytes) {
Quoted 8 lines

fs/xfs/xfs_icache.c xfs_inode_clear_eofblocks_tag( · line 1670

Quoted 3 lines
fs/xfs/xfs_icache.c +3 -0
+		trace_xfs_inode_free_cowblocks_invalid(ip);
+		xfs_inode_clear_cowblocks_tag(ip);
+		return false;

I think the flag update and tracepoint should probably remain in the caller. They're somewhat misplaced for a "xfs_can_do_something()" helper, particularly if it's ever exported and used in other contexts in the future. Otherwise seems fine.

Hmm. There's a subtlety to step around here, which is that this predicate can return false to mean "nothing here to see" or to mean "cannot clear anything at this time". We want the trace+clear for the first case, but not the second.

Quoted 5 lines

I suppose this function could return the regular error code int and the caller can figure out what that means to it, but then all the post-check stuff ends up duplicated in the callers... so maybe I should just rename it xfs_prep_free_cowblocks().

Are both checks necessarily required to be repeated under lock to fix the bug? IOW, Could the !fork || !flag check remain in the caller to cover the first case?

Quoted 8 lines

And change the comment to:

/*
* Set ourselves up to free CoW blocks from this file. If it's already
* clean then we can bail out quickly, but otherwise we must back off if
* the file is undergoing some kind of write.
*/

That sounds reasonable too.

Brian

Quoted 82 lines

--D

Quoted 66 lines

Brian

Quoted 64 lines
fs/xfs/xfs_icache.c +25 -22
+	}
+
+	/*
+	 * If the mapping is dirty or under writeback we cannot touch the
+	 * CoW fork.  Leave it alone if we're in the midst of a directio.
+	 */
+	if ((VFS_I(ip)->i_state & I_DIRTY_PAGES) ||
+	    mapping_tagged(VFS_I(ip)->i_mapping, PAGECACHE_TAG_DIRTY) ||
+	    mapping_tagged(VFS_I(ip)->i_mapping, PAGECACHE_TAG_WRITEBACK) ||
+	    atomic_read(&VFS_I(ip)->i_dio_count))
+		return false;
+
+	return true;
+}
+
 /*
  * Automatic CoW Reservation Freeing
  *
@@ -1672,29 +1701,12 @@ xfs_inode_free_cowblocks(
 	int			flags,
 	void			*args)
 {
-	int ret;
-	struct xfs_eofblocks *eofb = args;
-	int match;
+	struct xfs_eofblocks	*eofb = args;
 	struct xfs_ifork	*ifp = XFS_IFORK_PTR(ip, XFS_COW_FORK);
+	int			match;
+	int			ret = 0;
 
-	/*
-	 * Just clear the tag if we have an empty cow fork or none at all. It's
-	 * possible the inode was fully unshared since it was originally tagged.
-	 */
-	if (!xfs_is_reflink_inode(ip) || !ifp->if_bytes) {
-		trace_xfs_inode_free_cowblocks_invalid(ip);
-		xfs_inode_clear_cowblocks_tag(ip);
-		return 0;
-	}
-
-	/*
-	 * If the mapping is dirty or under writeback we cannot touch the
-	 * CoW fork.  Leave it alone if we're in the midst of a directio.
-	 */
-	if ((VFS_I(ip)->i_state & I_DIRTY_PAGES) ||
-	    mapping_tagged(VFS_I(ip)->i_mapping, PAGECACHE_TAG_DIRTY) ||
-	    mapping_tagged(VFS_I(ip)->i_mapping, PAGECACHE_TAG_WRITEBACK) ||
-	    atomic_read(&VFS_I(ip)->i_dio_count))
+	if (!xfs_can_free_cowblocks(ip, ifp))
 		return 0;
 
 	if (eofb) {
@@ -1715,7 +1727,12 @@ xfs_inode_free_cowblocks(
 	xfs_ilock(ip, XFS_IOLOCK_EXCL);
 	xfs_ilock(ip, XFS_MMAPLOCK_EXCL);
 
-	ret = xfs_reflink_cancel_cow_range(ip, 0, NULLFILEOFF, false);
+	/*
+	 * Check again, nobody else should be able to dirty blocks or change
+	 * the reflink iflag now that we have the first two locks held.
+	 */
+	if (xfs_can_free_cowblocks(ip, ifp))
+		ret = xfs_reflink_cancel_cow_range(ip, 0, NULLFILEOFF, false);
 
Quoted 10 lines
Quoted 6 lines
fs/xfs/xfs_icache.c +0 -1
 	xfs_iunlock(ip, XFS_MMAPLOCK_EXCL);
 	xfs_iunlock(ip, XFS_IOLOCK_EXCL);
--

To unsubscribe from this list: send the line "unsubscribe linux-xfs" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html

--
To unsubscribe from this list: send the line "unsubscribe linux-xfs" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html

--
To unsubscribe from this list: send the line "unsubscribe linux-xfs" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html

#8 lore raw
Quoted 100 lines · Brian Foster
Quoted 93 lines · Darrick J. Wong
Quoted 39 lines · Brian Foster
Quoted 38 lines · Darrick J. Wong

From: Darrick J. Wong <[email protected]>

Eryu Guan reported seeing occasional hangs when running generic/269 with a new fsstress that supports clonerange/deduperange. The cause of this hang is an infinite loop when we convert the CoW fork extents from unwritten to real just prior to writing the pages out; the infinite loop happens because there's nothing in the CoW fork to convert, and so it spins forever.

The underlying issue here is that when we go to perform these CoW fork conversions, we're supposed to have an extent waiting for us, but the low space CoW reaper has snuck in and blown them away! There are four conditions that can dissuade the reaper from touching our file -- no reflink iflag; dirty page cache; writeback in progress; or directio in progress. We check the four conditions prior to taking the locks, but we neglect to recheck them once we have the locks, which is how we end up whacking the writeback that's in progress.

Therefore, refactor the four checks into a helper function and call it once again once we have the locks to make sure we really want to reap the inode. While we're at it, add an ASSERT for this weird condition so that we'll fail noisily if we ever screw this up again.

---
 fs/xfs/libxfs/xfs_bmap.c |    7 +++++
 fs/xfs/xfs_icache.c      |   61 +++++++++++++++++++++++++++++-----------------
 2 files changed, 46 insertions(+), 22 deletions(-)
fs/xfs/libxfs/xfs_bmap.c +0 -0
index a01cef4..7bd933f 100644
@@ -4311,6 +4311,13 @@ xfs_bmapi_write(
 	while (bno < end && n < *nmap) {
 		bool			need_alloc = false, wasdelay = false;
 
Quoted 14 lines

fs/xfs/libxfs/xfs_bmap.c xfs_bmapi_write( · line 4320

Quoted 7 lines
fs/xfs/libxfs/xfs_bmap.c +7 -0
+		/*
+		 * CoW fork conversions should /never/ hit EOF.  There should
+		 * always be something for us to work on.
+		 */
+		ASSERT(!eof || !(flags & XFS_BMAPI_CONVERT) ||
+			       !(flags & XFS_BMAPI_COWFORK));
+

The hunk just below asserts for BMAPI_COWFORK in a case that explicitly considers eof. That makes the logic confusing to follow IMO, but I'm more wondering whether pushing something like ASSERT(!((flags & CONVERT) && (flags & COWFORK))) down into that hunk is effectively the same thing..? I.e., is it also true that we should not find a hole in the (CONVERT & COW) case?

Yes.

Quoted 22 lines
Quoted 22 lines
fs/xfs/libxfs/xfs_bmap.c +0 -0
 		/* in hole or beyoned EOF? */
 		if (eof || bma.got.br_startoff > bno) {
 			if (flags & XFS_BMAPI_DELALLOC) {
fs/xfs/xfs_icache.c +11 -0
index 1f84562..3fbcc03 100644
@@ -1654,6 +1654,35 @@ xfs_inode_clear_eofblocks_tag(
 			trace_xfs_perag_clear_eofblocks, XFS_ICI_EOFBLOCKS_TAG);
 }
 
+/* Is this a good time to reap the CoW reservations for this file? */
+static bool
+xfs_can_free_cowblocks(
+	struct xfs_inode	*ip,
+	struct xfs_ifork	*ifp)
+{
+	/*
+	 * Just clear the tag if we have an empty cow fork or none at all. It's
+	 * possible the inode was fully unshared since it was originally tagged.
+	 */
+	if (!xfs_is_reflink_inode(ip) || !ifp->if_bytes) {
Quoted 8 lines

fs/xfs/xfs_icache.c xfs_inode_clear_eofblocks_tag( · line 1670

Quoted 3 lines
fs/xfs/xfs_icache.c +3 -0
+		trace_xfs_inode_free_cowblocks_invalid(ip);
+		xfs_inode_clear_cowblocks_tag(ip);
+		return false;

I think the flag update and tracepoint should probably remain in the caller. They're somewhat misplaced for a "xfs_can_do_something()" helper, particularly if it's ever exported and used in other contexts in the future. Otherwise seems fine.

Hmm. There's a subtlety to step around here, which is that this predicate can return false to mean "nothing here to see" or to mean "cannot clear anything at this time". We want the trace+clear for the first case, but not the second.

Quoted 5 lines

I suppose this function could return the regular error code int and the caller can figure out what that means to it, but then all the post-check stuff ends up duplicated in the callers... so maybe I should just rename it xfs_prep_free_cowblocks().

Quoted 3 lines

Are both checks necessarily required to be repeated under lock to fix the bug? IOW, Could the !fork || !flag check remain in the caller to cover the first case?

They're necessary in both cases; generic/269 (when it wasn't hanging) would also blow the xfs_is_reflink_inode assert in xfs_reflink_cancel_cow_range if the flag got cleared before we can grab the iolock/mmaplock.

Quoted 10 lines
Quoted 8 lines

And change the comment to:

/*
* Set ourselves up to free CoW blocks from this file. If it's already
* clean then we can bail out quickly, but otherwise we must back off if
* the file is undergoing some kind of write.
*/

That sounds reasonable too.

<nod>

--D

Quoted 88 lines

Brian

Quoted 78 lines

--D

Quoted 66 lines

Brian

Quoted 64 lines
fs/xfs/xfs_icache.c +25 -22
+	}
+
+	/*
+	 * If the mapping is dirty or under writeback we cannot touch the
+	 * CoW fork.  Leave it alone if we're in the midst of a directio.
+	 */
+	if ((VFS_I(ip)->i_state & I_DIRTY_PAGES) ||
+	    mapping_tagged(VFS_I(ip)->i_mapping, PAGECACHE_TAG_DIRTY) ||
+	    mapping_tagged(VFS_I(ip)->i_mapping, PAGECACHE_TAG_WRITEBACK) ||
+	    atomic_read(&VFS_I(ip)->i_dio_count))
+		return false;
+
+	return true;
+}
+
 /*
  * Automatic CoW Reservation Freeing
  *
@@ -1672,29 +1701,12 @@ xfs_inode_free_cowblocks(
 	int			flags,
 	void			*args)
 {
-	int ret;
-	struct xfs_eofblocks *eofb = args;
-	int match;
+	struct xfs_eofblocks	*eofb = args;
 	struct xfs_ifork	*ifp = XFS_IFORK_PTR(ip, XFS_COW_FORK);
+	int			match;
+	int			ret = 0;
 
-	/*
-	 * Just clear the tag if we have an empty cow fork or none at all. It's
-	 * possible the inode was fully unshared since it was originally tagged.
-	 */
-	if (!xfs_is_reflink_inode(ip) || !ifp->if_bytes) {
-		trace_xfs_inode_free_cowblocks_invalid(ip);
-		xfs_inode_clear_cowblocks_tag(ip);
-		return 0;
-	}
-
-	/*
-	 * If the mapping is dirty or under writeback we cannot touch the
-	 * CoW fork.  Leave it alone if we're in the midst of a directio.
-	 */
-	if ((VFS_I(ip)->i_state & I_DIRTY_PAGES) ||
-	    mapping_tagged(VFS_I(ip)->i_mapping, PAGECACHE_TAG_DIRTY) ||
-	    mapping_tagged(VFS_I(ip)->i_mapping, PAGECACHE_TAG_WRITEBACK) ||
-	    atomic_read(&VFS_I(ip)->i_dio_count))
+	if (!xfs_can_free_cowblocks(ip, ifp))
 		return 0;
 
 	if (eofb) {
@@ -1715,7 +1727,12 @@ xfs_inode_free_cowblocks(
 	xfs_ilock(ip, XFS_IOLOCK_EXCL);
 	xfs_ilock(ip, XFS_MMAPLOCK_EXCL);
 
-	ret = xfs_reflink_cancel_cow_range(ip, 0, NULLFILEOFF, false);
+	/*
+	 * Check again, nobody else should be able to dirty blocks or change
+	 * the reflink iflag now that we have the first two locks held.
+	 */
+	if (xfs_can_free_cowblocks(ip, ifp))
+		ret = xfs_reflink_cancel_cow_range(ip, 0, NULLFILEOFF, false);
 
Quoted 10 lines
Quoted 6 lines
fs/xfs/xfs_icache.c +0 -1
 	xfs_iunlock(ip, XFS_MMAPLOCK_EXCL);
 	xfs_iunlock(ip, XFS_IOLOCK_EXCL);
--

To unsubscribe from this list: send the line "unsubscribe linux-xfs" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html

--
To unsubscribe from this list: send the line "unsubscribe linux-xfs" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html

Quoted 4 lines

--
To unsubscribe from this list: send the line "unsubscribe linux-xfs" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html

--
To unsubscribe from this list: send the line "unsubscribe linux-xfs" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html

#9 lore raw

PATCH v2 1/1 xfs: recheck reflink / dirty page status before freeing CoW reservations

In reply to Darrick J. Wong

From: Darrick J. Wong <[email protected]>

Eryu Guan reported seeing occasional hangs when running generic/269 with a new fsstress that supports clonerange/deduperange. The cause of this hang is an infinite loop when we convert the CoW fork extents from unwritten to real just prior to writing the pages out; the infinite loop happens because there's nothing in the CoW fork to convert, and so it spins forever.

The fundamental issue here is that when we go to perform these CoW fork conversions, we're supposed to have an extent waiting for us, but the low space CoW reaper has snuck in and blown them away! There are four conditions that can dissuade the reaper from touching our file -- no reflink iflag; dirty page cache; writeback in progress; or directio in progress. We check the four conditions prior to taking the locks, but we neglect to recheck them once we have the locks, which is how we end up whacking the writeback that's in progress.

Therefore, refactor the four checks into a helper function and call it once again once we have the locks to make sure we really want to reap the inode. While we're at it, add an ASSERT for this weird condition so that we'll fail noisily if we ever screw this up again.

---
v2: improve comments, minor refactors suggested by Brian Foster

---
 fs/xfs/libxfs/xfs_bmap.c |   10 +++++++
 fs/xfs/xfs_icache.c      |   63 +++++++++++++++++++++++++++++++---------------
 2 files changed, 51 insertions(+), 22 deletions(-)
fs/xfs/libxfs/xfs_bmap.c +9 -1
index a01cef4..3567db6 100644
@@ -4311,8 +4311,16 @@ xfs_bmapi_write(
 	while (bno < end && n < *nmap) {
 		bool			need_alloc = false, wasdelay = false;
 
-		/* in hole or beyoned EOF? */
+		/* in hole or beyond EOF? */
 		if (eof || bma.got.br_startoff > bno) {
+			/*
+			 * CoW fork conversions should /never/ hit EOF or
+			 * holes.  There should always be something for us
+			 * to work on.
+			 */
+			ASSERT(!((flags & XFS_BMAPI_CONVERT) &&
+			         (flags & XFS_BMAPI_COWFORK)));
+
 			if (flags & XFS_BMAPI_DELALLOC) {
 				/*
 				 * For the COW fork we can reasonably get a
fs/xfs/xfs_icache.c +42 -21

90 lines not on this page · raw message

Reviewed-by on v2 1/1
#10 lore raw
Quoted 23 lines · Darrick J. Wong

From: Darrick J. Wong <[email protected]>

Eryu Guan reported seeing occasional hangs when running generic/269 with a new fsstress that supports clonerange/deduperange. The cause of this hang is an infinite loop when we convert the CoW fork extents from unwritten to real just prior to writing the pages out; the infinite loop happens because there's nothing in the CoW fork to convert, and so it spins forever.

The fundamental issue here is that when we go to perform these CoW fork conversions, we're supposed to have an extent waiting for us, but the low space CoW reaper has snuck in and blown them away! There are four conditions that can dissuade the reaper from touching our file -- no reflink iflag; dirty page cache; writeback in progress; or directio in progress. We check the four conditions prior to taking the locks, but we neglect to recheck them once we have the locks, which is how we end up whacking the writeback that's in progress.

Therefore, refactor the four checks into a helper function and call it once again once we have the locks to make sure we really want to reap the inode. While we're at it, add an ASSERT for this weird condition so that we'll fail noisily if we ever screw this up again.

Quoted 6 lines

---
v2: improve comments, minor refactors suggested by Brian Foster
---

Quoted 128 lines
 fs/xfs/libxfs/xfs_bmap.c |   10 +++++++
 fs/xfs/xfs_icache.c      |   63 +++++++++++++++++++++++++++++++---------------
 2 files changed, 51 insertions(+), 22 deletions(-)
fs/xfs/libxfs/xfs_bmap.c +9 -1
index a01cef4..3567db6 100644
@@ -4311,8 +4311,16 @@ xfs_bmapi_write(
 	while (bno < end && n < *nmap) {
 		bool			need_alloc = false, wasdelay = false;
 
-		/* in hole or beyoned EOF? */
+		/* in hole or beyond EOF? */
 		if (eof || bma.got.br_startoff > bno) {
+			/*
+			 * CoW fork conversions should /never/ hit EOF or
+			 * holes.  There should always be something for us
+			 * to work on.
+			 */
+			ASSERT(!((flags & XFS_BMAPI_CONVERT) &&
+			         (flags & XFS_BMAPI_COWFORK)));
+
 			if (flags & XFS_BMAPI_DELALLOC) {
 				/*
 				 * For the COW fork we can reasonably get a
fs/xfs/xfs_icache.c +42 -22
index 1f84562..76df647 100644
@@ -1655,28 +1655,15 @@ xfs_inode_clear_eofblocks_tag(
 }
 
 /*
- * Automatic CoW Reservation Freeing
- *
- * These functions automatically garbage collect leftover CoW reservations
- * that were made on behalf of a cowextsize hint when we start to run out
- * of quota or when the reservations sit around for too long.  If the file
- * has dirty pages or is undergoing writeback, its CoW reservations will
- * be retained.
- *
- * The actual garbage collection piggybacks off the same code that runs
- * the speculative EOF preallocation garbage collector.
+ * Set ourselves up to free CoW blocks from this file.  If it's already clean
+ * then we can bail out quickly, but otherwise we must back off if the file
+ * is undergoing some kind of write.
  */
-STATIC int
-xfs_inode_free_cowblocks(
+static bool
+xfs_prep_free_cowblocks(
 	struct xfs_inode	*ip,
-	int			flags,
-	void			*args)
+	struct xfs_ifork	*ifp)
 {
-	int ret;
-	struct xfs_eofblocks *eofb = args;
-	int match;
-	struct xfs_ifork	*ifp = XFS_IFORK_PTR(ip, XFS_COW_FORK);
-
 	/*
 	 * Just clear the tag if we have an empty cow fork or none at all. It's
 	 * possible the inode was fully unshared since it was originally tagged.
@@ -1684,7 +1671,7 @@ xfs_inode_free_cowblocks(
 	if (!xfs_is_reflink_inode(ip) || !ifp->if_bytes) {
 		trace_xfs_inode_free_cowblocks_invalid(ip);
 		xfs_inode_clear_cowblocks_tag(ip);
-		return 0;
+		return false;
 	}
 
 	/*
@@ -1695,6 +1682,35 @@ xfs_inode_free_cowblocks(
 	    mapping_tagged(VFS_I(ip)->i_mapping, PAGECACHE_TAG_DIRTY) ||
 	    mapping_tagged(VFS_I(ip)->i_mapping, PAGECACHE_TAG_WRITEBACK) ||
 	    atomic_read(&VFS_I(ip)->i_dio_count))
+		return false;
+
+	return true;
+}
+
+/*
+ * Automatic CoW Reservation Freeing
+ *
+ * These functions automatically garbage collect leftover CoW reservations
+ * that were made on behalf of a cowextsize hint when we start to run out
+ * of quota or when the reservations sit around for too long.  If the file
+ * has dirty pages or is undergoing writeback, its CoW reservations will
+ * be retained.
+ *
+ * The actual garbage collection piggybacks off the same code that runs
+ * the speculative EOF preallocation garbage collector.
+ */
+STATIC int
+xfs_inode_free_cowblocks(
+	struct xfs_inode	*ip,
+	int			flags,
+	void			*args)
+{
+	struct xfs_eofblocks	*eofb = args;
+	struct xfs_ifork	*ifp = XFS_IFORK_PTR(ip, XFS_COW_FORK);
+	int			match;
+	int			ret = 0;
+
+	if (!xfs_prep_free_cowblocks(ip, ifp))
 		return 0;
 
 	if (eofb) {
@@ -1715,7 +1731,12 @@ xfs_inode_free_cowblocks(
 	xfs_ilock(ip, XFS_IOLOCK_EXCL);
 	xfs_ilock(ip, XFS_MMAPLOCK_EXCL);
 
-	ret = xfs_reflink_cancel_cow_range(ip, 0, NULLFILEOFF, false);
+	/*
+	 * Check again, nobody else should be able to dirty blocks or change
+	 * the reflink iflag now that we have the first two locks held.
+	 */
+	if (xfs_prep_free_cowblocks(ip, ifp))
+		ret = xfs_reflink_cancel_cow_range(ip, 0, NULLFILEOFF, false);
 
 	xfs_iunlock(ip, XFS_MMAPLOCK_EXCL);
 	xfs_iunlock(ip, XFS_IOLOCK_EXCL);
--

To unsubscribe from this list: send the line "unsubscribe linux-xfs" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html