xfs: rework zero range to prevent invalid i_size updates
PATCH v4 1/1 xfs: rework zero range to prevent invalid i_size updates
The zero range operation is analogous to fallocate with the exception of converting the range to zeroes. E.g., it attempts to allocate zeroed blocks over the range specified by the caller. The XFS implementation kills all delalloc blocks currently over the aligned range, converts the range to allocated zero blocks (unwritten extents) and handles the partial pages at the ends of the range by sending writes through the pagecache.
The current implementation suffers from several problems associated with inode size. If the aligned range covers an extending I/O, said I/O is discarded and an inode size update from a previous write never makes it to disk. Further, if an unaligned zero range extends beyond eof, the page write induced for the partial end page can itself increase the inode size, even if the zero range request is not supposed to update i_size (via KEEP_SIZE, similar to an fallocate beyond EOF).
The latter behavior not only incorrectly increases the inode size, but can lead to stray delalloc blocks on the inode. Typically, post-eof preallocation blocks are either truncated on release or inode eviction or explicitly written to by xfs_zero_eof() on natural file size extension. If the inode size increases due to zero range, however, associated blocks leak into the address space having never been converted or mapped to pagecache pages. A direct I/O to such an uncovered range cannot convert the extent via writeback and will BUG(). For example:
$ xfs_io -fc "pwrite 0 128k" -c "fzero -k 1m 54321" <file> ... $ xfs_io -d -c "pread 128k 128k" <file> <BUG>
If the entire delalloc extent happens to not have page coverage whatsoever (e.g., delalloc conversion couldn't find a large enough free space extent), even a full file writeback won't convert what's left of the extent and we'll assert on inode eviction.
Rework xfs_zero_file_space() to avoid buffered I/O for partial pages. Use the existing hole punch and prealloc mechanisms as primitives for zero range. We punch out the pagecache beforehand to eliminate unnecessary writeback. The hole punch mechanism handles partial block zeroing for us and facilitates the use of a single prealloc call over the entire range, which increases the odds of contiguous allocation.
---
This is v4, but effectively an alternate implementation to v3 that reduces zero range to a hole punch and preallocate operation. I originally reproduced a hang with v3 + my unwritten conversion helper patch that I thought somehow related to said helper, but I have seen what appears to be the same thing with this patch after a couple hundred cycles of generic/269. That leads me to believe that there is a more general problem perhaps related to the increased extent manipulation going on in either implementation (whether for conversion or removal). I don't reproduce such a failure with focused hole punch testing on tot, but I do reproduce other assert failures that I suspect just mask the ability to reproduce this one.
In short, all three of these configurations pass basic correctness tests for me. v3 has been the most reliable in stress tests. v3+unwritten conversion and v4 seem to reproduce this hang after a while. v4 is probably the most simple implementation of the three.
Brian
v4:
- Simplify the implementation to use hole punch.
v3: http://oss.sgi.com/archives/xfs/2014-10/msg00149.html
- Pass length to xfs_alloc_file_space() rather than end offset.
- Split up start/end page writeback branches.
- Fix up a bunch of comments.
v2: http://oss.sgi.com/archives/xfs/2014-10/msg00138.html
- Refactor the logic to punch out pagecache/delalloc first and do allocation last to prevent stray delalloc on ENOSPC.
v1: http://oss.sgi.com/archives/xfs/2014-10/msg00052.html
fs/xfs/xfs_bmap_util.c | 95 +++++++++++++++++++++++++++++--------------------- 1 file changed, 55 insertions(+), 40 deletions(-)
fs/xfs/xfs_bmap_util.c +55 -40
@@ -1338,7 +1338,10 @@ xfs_free_file_space( goto out; } - +/* +* Preallocate and zero a range of a file. This mechanism has the allocation +* semantics of fallocate and in addition converts data in the range to zeroes. +*/ int xfs_zero_file_space( struct xfs_inode *ip,
@@ -1346,65 +1349,77 @@ xfs_zero_file_space( xfs_off_t len) {struct xfs_mount *mp = ip->i_mount; -uint granularity; +uint blksize;xfs_off_t start_boundary;xfs_off_t end_boundary;int error; +loff_t eof;trace_xfs_zero_file_space (ip); - granularity =max_t (uint,1 << mp->m_sb.sb_blocklog,PAGE_CACHE_SIZE ); + blksize =1 << mp->m_sb.sb_blocklog;/* -* Round the range of extents we are going to convert inwards. If the -* offset is aligned, then it doesn't get changed so we zero from the -* start of the block offset points to. +* Align the range inward to page size. This represents the range of +* pages that can be tossed, even if dirty. */ - start_boundary =round_up (offset, granularity); - end_boundary =round_down (offset + len, granularity); + start_boundary =round_up (offset,PAGE_CACHE_SIZE ); + end_boundary =round_down (offset + len,PAGE_CACHE_SIZE );ASSERT (start_boundary >= offset);ASSERT (end_boundary <= offset + len); -if (start_boundary < end_boundary -1 ) { +/* +* If the range covers one or more full pages, punch out the pagecache +* and any delalloc blocks over the range. This is an optimization to +* prevent writeback and delalloc extent conversion over the zeroed +* range via the hole punch or prealloc calls. +* +* We only handle the page aligned range here because this function does +* not handle the partial block zeroing necessary to keep the cache and +* on-disk data consistent. That is the responsibility of the +* xfs_free_file_space() call below. +*/ +if (end_boundary > start_boundary) {/* -* Writeback the range to ensure any inode size updates due to -* appending writes make it to disk (otherwise we could just -* punch out the delalloc blocks). +* Flush the eof page first if it falls within the range so we +* do not lose i_size updates. */ - error =filemap_write_and_wait_range (VFS_I (ip)->i_mapping, - start_boundary, end_boundary -1 ); -if (error) - goto out; + eof =round_down (i_size_read (VFS_I (ip)) -1 ,PAGE_CACHE_SIZE ); +if (i_size_read (VFS_I (ip)) > ip->i_d.di_size && + eof >= start_boundary && eof <= end_boundary) +filemap_write_and_wait_range (VFS_I (ip)->i_mapping, eof, +-1 );truncate_pagecache_range (VFS_I (ip), start_boundary, end_boundary -1 ); -/* convert the blocks */ - error =xfs_alloc_file_space (ip, start_boundary, - end_boundary - start_boundary -1 , -XFS_BMAPI_PREALLOC |XFS_BMAPI_CONVERT ); -if (error) - goto out; - -/* We've handled the interior of the range, now for the edges */ -if (start_boundary != offset) { - error =xfs_iozero (ip, offset, start_boundary - offset); -if (error) - goto out; - } - -if (end_boundary != offset + len) - error =xfs_iozero (ip, end_boundary, - offset + len - end_boundary); - - }else { -/* -* It's either a sub-granularity range or the range spanned lies -* partially across two adjacent blocks. -*/ - error =xfs_iozero (ip, offset, len); +xfs_ilock (ip,XFS_ILOCK_EXCL ); + error =xfs_bmap_punch_delalloc_range (ip, +XFS_B_TO_FSBT (mp, start_boundary), +XFS_B_TO_FSB (mp, end_boundary - start_boundary)); +xfs_iunlock (ip,XFS_ILOCK_EXCL ); } +/* +* Punch a hole and prealloc the range. We use hole punch rather than +* unwritten extent conversion for two reasons: +* +* 1.) Hole punch handles partial block zeroing for us. Note that we've +* already tossed pagecache over the aligned range so we won't write +* back too much data (only unaligned start/end pages to avoid races +* with uncached I/O). +* +* 2.) If prealloc returns ENOSPC, the file range is still zero-valued +* by virtue of the hole punch. +*/ + error =xfs_free_file_space (ip, offset, len); +if (error) + goto out; + + error =xfs_alloc_file_space (ip,round_down (offset, blksize), +round_up (offset + len, blksize) - +round_down (offset, blksize), +XFS_BMAPI_PREALLOC ); out:return error;
Signature · 6 lines
1.8.3.1 _______________________________________________ xfs mailing list [email protected] http://oss.sgi.com/mailman/listinfo/xfs
Quoted 43 lines · Brian Foster
The zero range operation is analogous to fallocate with the exception of converting the range to zeroes. E.g., it attempts to allocate zeroed blocks over the range specified by the caller. The XFS implementation kills all delalloc blocks currently over the aligned range, converts the range to allocated zero blocks (unwritten extents) and handles the partial pages at the ends of the range by sending writes through the pagecache.
The current implementation suffers from several problems associated with inode size. If the aligned range covers an extending I/O, said I/O is discarded and an inode size update from a previous write never makes it to disk. Further, if an unaligned zero range extends beyond eof, the page write induced for the partial end page can itself increase the inode size, even if the zero range request is not supposed to update
i_size(viaKEEP_SIZE, similar to an fallocate beyond EOF).The latter behavior not only incorrectly increases the inode size, but can lead to stray delalloc blocks on the inode. Typically, post-eof preallocation blocks are either truncated on release or inode eviction or explicitly written to by
xfs_zero_eof()on natural file size extension. If the inode size increases due to zero range, however, associated blocks leak into the address space having never been converted or mapped to pagecache pages. A direct I/O to such an uncovered range cannot convert the extent via writeback and willBUG(). For example:$ xfs_io -fc "pwrite 0 128k" -c "fzero -k 1m 54321" <file> ... $ xfs_io -d -c "pread 128k 128k" <file> <BUG>If the entire delalloc extent happens to not have page coverage whatsoever (e.g., delalloc conversion couldn't find a large enough free space extent), even a full file writeback won't convert what's left of the extent and we'll assert on inode eviction.
Rework
xfs_zero_file_space()to avoid buffered I/O for partial pages. Use the existing hole punch and prealloc mechanisms as primitives for zero range. We punch out the pagecache beforehand to eliminate unnecessary writeback. The hole punch mechanism handles partial block zeroing for us and facilitates the use of a single prealloc call over the entire range, which increases the odds of contiguous allocation.
Quoted 2 lines
Signed-off-by Brian Foster <[email protected]>---
This patch triggers the same bug pretty much straight away on generic/033 on all my test systems:
[ 306.378041] XFS: Assertion failed: startblockval(del.br_startblock) > 0, file: fs/xfs/libxfs/xfs_bmap.c, line: 5279 [ 306.380694] ------------[ cut here ]------------ [ 306.381655] kernel BUG at fs/xfs/xfs_message.c:107! [ 306.382535] invalid opcode: 0000 [#1] SMP [ 306.383310] Modules linked in: [ 306.383889] CPU: 0 PID: 12151 Comm: xfs_io Not tainted 3.17.0-dgc+ #537 [ 306.384665] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS Bochs 01/01/2011 [ 306.384665] task: ffff88007597e2c0 ti: ffff880077644000 task.ti: ffff880077644000 [ 306.384665] RIP: 0010:[<ffffffff814dadb2>] [<ffffffff814dadb2>] assfail+0x22/0x30 [ 306.384665] RSP: 0018:ffff880077647c98 EFLAGS: 00010296 [ 306.384665] RAX: 0000000000000067 RBX: 0000000000000007 RCX: 0000000000000000 [ 306.384665] RDX: 0000000000000001 RSI: ffff88007fc0d258 RDI: ffff88007fc0d258 [ 306.384665] RBP: ffff880077647c98 R08: 000000000000000a R09: 0000000000000000 [ 306.384665] R10: 000000000000026b R11: ffff880077647946 R12: 0000000000000007 [ 306.384665] R13: ffff88006a35d300 R14: ffff880076669370 R15: ffff880077647db0 [ 306.384665] FS: 00007fc0b858e700(0000) GS:ffff88007fc00000(0000) knlGS:0000000000000000 [ 306.384665] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [ 306.384665] CR2: 000000000061dc40 CR3: 000000007592d000 CR4: 00000000000006f0 [ 306.384665] Stack: [ 306.384665] ffff880077647d88 ffffffff81491341 ffff880077647d40 ffff880077647db4 [ 306.384665] ffff880075997000 0000000100000007 ffff88006a35d340 0000000000000000 [ 306.384665] 0000000000000000 ffffffff00000000 0000000700000000 0000000000000000 [ 306.384665] Call Trace: [ 306.384665] [<ffffffff81491341>] xfs_bunmapi+0x781/0x1000 [ 306.384665] [<ffffffff814c0ad6>] xfs_bmap_punch_delalloc_range+0xf6/0x1a0 [ 306.384665] [<ffffffff814c1b13>] xfs_zero_file_space+0xf3/0x1d0 [ 306.384665] [<ffffffff814c8538>] xfs_file_fallocate+0xe8/0x2f0 [ 306.384665] [<ffffffff811aeb48>] ? __sb_start_write+0x58/0xf0 [ 306.384665] [<ffffffff811aa8b7>] do_fallocate+0x127/0x1c0 [ 306.384665] [<ffffffff811aa994>] SyS_fallocate+0x44/0x70 [ 306.384665] [<ffffffff81d01a29>] system_call_fastpath+0x16/0x1b [ 306.384665] Code: 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 55 48 89 f1 41 89 d0 48 89 e5 48 89 fa 48 c7 c6 f0 67 15 82 31 ff 31 c0 e8 ce fb ff ff <0f> 0b 66 66 66 [ 306.384665] RIP [<ffffffff814dadb2>] assfail+0x22/0x30 [ 306.384665] RSP <ffff880077647c98> [ 306.418027] ---[ end trace 18ffcc2e14a50ab1 ]---
I'm running 3.17 + for-next + a handful of local patches, but this is the only patch that modifies anything in this area. I'll remove all the other patches I have just to check....
Cheers,
Dave.
Signature · 7 lines
Dave Chinner [email protected] _______________________________________________ xfs mailing list [email protected] http://oss.sgi.com/mailman/listinfo/xfs
Quoted 3 lines · Dave Chinner
I'm running 3.17 + for-next + a handful of local patches, but this is the only patch that modifies anything in this area. I'll remove all the other patches I have just to check....
When this is the only patch on top of 3.17+for-next it still triggers.
Cheers,
Dave.
Signature · 7 lines
Dave Chinner [email protected] _______________________________________________ xfs mailing list [email protected] http://oss.sgi.com/mailman/listinfo/xfs
Quoted 8 lines · Dave Chinner
Quoted 3 lines · Dave Chinner
I'm running 3.17 + for-next + a handful of local patches, but this is the only patch that modifies anything in this area. I'll remove all the other patches I have just to check....
When this is the only patch on top of 3.17+for-next it still triggers.
Interesting, I didn't catch this. I do reproduce with v4 and v3. Note that this is the assert for having at least 1 indirect block reservation, so it's a separate problem (not a stale delalloc problem):
http://oss.sgi.com/archives/xfs/2014-09/msg00337.html
What looks like is going on here is this patch regresses current for-next due to our previous zero range workaround to flush on zero range. This was originally reproducible on tot, the zero range flush quiets it down by forcing delalloc conversion, and this patch reintroduces it simply by eliminating the flush.
I've just recently got back to fixing that problem. I have a couple patches that need more testing, but I'll try to get them posted for review today.
Brian
Quoted 6 lines
Cheers,
Dave.
Signature · 2 lines
Dave Chinner [email protected]
_______________________________________________
xfs mailing list
[email protected]
http://oss.sgi.com/mailman/listinfo/xfs
Quoted 178 lines · Brian Foster
The zero range operation is analogous to fallocate with the exception of converting the range to zeroes. E.g., it attempts to allocate zeroed blocks over the range specified by the caller. The XFS implementation kills all delalloc blocks currently over the aligned range, converts the range to allocated zero blocks (unwritten extents) and handles the partial pages at the ends of the range by sending writes through the pagecache.
The current implementation suffers from several problems associated with inode size. If the aligned range covers an extending I/O, said I/O is discarded and an inode size update from a previous write never makes it to disk. Further, if an unaligned zero range extends beyond eof, the page write induced for the partial end page can itself increase the inode size, even if the zero range request is not supposed to update
i_size(viaKEEP_SIZE, similar to an fallocate beyond EOF).The latter behavior not only incorrectly increases the inode size, but can lead to stray delalloc blocks on the inode. Typically, post-eof preallocation blocks are either truncated on release or inode eviction or explicitly written to by
xfs_zero_eof()on natural file size extension. If the inode size increases due to zero range, however, associated blocks leak into the address space having never been converted or mapped to pagecache pages. A direct I/O to such an uncovered range cannot convert the extent via writeback and willBUG(). For example:$ xfs_io -fc "pwrite 0 128k" -c "fzero -k 1m 54321" <file> ... $ xfs_io -d -c "pread 128k 128k" <file> <BUG>If the entire delalloc extent happens to not have page coverage whatsoever (e.g., delalloc conversion couldn't find a large enough free space extent), even a full file writeback won't convert what's left of the extent and we'll assert on inode eviction.
Rework
xfs_zero_file_space()to avoid buffered I/O for partial pages. Use the existing hole punch and prealloc mechanisms as primitives for zero range. We punch out the pagecache beforehand to eliminate unnecessary writeback. The hole punch mechanism handles partial block zeroing for us and facilitates the use of a single prealloc call over the entire range, which increases the odds of contiguous allocation.Signed-off-by Brian Foster <[email protected]>---
This is v4, but effectively an alternate implementation to v3 that reduces zero range to a hole punch and preallocate operation. I originally reproduced a hang with v3 + my unwritten conversion helper patch that I thought somehow related to said helper, but I have seen what appears to be the same thing with this patch after a couple hundred cycles of generic/269. That leads me to believe that there is a more general problem perhaps related to the increased extent manipulation going on in either implementation (whether for conversion or removal). I don't reproduce such a failure with focused hole punch testing on tot, but I do reproduce other assert failures that I suspect just mask the ability to reproduce this one.
In short, all three of these configurations pass basic correctness tests for me. v3 has been the most reliable in stress tests. v3+unwritten conversion and v4 seem to reproduce this hang after a while. v4 is probably the most simple implementation of the three.
Brian
v4:
- Simplify the implementation to use hole punch.
v3: http://oss.sgi.com/archives/xfs/2014-10/msg00149.html
- Pass length toxfs_alloc_file_space()rather than end offset.
- Split up start/end page writeback branches.
- Fix up a bunch of comments.
v2: http://oss.sgi.com/archives/xfs/2014-10/msg00138.html
- Refactor the logic to punch out pagecache/delalloc first and do allocation last to prevent stray delalloc on ENOSPC.
v1: http://oss.sgi.com/archives/xfs/2014-10/msg00052.htmlfs/xfs/xfs_bmap_util.c | 95 +++++++++++++++++++++++++++++--------------------- 1 file changed, 55 insertions(+), 40 deletions(-)fs/xfs/xfs_bmap_util.c +30
-36@@ -1338,7 +1338,10 @@ xfs_free_file_space( goto out; } - +/* +* Preallocate and zero a range of a file. This mechanism has the allocation +* semantics of fallocate and in addition converts data in the range to zeroes. +*/ int xfs_zero_file_space( struct xfs_inode *ip,@@ -1346,65 +1349,77 @@ xfs_zero_file_space( xfs_off_t len) {struct xfs_mount *mp = ip->i_mount; -uint granularity; +uint blksize;xfs_off_t start_boundary;xfs_off_t end_boundary;int error; +loff_t eof;trace_xfs_zero_file_space (ip); - granularity =max_t (uint,1 << mp->m_sb.sb_blocklog,PAGE_CACHE_SIZE ); + blksize =1 << mp->m_sb.sb_blocklog;/* -* Round the range of extents we are going to convert inwards. If the -* offset is aligned, then it doesn't get changed so we zero from the -* start of the block offset points to. +* Align the range inward to page size. This represents the range of +* pages that can be tossed, even if dirty. */ - start_boundary =round_up (offset, granularity); - end_boundary =round_down (offset + len, granularity); + start_boundary =round_up (offset,PAGE_CACHE_SIZE ); + end_boundary =round_down (offset + len,PAGE_CACHE_SIZE );ASSERT (start_boundary >= offset);ASSERT (end_boundary <= offset + len); -if (start_boundary < end_boundary -1 ) { +/* +* If the range covers one or more full pages, punch out the pagecache +* and any delalloc blocks over the range. This is an optimization to +* prevent writeback and delalloc extent conversion over the zeroed +* range via the hole punch or prealloc calls. +* +* We only handle the page aligned range here because this function does +* not handle the partial block zeroing necessary to keep the cache and +* on-disk data consistent. That is the responsibility of the +* xfs_free_file_space() call below. +*/ +if (end_boundary > start_boundary) {/* -* Writeback the range to ensure any inode size updates due to -* appending writes make it to disk (otherwise we could just -* punch out the delalloc blocks). +* Flush the eof page first if it falls within the range so we +* do not lose i_size updates. */ - error =filemap_write_and_wait_range (VFS_I (ip)->i_mapping, - start_boundary, end_boundary -1 ); -if (error) - goto out; + eof =round_down (i_size_read (VFS_I (ip)) -1 ,PAGE_CACHE_SIZE ); +if (i_size_read (VFS_I (ip)) > ip->i_d.di_size && + eof >= start_boundary && eof <= end_boundary) +filemap_write_and_wait_range (VFS_I (ip)->i_mapping, eof, +-1 );truncate_pagecache_range (VFS_I (ip), start_boundary, end_boundary -1 ); -/* convert the blocks */ - error =xfs_alloc_file_space (ip, start_boundary, - end_boundary - start_boundary -1 , -XFS_BMAPI_PREALLOC |XFS_BMAPI_CONVERT ); -if (error) - goto out; - -/* We've handled the interior of the range, now for the edges */ -if (start_boundary != offset) { - error =xfs_iozero (ip, offset, start_boundary - offset); -if (error) - goto out; - } - -if (end_boundary != offset + len) - error =xfs_iozero (ip, end_boundary, - offset + len - end_boundary); - - }else { -/*
fs/xfs/xfs_bmap_util.c xfs_zero_file_space( · line 1401
Quoted 10 lines
fs/xfs/xfs_bmap_util.c +5
-4-* It's either a sub-granularity range or the range spanned lies -* partially across two adjacent blocks. -*/ - error =xfs_iozero (ip, offset, len); +xfs_ilock (ip,XFS_ILOCK_EXCL ); + error =xfs_bmap_punch_delalloc_range (ip, +XFS_B_TO_FSBT (mp, start_boundary), +XFS_B_TO_FSB (mp, end_boundary - start_boundary)); +xfs_iunlock (ip,XFS_ILOCK_EXCL ); }
It appears that the tp->t_blk_res overrun assert is also related to delalloc punching. I'm basically seeing a writepage attempted for a page over a delalloc extent (imap in writepage reflects this), but at some point before we get into the allocation (I suspect between where we release and reacquire ilock in xfs_map_blocks() to iomap_write_allocate()) the delalloc blocks that relate this particular page to the extent disappear. An in-core extent dump after doing the allocation but before inserting into the extent tree shows the range to be allocated as a hole. Indeed, the allocator saw a hole (wasdel == 0) and ended up doing the full allocation while the transaction had a reservation for a delalloc conversion, which we end up overrunning.
It's not quite clear to me how this is happening. I would expect truncate_pagecache_range() to serialize against writeback via page lock. Regardless, the scenario of writing back pages into unallocated space that writeback thinks is covered by delayed allocation suggests something racing with delalloc punching. The problem seems to disappear if I comment it out from zero range, so we might want to kill the above hunk entirely for the time being.
Brian
Quoted 31 lines
fs/xfs/xfs_bmap_util.c +20
-0+/* +* Punch a hole and prealloc the range. We use hole punch rather than +* unwritten extent conversion for two reasons: +* +* 1.) Hole punch handles partial block zeroing for us. Note that we've +* already tossed pagecache over the aligned range so we won't write +* back too much data (only unaligned start/end pages to avoid races +* with uncached I/O). +* +* 2.) If prealloc returns ENOSPC, the file range is still zero-valued +* by virtue of the hole punch. +*/ + error =xfs_free_file_space (ip, offset, len); +if (error) + goto out; + + error =xfs_alloc_file_space (ip,round_down (offset, blksize), +round_up (offset + len, blksize) - +round_down (offset, blksize), +XFS_BMAPI_PREALLOC ); out:return error;Signature · 6 lines
1.8.3.1 _______________________________________________ xfs mailing list [email protected] http://oss.sgi.com/mailman/listinfo/xfs
_______________________________________________
xfs mailing list
[email protected]
http://oss.sgi.com/mailman/listinfo/xfs
Quoted 12 lines · Brian Foster
It appears that the
tp->t_blk_resoverrun assert is also related to delalloc punching. I'm basically seeing a writepage attempted for a page over a delalloc extent (imap in writepage reflects this), but at some point before we get into the allocation (I suspect between where we release and reacquire ilock inxfs_map_blocks()toiomap_write_allocate()) the delalloc blocks that relate this particular page to the extent disappear. An in-core extent dump after doing the allocation but before inserting into the extent tree shows the range to be allocated as a hole. Indeed, the allocator saw a hole (wasdel == 0) and ended up doing the full allocation while the transaction had a reservation for a delalloc conversion, which we end up overrunning.
Quoted 2 lines
It's not quite clear to me how this is happening. I would expect
truncate_pagecache_range()to serialize against writeback via page lock.
It does. But the problem won't be truncate_pagecache_range(), it will be that punching delalloc blocks doesn't serialise against the page lock.
i.e. we run truncate_pagecache_range/truncate_setsize without holding inode locks, and they serialise against writeback via page locks. However, we run xfs_bmap_punch_delalloc_range() holding inode locks, but no page locks. Hence both can serialise against writeback, but not against each other. therefore:
writeback zero range
page_lock
page is delalloc
XFS_ILOCK_SHARED XFS_ILOCK_EXCL
map delalloc <blocks>
xfs_iunlock
punch delalloc range xfs_iunlock
XFS_ILOCK_EXCL pagecache_truncate_range
allocate HOLE!!!! page_lock
xfs_iunlock <blocks>
starts page writeback
unlocks page
waits on writeback removes page from cache
Basically, we back to the fundamental problem that we can't manipulate extent state directly if there are *unlocked* pages in the page cache over that range because we hold meaningful writeback state on the page.
Quoted 5 lines
Regardless, the scenario of writing back pages into unallocated space that writeback thinks is covered by delayed allocation suggests something racing with delalloc punching. The problem seems to disappear if I comment it out from zero range, so we might want to kill the above hunk entirely for the time being.
Yes, but I think I see the way forward now. xfs_vm_writepage() needs to unconditionally map the page it is being passed rather than relying on the state of bufferheads attached to the buffer. We can't serialise the page cache state against direct extent manipulations, therefore we have to make all decisions based on one set of state we can serialise access to sanely.
The issue is that we can't currently serialise extent manipulations against writeback easily, but I think I see a neat and easy way to do this, and it mirrors a technique we already use(*): add a cursor to track the active map we are operating on so that we know if we are about to modify and extent we are currently writing to.
Actually, a cursor solves a couple of other problems in this mapping code - see the big comment in xfs_iomap_write_allocate() about using a single map to avoid lookup/unlock/lock/allocate race conditions with truncate/hole-punch.
So I think I have a solution that moves us towards the goal of "die, bufferheads, die" and solves these writeback vs page cache vs extent manipulation issues - let me do a bit more thinking about it and I'll write something up.
Cheers,
Dave.
(*) we use a cursor for a similar purpose during AIL traversal: to detect item deletion while the AIL lock was dropped that would cause us to follow an invalid list pointer.
Signature · 7 lines
Dave Chinner [email protected] _______________________________________________ xfs mailing list [email protected] http://oss.sgi.com/mailman/listinfo/xfs
Quoted 13 lines · Dave Chinner
Quoted 12 lines · Brian Foster
It appears that the
tp->t_blk_resoverrun assert is also related to delalloc punching. I'm basically seeing a writepage attempted for a page over a delalloc extent (imap in writepage reflects this), but at some point before we get into the allocation (I suspect between where we release and reacquire ilock inxfs_map_blocks()toiomap_write_allocate()) the delalloc blocks that relate this particular page to the extent disappear. An in-core extent dump after doing the allocation but before inserting into the extent tree shows the range to be allocated as a hole. Indeed, the allocator saw a hole (wasdel == 0) and ended up doing the full allocation while the transaction had a reservation for a delalloc conversion, which we end up overrunning.
Quoted 7 lines
Quoted 2 lines
It's not quite clear to me how this is happening. I would expect
truncate_pagecache_range()to serialize against writeback via page lock.It does. But the problem won't be
truncate_pagecache_range(), it will be that punching delalloc blocks doesn't serialise against the page lock.
Ok...
Quoted 6 lines
i.e. we run truncate_pagecache_range/truncate_setsize without holding inode locks, and they serialise against writeback via page locks. However, we run
xfs_bmap_punch_delalloc_range()holding inode locks, but no page locks. Hence both can serialise against writeback, but not against each other. therefore:
Quoted 7 lines
writeback zero range
page_lock
page is delalloc
XFS_ILOCK_SHAREDXFS_ILOCK_EXCL
map delalloc <blocks>
xfs_iunlock
This pretty much maps the observed behavior and what I suspect is happening on a basic level (e.g., this ilock cycle opens a race window). What still isn't clear is how this gets past the pagecache truncate in such a state. The zero range path truncates pagecache before the delalloc punch, which does serialize on the page lock. If it gets the lock it completely tosses the page, which it looks like the mapping checks in writepage should handle. If it doesn't get the lock or the page is writeback, it looks like it waits on writeback.
This is an fsx workload so it is single threaded I/O. I'm not quite seeing how we get through the writeback/pagecache truncate in a state with a delalloc page.
Quoted 5 lines
punch delalloc range
xfs_iunlock
XFS_ILOCK_EXCLpagecache_truncate_range
allocate HOLE!!!!page_lock
xfs_iunlock<blocks>
Ah, so this makes sense according to the code as it is today because we punch delalloc blocks and then truncate pagecache. Note that this zero range rework does the pagecache truncate first, then punches out the delalloc blocks. Hence my expectation of either seeing the page tossed or the extent converted before getting to the delalloc punch.
Quoted 16 lines
starts page writeback
unlocks page
waits on writeback removes page from cacheBasically, we back to the fundamental problem that we can't manipulate extent state directly if there are *unlocked* pages in the page cache over that range because we hold meaningful writeback state on the page.
Quoted 5 lines
Regardless, the scenario of writing back pages into unallocated space that writeback thinks is covered by delayed allocation suggests something racing with delalloc punching. The problem seems to disappear if I comment it out from zero range, so we might want to kill the above hunk entirely for the time being.
Quoted 7 lines
Yes, but I think I see the way forward now.
xfs_vm_writepage()needs to unconditionally map the page it is being passed rather than relying on the state of bufferheads attached to the buffer. We can't serialise the page cache state against direct extent manipulations, therefore we have to make all decisions based on one set of state we can serialise access to sanely.
Sounds interesting. I'm all for condensing our page state management towards killing buffer_head. Tracking down some of these issues has helped clarify the general problem for me and also shows that instances are quite difficult to root cause. This one in particular is a zero range operation that presumably works (no fsx failures), only to cause an implicit error in an asynchronous path (writeback) sometime later. That makes it difficult to detect and equally difficult to try and narrow down.
Quoted 6 lines
The issue is that we can't currently serialise extent manipulations against writeback easily, but I think I see a neat and easy way to do this, and it mirrors a technique we already
use(*): add a cursor to track the active map we are operating on so that we know if we are about to modify and extent we are currently writing to.
Ok, so I take it this would allow us to mark an extent as "under modification" or "in-use" in the sense of its state is significant for a code sequence larger than an ilock critical section (i.e., an imap, extent-conversion, writeback sequence). Therefore, one side can invalidate another if a modification is made, or conversely the other side could simply block on the operation if that is more appropriate. I _think_ I get where you're going there, but I await more details. ;)
I'll post a new version of this patch regardless because as mentioned previously, I'd like to get some kind of checkpoint into the tree that works correctly (no asserts), even if the implementation is not ideal. I've not been able to reproduce any asserts so far without the delalloc punch, but I'd also like to see if any of your subsequent testing uncovers anything else.
Brian
Quoted 10 lines
Actually, a cursor solves a couple of other problems in this mapping code - see the big comment in
xfs_iomap_write_allocate()about using a single map to avoid lookup/unlock/lock/allocate race conditions with truncate/hole-punch.So I think I have a solution that moves us towards the goal of "die, bufferheads, die" and solves these writeback vs page cache vs extent manipulation issues - let me do a bit more thinking about it and I'll write something up.
Quoted 15 lines
Cheers,
Dave.
(*) we use a cursor for a similar purpose during AIL traversal: to detect item deletion while the AIL lock was dropped that would cause us to follow an invalid list pointer.
Signature · 7 lines
Dave Chinner [email protected] _______________________________________________ xfs mailing list [email protected] http://oss.sgi.com/mailman/listinfo/xfs
_______________________________________________
xfs mailing list
[email protected]
http://oss.sgi.com/mailman/listinfo/xfs