2018-06-06 02:42:14 +00:00
|
|
|
// SPDX-License-Identifier: GPL-2.0
|
2005-04-16 22:20:36 +00:00
|
|
|
/*
|
2006-09-28 00:52:15 +00:00
|
|
|
* Copyright (c) 2000-2006 Silicon Graphics, Inc.
|
2005-11-02 03:58:39 +00:00
|
|
|
* All Rights Reserved.
|
2005-04-16 22:20:36 +00:00
|
|
|
*/
|
2006-11-11 07:03:49 +00:00
|
|
|
#include "xfs.h"
|
2006-10-20 06:28:16 +00:00
|
|
|
#include <linux/backing-dev.h>
|
2022-06-03 05:37:30 +00:00
|
|
|
#include <linux/dax.h>
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2019-06-29 02:25:35 +00:00
|
|
|
#include "xfs_shared.h"
|
2014-11-28 03:25:04 +00:00
|
|
|
#include "xfs_format.h"
|
2013-10-22 23:50:10 +00:00
|
|
|
#include "xfs_log_format.h"
|
2013-08-12 10:49:32 +00:00
|
|
|
#include "xfs_trans_resv.h"
|
2009-03-03 19:48:37 +00:00
|
|
|
#include "xfs_mount.h"
|
2009-12-14 23:14:59 +00:00
|
|
|
#include "xfs_trace.h"
|
2013-10-22 23:50:10 +00:00
|
|
|
#include "xfs_log.h"
|
2020-06-29 21:48:47 +00:00
|
|
|
#include "xfs_log_recover.h"
|
xfs: xfs_is_shutdown vs xlog_is_shutdown cage fight
I've been chasing a recent resurgence in generic/388 recovery
failure and/or corruption events. The events have largely been
uninitialised inode chunks being tripped over in log recovery
such as:
XFS (pmem1): User initiated shutdown received.
pmem1: writeback error on inode 12621949, offset 1019904, sector 12968096
XFS (pmem1): Log I/O Error (0x6) detected at xfs_fs_goingdown+0xa3/0xf0 (fs/xfs/xfs_fsops.c:500). Shutting down filesystem.
XFS (pmem1): Please unmount the filesystem and rectify the problem(s)
XFS (pmem1): Unmounting Filesystem
XFS (pmem1): Mounting V5 Filesystem
XFS (pmem1): Starting recovery (logdev: internal)
XFS (pmem1): bad inode magic/vsn daddr 8723584 #0 (magic=1818)
XFS (pmem1): Metadata corruption detected at xfs_inode_buf_verify+0x180/0x190, xfs_inode block 0x851c80 xfs_inode_buf_verify
XFS (pmem1): Unmount and run xfs_repair
XFS (pmem1): First 128 bytes of corrupted metadata buffer:
00000000: 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 ................
00000010: 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 ................
00000020: 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 ................
00000030: 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 ................
00000040: 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 ................
00000050: 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 ................
00000060: 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 ................
00000070: 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 ................
XFS (pmem1): metadata I/O error in "xlog_recover_items_pass2+0x52/0xc0" at daddr 0x851c80 len 32 error 117
XFS (pmem1): log mount/recovery failed: error -117
XFS (pmem1): log mount failed
There have been isolated random other issues, too - xfs_repair fails
because it finds some corruption in symlink blocks, rmap
inconsistencies, etc - but they are nowhere near as common as the
uninitialised inode chunk failure.
The problem has clearly happened at runtime before recovery has run;
I can see the ICREATE log item in the log shortly before the
actively recovered range of the log. This means the ICREATE was
definitely created and written to the log, but for some reason the
tail of the log has been moved past the ordered buffer log item that
tracks INODE_ALLOC buffers and, supposedly, prevents the tail of the
log moving past the ICREATE log item before the inode chunk buffer
is written to disk.
Tracing the fsstress processes that are running when the filesystem
shut down immediately pin-pointed the problem:
user shutdown marks xfs_mount as shutdown
godown-213341 [008] 6398.022871: console: [ 6397.915392] XFS (pmem1): User initiated shutdown received.
.....
aild tries to push ordered inode cluster buffer
xfsaild/pmem1-213314 [001] 6398.022974: xfs_buf_trylock: dev 259:1 daddr 0x851c80 bbcount 0x20 hold 16 pincount 0 lock 0 flags DONE|INODES|PAGES caller xfs_inode_item_push+0x8e
xfsaild/pmem1-213314 [001] 6398.022976: xfs_ilock_nowait: dev 259:1 ino 0x851c80 flags ILOCK_SHARED caller xfs_iflush_cluster+0xae
xfs_iflush_cluster() checks xfs_is_shutdown(), returns true,
calls xfs_iflush_abort() to kill writeback of the inode.
Inode is removed from AIL, drops cluster buffer reference.
xfsaild/pmem1-213314 [001] 6398.022977: xfs_ail_delete: dev 259:1 lip 0xffff88880247ed80 old lsn 7/20344 new lsn 7/21000 type XFS_LI_INODE flags IN_AIL
xfsaild/pmem1-213314 [001] 6398.022978: xfs_buf_rele: dev 259:1 daddr 0x851c80 bbcount 0x20 hold 17 pincount 0 lock 0 flags DONE|INODES|PAGES caller xfs_iflush_abort+0xd7
.....
All inodes on cluster buffer are aborted, then the cluster buffer
itself is aborted and removed from the AIL *without writeback*:
xfsaild/pmem1-213314 [001] 6398.023011: xfs_buf_error_relse: dev 259:1 daddr 0x851c80 bbcount 0x20 hold 2 pincount 0 lock 0 flags ASYNC|DONE|STALE|INODES|PAGES caller xfs_buf_ioend_fail+0x33
xfsaild/pmem1-213314 [001] 6398.023012: xfs_ail_delete: dev 259:1 lip 0xffff8888053efde8 old lsn 7/20344 new lsn 7/20344 type XFS_LI_BUF flags IN_AIL
The inode buffer was at 7/20344 when it was removed from the AIL.
xfsaild/pmem1-213314 [001] 6398.023012: xfs_buf_item_relse: dev 259:1 daddr 0x851c80 bbcount 0x20 hold 2 pincount 0 lock 0 flags ASYNC|DONE|STALE|INODES|PAGES caller xfs_buf_item_done+0x31
xfsaild/pmem1-213314 [001] 6398.023012: xfs_buf_rele: dev 259:1 daddr 0x851c80 bbcount 0x20 hold 2 pincount 0 lock 0 flags ASYNC|DONE|STALE|INODES|PAGES caller xfs_buf_item_relse+0x39
.....
Userspace is still running, doing stuff. an fsstress process runs
syncfs() or sync() and we end up in sync_fs_one_sb() which issues
a log force. This pushes on the CIL:
fsstress-213322 [001] 6398.024430: xfs_fs_sync_fs: dev 259:1 m_features 0x20000000019ff6e9 opstate (clean|shutdown|inodegc|blockgc) s_flags 0x70810000 caller sync_fs_one_sb+0x26
fsstress-213322 [001] 6398.024430: xfs_log_force: dev 259:1 lsn 0x0 caller xfs_fs_sync_fs+0x82
fsstress-213322 [001] 6398.024430: xfs_log_force: dev 259:1 lsn 0x5f caller xfs_log_force+0x7c
<...>-194402 [001] 6398.024467: kmem_alloc: size 176 flags 0x14 caller xlog_cil_push_work+0x9f
And the CIL fills up iclogs with pending changes. This picks up
the current tail from the AIL:
<...>-194402 [001] 6398.024497: xlog_iclog_get_space: dev 259:1 state XLOG_STATE_ACTIVE refcnt 1 offset 0 lsn 0x0 flags caller xlog_write+0x149
<...>-194402 [001] 6398.024498: xlog_iclog_switch: dev 259:1 state XLOG_STATE_ACTIVE refcnt 1 offset 0 lsn 0x700005408 flags caller xlog_state_get_iclog_space+0x37e
<...>-194402 [001] 6398.024521: xlog_iclog_release: dev 259:1 state XLOG_STATE_WANT_SYNC refcnt 1 offset 32256 lsn 0x700005408 flags caller xlog_write+0x5f9
<...>-194402 [001] 6398.024522: xfs_log_assign_tail_lsn: dev 259:1 new tail lsn 7/21000, old lsn 7/20344, last sync 7/21448
And it moves the tail of the log to 7/21000 from 7/20344. This
*moves the tail of the log beyond the ICREATE transaction* that was
at 7/20344 and pinned by the inode cluster buffer that was cancelled
above.
....
godown-213341 [008] 6398.027005: xfs_force_shutdown: dev 259:1 tag logerror flags log_io|force_umount file fs/xfs/xfs_fsops.c line_num 500
godown-213341 [008] 6398.027022: console: [ 6397.915406] pmem1: writeback error on inode 12621949, offset 1019904, sector 12968096
godown-213341 [008] 6398.030551: console: [ 6397.919546] XFS (pmem1): Log I/O Error (0x6) detected at xfs_fs_goingdown+0xa3/0xf0 (fs/
And finally the log itself is now shutdown, stopping all further
writes to the log. But this is too late to prevent the corruption
that moving the tail of the log forwards after we start cancelling
writeback causes.
The fundamental problem here is that we are using the wrong shutdown
checks for log items. We've long conflated mount shutdown with log
shutdown state, and I started separating that recently with the
atomic shutdown state changes in commit b36d4651e165 ("xfs: make
forced shutdown processing atomic"). The changes in that commit
series are directly responsible for being able to diagnose this
issue because it clearly separated mount shutdown from log shutdown.
Essentially, once we start cancelling writeback of log items and
removing them from the AIL because the filesystem is shut down, we
*cannot* update the journal because we may have cancelled the items
that pin the tail of the log. That moves the tail of the log
forwards without having written the metadata back, hence we have
corrupt in memory state and writing to the journal propagates that
to the on-disk state.
What commit b36d4651e165 makes clear is that log item state needs to
change relative to log shutdown, not mount shutdown. IOWs, anything
that aborts metadata writeback needs to check log shutdown state
because log items directly affect log consistency. Having them check
mount shutdown state introduces the above race condition where we
cancel metadata writeback before the log shuts down.
To fix this, this patch works through all log items and converts
shutdown checks to use xlog_is_shutdown() rather than
xfs_is_shutdown(), so that we don't start aborting metadata
writeback before we shut off journal writes.
AFAICT, this race condition is a zero day IO error handling bug in
XFS that dates back to the introduction of XLOG_IO_ERROR,
XLOG_STATE_IOERROR and XFS_FORCED_SHUTDOWN back in January 1997.
Signed-off-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Darrick J. Wong <djwong@kernel.org>
Signed-off-by: Darrick J. Wong <djwong@kernel.org>
2022-03-17 16:09:13 +00:00
|
|
|
#include "xfs_log_priv.h"
|
2020-06-29 21:48:46 +00:00
|
|
|
#include "xfs_trans.h"
|
|
|
|
#include "xfs_buf_item.h"
|
2017-10-31 19:04:49 +00:00
|
|
|
#include "xfs_errortag.h"
|
2017-10-17 21:16:29 +00:00
|
|
|
#include "xfs_error.h"
|
2021-06-02 00:48:24 +00:00
|
|
|
#include "xfs_ag.h"
|
2024-02-22 20:43:21 +00:00
|
|
|
#include "xfs_buf_mem.h"
|
2009-03-03 19:48:37 +00:00
|
|
|
|
2022-07-19 01:20:37 +00:00
|
|
|
struct kmem_cache *xfs_buf_cache;
|
2005-06-21 05:14:01 +00:00
|
|
|
|
2018-10-18 06:21:29 +00:00
|
|
|
/*
|
|
|
|
* Locking orders
|
|
|
|
*
|
|
|
|
* xfs_buf_ioacct_inc:
|
|
|
|
* xfs_buf_ioacct_dec:
|
|
|
|
* b_sema (caller holds)
|
|
|
|
* b_lock
|
|
|
|
*
|
|
|
|
* xfs_buf_stale:
|
|
|
|
* b_sema (caller holds)
|
|
|
|
* b_lock
|
|
|
|
* lru_lock
|
|
|
|
*
|
|
|
|
* xfs_buf_rele:
|
|
|
|
* b_lock
|
|
|
|
* pag_buf_lock
|
|
|
|
* lru_lock
|
|
|
|
*
|
2021-01-23 00:48:19 +00:00
|
|
|
* xfs_buftarg_drain_rele
|
2018-10-18 06:21:29 +00:00
|
|
|
* lru_lock
|
|
|
|
* b_lock (trylock due to inversion)
|
|
|
|
*
|
|
|
|
* xfs_buftarg_isolate
|
|
|
|
* lru_lock
|
|
|
|
* b_lock (trylock due to inversion)
|
|
|
|
*/
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2020-09-01 17:55:47 +00:00
|
|
|
static int __xfs_buf_submit(struct xfs_buf *bp, bool wait);
|
|
|
|
|
|
|
|
static inline int
|
|
|
|
xfs_buf_submit(
|
|
|
|
struct xfs_buf *bp)
|
|
|
|
{
|
|
|
|
return __xfs_buf_submit(bp, !(bp->b_flags & XBF_ASYNC));
|
|
|
|
}
|
|
|
|
|
2024-02-22 20:41:02 +00:00
|
|
|
static inline bool xfs_buf_is_uncached(struct xfs_buf *bp)
|
|
|
|
{
|
|
|
|
return bp->b_rhash_key == XFS_BUF_DADDR_NULL;
|
|
|
|
}
|
|
|
|
|
2010-01-25 17:42:24 +00:00
|
|
|
static inline int
|
|
|
|
xfs_buf_is_vmapped(
|
|
|
|
struct xfs_buf *bp)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Return true if the buffer is vmapped.
|
|
|
|
*
|
2012-04-23 05:59:07 +00:00
|
|
|
* b_addr is null if the buffer is not mapped, but the code is clever
|
|
|
|
* enough to know it doesn't have to map a single page, so the check has
|
|
|
|
* to be both for b_addr and bp->b_page_count > 1.
|
2010-01-25 17:42:24 +00:00
|
|
|
*/
|
2012-04-23 05:59:07 +00:00
|
|
|
return bp->b_addr && bp->b_page_count > 1;
|
2010-01-25 17:42:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static inline int
|
|
|
|
xfs_buf_vmap_len(
|
|
|
|
struct xfs_buf *bp)
|
|
|
|
{
|
2021-06-07 01:49:50 +00:00
|
|
|
return (bp->b_page_count * PAGE_SIZE);
|
2010-01-25 17:42:24 +00:00
|
|
|
}
|
|
|
|
|
xfs: track and serialize in-flight async buffers against unmount
Newly allocated XFS metadata buffers are added to the LRU once the hold
count is released, which typically occurs after I/O completion. There is
no other mechanism at current that tracks the existence or I/O state of
a new buffer. Further, readahead I/O tends to be submitted
asynchronously by nature, which means the I/O can remain in flight and
actually complete long after the calling context is gone. This means
that file descriptors or any other holds on the filesystem can be
released, allowing the filesystem to be unmounted while I/O is still in
flight. When I/O completion occurs, core data structures may have been
freed, causing completion to run into invalid memory accesses and likely
to panic.
This problem is reproduced on XFS via directory readahead. A filesystem
is mounted, a directory is opened/closed and the filesystem immediately
unmounted. The open/close cycle triggers a directory readahead that if
delayed long enough, runs buffer I/O completion after the unmount has
completed.
To address this problem, add a mechanism to track all in-flight,
asynchronous buffers using per-cpu counters in the buftarg. The buffer
is accounted on the first I/O submission after the current reference is
acquired and unaccounted once the buffer is returned to the LRU or
freed. Update xfs_wait_buftarg() to wait on all in-flight I/O before
walking the LRU list. Once in-flight I/O has completed and the workqueue
has drained, all new buffers should have been released onto the LRU.
Signed-off-by: Brian Foster <bfoster@redhat.com>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Signed-off-by: Dave Chinner <david@fromorbit.com>
2016-07-20 01:15:28 +00:00
|
|
|
/*
|
|
|
|
* Bump the I/O in flight count on the buftarg if we haven't yet done so for
|
|
|
|
* this buffer. The count is incremented once per buffer (per hold cycle)
|
|
|
|
* because the corresponding decrement is deferred to buffer release. Buffers
|
|
|
|
* can undergo I/O multiple times in a hold-release cycle and per buffer I/O
|
|
|
|
* tracking adds unnecessary overhead. This is used for sychronization purposes
|
2021-01-23 00:48:19 +00:00
|
|
|
* with unmount (see xfs_buftarg_drain()), so all we really need is a count of
|
xfs: track and serialize in-flight async buffers against unmount
Newly allocated XFS metadata buffers are added to the LRU once the hold
count is released, which typically occurs after I/O completion. There is
no other mechanism at current that tracks the existence or I/O state of
a new buffer. Further, readahead I/O tends to be submitted
asynchronously by nature, which means the I/O can remain in flight and
actually complete long after the calling context is gone. This means
that file descriptors or any other holds on the filesystem can be
released, allowing the filesystem to be unmounted while I/O is still in
flight. When I/O completion occurs, core data structures may have been
freed, causing completion to run into invalid memory accesses and likely
to panic.
This problem is reproduced on XFS via directory readahead. A filesystem
is mounted, a directory is opened/closed and the filesystem immediately
unmounted. The open/close cycle triggers a directory readahead that if
delayed long enough, runs buffer I/O completion after the unmount has
completed.
To address this problem, add a mechanism to track all in-flight,
asynchronous buffers using per-cpu counters in the buftarg. The buffer
is accounted on the first I/O submission after the current reference is
acquired and unaccounted once the buffer is returned to the LRU or
freed. Update xfs_wait_buftarg() to wait on all in-flight I/O before
walking the LRU list. Once in-flight I/O has completed and the workqueue
has drained, all new buffers should have been released onto the LRU.
Signed-off-by: Brian Foster <bfoster@redhat.com>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Signed-off-by: Dave Chinner <david@fromorbit.com>
2016-07-20 01:15:28 +00:00
|
|
|
* in-flight buffers.
|
|
|
|
*
|
|
|
|
* Buffers that are never released (e.g., superblock, iclog buffers) must set
|
|
|
|
* the XBF_NO_IOACCT flag before I/O submission. Otherwise, the buftarg count
|
|
|
|
* never reaches zero and unmount hangs indefinitely.
|
|
|
|
*/
|
|
|
|
static inline void
|
|
|
|
xfs_buf_ioacct_inc(
|
|
|
|
struct xfs_buf *bp)
|
|
|
|
{
|
2017-05-31 15:22:52 +00:00
|
|
|
if (bp->b_flags & XBF_NO_IOACCT)
|
xfs: track and serialize in-flight async buffers against unmount
Newly allocated XFS metadata buffers are added to the LRU once the hold
count is released, which typically occurs after I/O completion. There is
no other mechanism at current that tracks the existence or I/O state of
a new buffer. Further, readahead I/O tends to be submitted
asynchronously by nature, which means the I/O can remain in flight and
actually complete long after the calling context is gone. This means
that file descriptors or any other holds on the filesystem can be
released, allowing the filesystem to be unmounted while I/O is still in
flight. When I/O completion occurs, core data structures may have been
freed, causing completion to run into invalid memory accesses and likely
to panic.
This problem is reproduced on XFS via directory readahead. A filesystem
is mounted, a directory is opened/closed and the filesystem immediately
unmounted. The open/close cycle triggers a directory readahead that if
delayed long enough, runs buffer I/O completion after the unmount has
completed.
To address this problem, add a mechanism to track all in-flight,
asynchronous buffers using per-cpu counters in the buftarg. The buffer
is accounted on the first I/O submission after the current reference is
acquired and unaccounted once the buffer is returned to the LRU or
freed. Update xfs_wait_buftarg() to wait on all in-flight I/O before
walking the LRU list. Once in-flight I/O has completed and the workqueue
has drained, all new buffers should have been released onto the LRU.
Signed-off-by: Brian Foster <bfoster@redhat.com>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Signed-off-by: Dave Chinner <david@fromorbit.com>
2016-07-20 01:15:28 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
ASSERT(bp->b_flags & XBF_ASYNC);
|
2017-05-31 15:22:52 +00:00
|
|
|
spin_lock(&bp->b_lock);
|
|
|
|
if (!(bp->b_state & XFS_BSTATE_IN_FLIGHT)) {
|
|
|
|
bp->b_state |= XFS_BSTATE_IN_FLIGHT;
|
|
|
|
percpu_counter_inc(&bp->b_target->bt_io_count);
|
|
|
|
}
|
|
|
|
spin_unlock(&bp->b_lock);
|
xfs: track and serialize in-flight async buffers against unmount
Newly allocated XFS metadata buffers are added to the LRU once the hold
count is released, which typically occurs after I/O completion. There is
no other mechanism at current that tracks the existence or I/O state of
a new buffer. Further, readahead I/O tends to be submitted
asynchronously by nature, which means the I/O can remain in flight and
actually complete long after the calling context is gone. This means
that file descriptors or any other holds on the filesystem can be
released, allowing the filesystem to be unmounted while I/O is still in
flight. When I/O completion occurs, core data structures may have been
freed, causing completion to run into invalid memory accesses and likely
to panic.
This problem is reproduced on XFS via directory readahead. A filesystem
is mounted, a directory is opened/closed and the filesystem immediately
unmounted. The open/close cycle triggers a directory readahead that if
delayed long enough, runs buffer I/O completion after the unmount has
completed.
To address this problem, add a mechanism to track all in-flight,
asynchronous buffers using per-cpu counters in the buftarg. The buffer
is accounted on the first I/O submission after the current reference is
acquired and unaccounted once the buffer is returned to the LRU or
freed. Update xfs_wait_buftarg() to wait on all in-flight I/O before
walking the LRU list. Once in-flight I/O has completed and the workqueue
has drained, all new buffers should have been released onto the LRU.
Signed-off-by: Brian Foster <bfoster@redhat.com>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Signed-off-by: Dave Chinner <david@fromorbit.com>
2016-07-20 01:15:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Clear the in-flight state on a buffer about to be released to the LRU or
|
|
|
|
* freed and unaccount from the buftarg.
|
|
|
|
*/
|
|
|
|
static inline void
|
2017-05-31 15:22:52 +00:00
|
|
|
__xfs_buf_ioacct_dec(
|
xfs: track and serialize in-flight async buffers against unmount
Newly allocated XFS metadata buffers are added to the LRU once the hold
count is released, which typically occurs after I/O completion. There is
no other mechanism at current that tracks the existence or I/O state of
a new buffer. Further, readahead I/O tends to be submitted
asynchronously by nature, which means the I/O can remain in flight and
actually complete long after the calling context is gone. This means
that file descriptors or any other holds on the filesystem can be
released, allowing the filesystem to be unmounted while I/O is still in
flight. When I/O completion occurs, core data structures may have been
freed, causing completion to run into invalid memory accesses and likely
to panic.
This problem is reproduced on XFS via directory readahead. A filesystem
is mounted, a directory is opened/closed and the filesystem immediately
unmounted. The open/close cycle triggers a directory readahead that if
delayed long enough, runs buffer I/O completion after the unmount has
completed.
To address this problem, add a mechanism to track all in-flight,
asynchronous buffers using per-cpu counters in the buftarg. The buffer
is accounted on the first I/O submission after the current reference is
acquired and unaccounted once the buffer is returned to the LRU or
freed. Update xfs_wait_buftarg() to wait on all in-flight I/O before
walking the LRU list. Once in-flight I/O has completed and the workqueue
has drained, all new buffers should have been released onto the LRU.
Signed-off-by: Brian Foster <bfoster@redhat.com>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Signed-off-by: Dave Chinner <david@fromorbit.com>
2016-07-20 01:15:28 +00:00
|
|
|
struct xfs_buf *bp)
|
|
|
|
{
|
2017-06-08 15:23:07 +00:00
|
|
|
lockdep_assert_held(&bp->b_lock);
|
xfs: track and serialize in-flight async buffers against unmount
Newly allocated XFS metadata buffers are added to the LRU once the hold
count is released, which typically occurs after I/O completion. There is
no other mechanism at current that tracks the existence or I/O state of
a new buffer. Further, readahead I/O tends to be submitted
asynchronously by nature, which means the I/O can remain in flight and
actually complete long after the calling context is gone. This means
that file descriptors or any other holds on the filesystem can be
released, allowing the filesystem to be unmounted while I/O is still in
flight. When I/O completion occurs, core data structures may have been
freed, causing completion to run into invalid memory accesses and likely
to panic.
This problem is reproduced on XFS via directory readahead. A filesystem
is mounted, a directory is opened/closed and the filesystem immediately
unmounted. The open/close cycle triggers a directory readahead that if
delayed long enough, runs buffer I/O completion after the unmount has
completed.
To address this problem, add a mechanism to track all in-flight,
asynchronous buffers using per-cpu counters in the buftarg. The buffer
is accounted on the first I/O submission after the current reference is
acquired and unaccounted once the buffer is returned to the LRU or
freed. Update xfs_wait_buftarg() to wait on all in-flight I/O before
walking the LRU list. Once in-flight I/O has completed and the workqueue
has drained, all new buffers should have been released onto the LRU.
Signed-off-by: Brian Foster <bfoster@redhat.com>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Signed-off-by: Dave Chinner <david@fromorbit.com>
2016-07-20 01:15:28 +00:00
|
|
|
|
2017-05-31 15:22:52 +00:00
|
|
|
if (bp->b_state & XFS_BSTATE_IN_FLIGHT) {
|
|
|
|
bp->b_state &= ~XFS_BSTATE_IN_FLIGHT;
|
|
|
|
percpu_counter_dec(&bp->b_target->bt_io_count);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void
|
|
|
|
xfs_buf_ioacct_dec(
|
|
|
|
struct xfs_buf *bp)
|
|
|
|
{
|
|
|
|
spin_lock(&bp->b_lock);
|
|
|
|
__xfs_buf_ioacct_dec(bp);
|
|
|
|
spin_unlock(&bp->b_lock);
|
xfs: track and serialize in-flight async buffers against unmount
Newly allocated XFS metadata buffers are added to the LRU once the hold
count is released, which typically occurs after I/O completion. There is
no other mechanism at current that tracks the existence or I/O state of
a new buffer. Further, readahead I/O tends to be submitted
asynchronously by nature, which means the I/O can remain in flight and
actually complete long after the calling context is gone. This means
that file descriptors or any other holds on the filesystem can be
released, allowing the filesystem to be unmounted while I/O is still in
flight. When I/O completion occurs, core data structures may have been
freed, causing completion to run into invalid memory accesses and likely
to panic.
This problem is reproduced on XFS via directory readahead. A filesystem
is mounted, a directory is opened/closed and the filesystem immediately
unmounted. The open/close cycle triggers a directory readahead that if
delayed long enough, runs buffer I/O completion after the unmount has
completed.
To address this problem, add a mechanism to track all in-flight,
asynchronous buffers using per-cpu counters in the buftarg. The buffer
is accounted on the first I/O submission after the current reference is
acquired and unaccounted once the buffer is returned to the LRU or
freed. Update xfs_wait_buftarg() to wait on all in-flight I/O before
walking the LRU list. Once in-flight I/O has completed and the workqueue
has drained, all new buffers should have been released onto the LRU.
Signed-off-by: Brian Foster <bfoster@redhat.com>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Signed-off-by: Dave Chinner <david@fromorbit.com>
2016-07-20 01:15:28 +00:00
|
|
|
}
|
|
|
|
|
2010-12-02 05:30:55 +00:00
|
|
|
/*
|
|
|
|
* When we mark a buffer stale, we remove the buffer from the LRU and clear the
|
|
|
|
* b_lru_ref count so that the buffer is freed immediately when the buffer
|
|
|
|
* reference count falls to zero. If the buffer is already on the LRU, we need
|
|
|
|
* to remove the reference that LRU holds on the buffer.
|
|
|
|
*
|
|
|
|
* This prevents build-up of stale buffers on the LRU.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
xfs_buf_stale(
|
|
|
|
struct xfs_buf *bp)
|
|
|
|
{
|
xfs: on-stack delayed write buffer lists
Queue delwri buffers on a local on-stack list instead of a per-buftarg one,
and write back the buffers per-process instead of by waking up xfsbufd.
This is now easily doable given that we have very few places left that write
delwri buffers:
- log recovery:
Only done at mount time, and already forcing out the buffers
synchronously using xfs_flush_buftarg
- quotacheck:
Same story.
- dquot reclaim:
Writes out dirty dquots on the LRU under memory pressure. We might
want to look into doing more of this via xfsaild, but it's already
more optimal than the synchronous inode reclaim that writes each
buffer synchronously.
- xfsaild:
This is the main beneficiary of the change. By keeping a local list
of buffers to write we reduce latency of writing out buffers, and
more importably we can remove all the delwri list promotions which
were hitting the buffer cache hard under sustained metadata loads.
The implementation is very straight forward - xfs_buf_delwri_queue now gets
a new list_head pointer that it adds the delwri buffers to, and all callers
need to eventually submit the list using xfs_buf_delwi_submit or
xfs_buf_delwi_submit_nowait. Buffers that already are on a delwri list are
skipped in xfs_buf_delwri_queue, assuming they already are on another delwri
list. The biggest change to pass down the buffer list was done to the AIL
pushing. Now that we operate on buffers the trylock, push and pushbuf log
item methods are merged into a single push routine, which tries to lock the
item, and if possible add the buffer that needs writeback to the buffer list.
This leads to much simpler code than the previous split but requires the
individual IOP_PUSH instances to unlock and reacquire the AIL around calls
to blocking routines.
Given that xfsailds now also handle writing out buffers, the conditions for
log forcing and the sleep times needed some small changes. The most
important one is that we consider an AIL busy as long we still have buffers
to push, and the other one is that we do increment the pushed LSN for
buffers that are under flushing at this moment, but still count them towards
the stuck items for restart purposes. Without this we could hammer on stuck
items without ever forcing the log and not make progress under heavy random
delete workloads on fast flash storage devices.
[ Dave Chinner:
- rebase on previous patches.
- improved comments for XBF_DELWRI_Q handling
- fix XBF_ASYNC handling in queue submission (test 106 failure)
- rename delwri submit function buffer list parameters for clarity
- xfs_efd_item_push() should return XFS_ITEM_PINNED ]
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Mark Tinguely <tinguely@sgi.com>
Signed-off-by: Ben Myers <bpm@sgi.com>
2012-04-23 05:58:39 +00:00
|
|
|
ASSERT(xfs_buf_islocked(bp));
|
|
|
|
|
2010-12-02 05:30:55 +00:00
|
|
|
bp->b_flags |= XBF_STALE;
|
xfs: on-stack delayed write buffer lists
Queue delwri buffers on a local on-stack list instead of a per-buftarg one,
and write back the buffers per-process instead of by waking up xfsbufd.
This is now easily doable given that we have very few places left that write
delwri buffers:
- log recovery:
Only done at mount time, and already forcing out the buffers
synchronously using xfs_flush_buftarg
- quotacheck:
Same story.
- dquot reclaim:
Writes out dirty dquots on the LRU under memory pressure. We might
want to look into doing more of this via xfsaild, but it's already
more optimal than the synchronous inode reclaim that writes each
buffer synchronously.
- xfsaild:
This is the main beneficiary of the change. By keeping a local list
of buffers to write we reduce latency of writing out buffers, and
more importably we can remove all the delwri list promotions which
were hitting the buffer cache hard under sustained metadata loads.
The implementation is very straight forward - xfs_buf_delwri_queue now gets
a new list_head pointer that it adds the delwri buffers to, and all callers
need to eventually submit the list using xfs_buf_delwi_submit or
xfs_buf_delwi_submit_nowait. Buffers that already are on a delwri list are
skipped in xfs_buf_delwri_queue, assuming they already are on another delwri
list. The biggest change to pass down the buffer list was done to the AIL
pushing. Now that we operate on buffers the trylock, push and pushbuf log
item methods are merged into a single push routine, which tries to lock the
item, and if possible add the buffer that needs writeback to the buffer list.
This leads to much simpler code than the previous split but requires the
individual IOP_PUSH instances to unlock and reacquire the AIL around calls
to blocking routines.
Given that xfsailds now also handle writing out buffers, the conditions for
log forcing and the sleep times needed some small changes. The most
important one is that we consider an AIL busy as long we still have buffers
to push, and the other one is that we do increment the pushed LSN for
buffers that are under flushing at this moment, but still count them towards
the stuck items for restart purposes. Without this we could hammer on stuck
items without ever forcing the log and not make progress under heavy random
delete workloads on fast flash storage devices.
[ Dave Chinner:
- rebase on previous patches.
- improved comments for XBF_DELWRI_Q handling
- fix XBF_ASYNC handling in queue submission (test 106 failure)
- rename delwri submit function buffer list parameters for clarity
- xfs_efd_item_push() should return XFS_ITEM_PINNED ]
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Mark Tinguely <tinguely@sgi.com>
Signed-off-by: Ben Myers <bpm@sgi.com>
2012-04-23 05:58:39 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Clear the delwri status so that a delwri queue walker will not
|
|
|
|
* flush this buffer to disk now that it is stale. The delwri queue has
|
|
|
|
* a reference to the buffer, so this is safe to do.
|
|
|
|
*/
|
|
|
|
bp->b_flags &= ~_XBF_DELWRI_Q;
|
|
|
|
|
xfs: track and serialize in-flight async buffers against unmount
Newly allocated XFS metadata buffers are added to the LRU once the hold
count is released, which typically occurs after I/O completion. There is
no other mechanism at current that tracks the existence or I/O state of
a new buffer. Further, readahead I/O tends to be submitted
asynchronously by nature, which means the I/O can remain in flight and
actually complete long after the calling context is gone. This means
that file descriptors or any other holds on the filesystem can be
released, allowing the filesystem to be unmounted while I/O is still in
flight. When I/O completion occurs, core data structures may have been
freed, causing completion to run into invalid memory accesses and likely
to panic.
This problem is reproduced on XFS via directory readahead. A filesystem
is mounted, a directory is opened/closed and the filesystem immediately
unmounted. The open/close cycle triggers a directory readahead that if
delayed long enough, runs buffer I/O completion after the unmount has
completed.
To address this problem, add a mechanism to track all in-flight,
asynchronous buffers using per-cpu counters in the buftarg. The buffer
is accounted on the first I/O submission after the current reference is
acquired and unaccounted once the buffer is returned to the LRU or
freed. Update xfs_wait_buftarg() to wait on all in-flight I/O before
walking the LRU list. Once in-flight I/O has completed and the workqueue
has drained, all new buffers should have been released onto the LRU.
Signed-off-by: Brian Foster <bfoster@redhat.com>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Signed-off-by: Dave Chinner <david@fromorbit.com>
2016-07-20 01:15:28 +00:00
|
|
|
/*
|
|
|
|
* Once the buffer is marked stale and unlocked, a subsequent lookup
|
|
|
|
* could reset b_flags. There is no guarantee that the buffer is
|
|
|
|
* unaccounted (released to LRU) before that occurs. Drop in-flight
|
|
|
|
* status now to preserve accounting consistency.
|
|
|
|
*/
|
2013-08-28 00:18:06 +00:00
|
|
|
spin_lock(&bp->b_lock);
|
2017-05-31 15:22:52 +00:00
|
|
|
__xfs_buf_ioacct_dec(bp);
|
|
|
|
|
2013-08-28 00:18:06 +00:00
|
|
|
atomic_set(&bp->b_lru_ref, 0);
|
|
|
|
if (!(bp->b_state & XFS_BSTATE_DISPOSE) &&
|
list_lru: allow explicit memcg and NUMA node selection
Patch series "workload-specific and memory pressure-driven zswap
writeback", v8.
There are currently several issues with zswap writeback:
1. There is only a single global LRU for zswap, making it impossible to
perform worload-specific shrinking - an memcg under memory pressure
cannot determine which pages in the pool it owns, and often ends up
writing pages from other memcgs. This issue has been previously
observed in practice and mitigated by simply disabling
memcg-initiated shrinking:
https://lore.kernel.org/all/20230530232435.3097106-1-nphamcs@gmail.com/T/#u
But this solution leaves a lot to be desired, as we still do not
have an avenue for an memcg to free up its own memory locked up in
the zswap pool.
2. We only shrink the zswap pool when the user-defined limit is hit.
This means that if we set the limit too high, cold data that are
unlikely to be used again will reside in the pool, wasting precious
memory. It is hard to predict how much zswap space will be needed
ahead of time, as this depends on the workload (specifically, on
factors such as memory access patterns and compressibility of the
memory pages).
This patch series solves these issues by separating the global zswap LRU
into per-memcg and per-NUMA LRUs, and performs workload-specific (i.e
memcg- and NUMA-aware) zswap writeback under memory pressure. The new
shrinker does not have any parameter that must be tuned by the user, and
can be opted in or out on a per-memcg basis.
As a proof of concept, we ran the following synthetic benchmark: build the
linux kernel in a memory-limited cgroup, and allocate some cold data in
tmpfs to see if the shrinker could write them out and improved the overall
performance. Depending on the amount of cold data generated, we observe
from 14% to 35% reduction in kernel CPU time used in the kernel builds.
This patch (of 6):
The interface of list_lru is based on the assumption that the list node
and the data it represents belong to the same allocated on the correct
node/memcg. While this assumption is valid for existing slab objects LRU
such as dentries and inodes, it is undocumented, and rather inflexible for
certain potential list_lru users (such as the upcoming zswap shrinker and
the THP shrinker). It has caused us a lot of issues during our
development.
This patch changes list_lru interface so that the caller must explicitly
specify numa node and memcg when adding and removing objects. The old
list_lru_add() and list_lru_del() are renamed to list_lru_add_obj() and
list_lru_del_obj(), respectively.
It also extends the list_lru API with a new function, list_lru_putback,
which undoes a previous list_lru_isolate call. Unlike list_lru_add, it
does not increment the LRU node count (as list_lru_isolate does not
decrement the node count). list_lru_putback also allows for explicit
memcg and NUMA node selection.
Link: https://lkml.kernel.org/r/20231130194023.4102148-1-nphamcs@gmail.com
Link: https://lkml.kernel.org/r/20231130194023.4102148-2-nphamcs@gmail.com
Signed-off-by: Nhat Pham <nphamcs@gmail.com>
Suggested-by: Johannes Weiner <hannes@cmpxchg.org>
Acked-by: Johannes Weiner <hannes@cmpxchg.org>
Tested-by: Bagas Sanjaya <bagasdotme@gmail.com>
Cc: Chris Li <chrisl@kernel.org>
Cc: Dan Streetman <ddstreet@ieee.org>
Cc: Domenico Cerasuolo <cerasuolodomenico@gmail.com>
Cc: Michal Hocko <mhocko@kernel.org>
Cc: Muchun Song <muchun.song@linux.dev>
Cc: Roman Gushchin <roman.gushchin@linux.dev>
Cc: Seth Jennings <sjenning@redhat.com>
Cc: Shakeel Butt <shakeelb@google.com>
Cc: Shuah Khan <shuah@kernel.org>
Cc: Vitaly Wool <vitaly.wool@konsulko.com>
Cc: Yosry Ahmed <yosryahmed@google.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
2023-11-30 19:40:18 +00:00
|
|
|
(list_lru_del_obj(&bp->b_target->bt_lru, &bp->b_lru)))
|
2013-08-28 00:18:05 +00:00
|
|
|
atomic_dec(&bp->b_hold);
|
|
|
|
|
2010-12-02 05:30:55 +00:00
|
|
|
ASSERT(atomic_read(&bp->b_hold) >= 1);
|
2013-08-28 00:18:06 +00:00
|
|
|
spin_unlock(&bp->b_lock);
|
2010-12-02 05:30:55 +00:00
|
|
|
}
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2012-06-22 08:50:09 +00:00
|
|
|
static int
|
|
|
|
xfs_buf_get_maps(
|
|
|
|
struct xfs_buf *bp,
|
|
|
|
int map_count)
|
|
|
|
{
|
|
|
|
ASSERT(bp->b_maps == NULL);
|
|
|
|
bp->b_map_count = map_count;
|
|
|
|
|
|
|
|
if (map_count == 1) {
|
2012-12-04 23:18:02 +00:00
|
|
|
bp->b_maps = &bp->__b_map;
|
2012-06-22 08:50:09 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2024-01-15 22:59:39 +00:00
|
|
|
bp->b_maps = kzalloc(map_count * sizeof(struct xfs_buf_map),
|
2024-01-15 22:59:46 +00:00
|
|
|
GFP_KERNEL | __GFP_NOLOCKDEP | __GFP_NOFAIL);
|
2012-06-22 08:50:09 +00:00
|
|
|
if (!bp->b_maps)
|
2014-06-25 04:58:08 +00:00
|
|
|
return -ENOMEM;
|
2012-06-22 08:50:09 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Frees b_pages if it was allocated.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
xfs_buf_free_maps(
|
|
|
|
struct xfs_buf *bp)
|
|
|
|
{
|
2012-12-04 23:18:02 +00:00
|
|
|
if (bp->b_maps != &bp->__b_map) {
|
2024-01-15 22:59:43 +00:00
|
|
|
kfree(bp->b_maps);
|
2012-06-22 08:50:09 +00:00
|
|
|
bp->b_maps = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-24 01:01:15 +00:00
|
|
|
static int
|
2012-06-22 08:50:09 +00:00
|
|
|
_xfs_buf_alloc(
|
2011-10-10 16:52:48 +00:00
|
|
|
struct xfs_buftarg *target,
|
2012-06-22 08:50:09 +00:00
|
|
|
struct xfs_buf_map *map,
|
|
|
|
int nmaps,
|
2020-01-24 01:01:15 +00:00
|
|
|
xfs_buf_flags_t flags,
|
|
|
|
struct xfs_buf **bpp)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2011-10-10 16:52:48 +00:00
|
|
|
struct xfs_buf *bp;
|
2012-06-22 08:50:09 +00:00
|
|
|
int error;
|
|
|
|
int i;
|
2011-10-10 16:52:48 +00:00
|
|
|
|
2020-01-24 01:01:15 +00:00
|
|
|
*bpp = NULL;
|
2024-01-15 22:59:46 +00:00
|
|
|
bp = kmem_cache_zalloc(xfs_buf_cache,
|
|
|
|
GFP_KERNEL | __GFP_NOLOCKDEP | __GFP_NOFAIL);
|
2011-10-10 16:52:48 +00:00
|
|
|
|
2005-04-16 22:20:36 +00:00
|
|
|
/*
|
2012-04-23 05:59:05 +00:00
|
|
|
* We don't want certain flags to appear in b_flags unless they are
|
|
|
|
* specifically set by later operations on the buffer.
|
2005-04-16 22:20:36 +00:00
|
|
|
*/
|
2012-04-23 05:59:07 +00:00
|
|
|
flags &= ~(XBF_UNMAPPED | XBF_TRYLOCK | XBF_ASYNC | XBF_READ_AHEAD);
|
2006-01-11 04:39:08 +00:00
|
|
|
|
|
|
|
atomic_set(&bp->b_hold, 1);
|
2010-12-02 05:30:55 +00:00
|
|
|
atomic_set(&bp->b_lru_ref, 1);
|
2008-08-13 06:36:11 +00:00
|
|
|
init_completion(&bp->b_iowait);
|
2010-12-02 05:30:55 +00:00
|
|
|
INIT_LIST_HEAD(&bp->b_lru);
|
2006-01-11 04:39:08 +00:00
|
|
|
INIT_LIST_HEAD(&bp->b_list);
|
2018-01-24 21:38:49 +00:00
|
|
|
INIT_LIST_HEAD(&bp->b_li_list);
|
2010-09-07 14:33:15 +00:00
|
|
|
sema_init(&bp->b_sema, 0); /* held, no waiters */
|
2013-08-28 00:18:06 +00:00
|
|
|
spin_lock_init(&bp->b_lock);
|
2006-01-11 04:39:08 +00:00
|
|
|
bp->b_target = target;
|
2019-06-29 02:27:29 +00:00
|
|
|
bp->b_mount = target->bt_mount;
|
2012-06-22 08:50:09 +00:00
|
|
|
bp->b_flags = flags;
|
2012-04-23 05:58:50 +00:00
|
|
|
|
2005-04-16 22:20:36 +00:00
|
|
|
/*
|
2012-04-23 05:58:52 +00:00
|
|
|
* Set length and io_length to the same value initially.
|
|
|
|
* I/O routines should use io_length, which will be the same in
|
2005-04-16 22:20:36 +00:00
|
|
|
* most cases but may be reset (e.g. XFS recovery).
|
|
|
|
*/
|
2012-06-22 08:50:09 +00:00
|
|
|
error = xfs_buf_get_maps(bp, nmaps);
|
|
|
|
if (error) {
|
2021-10-12 18:09:23 +00:00
|
|
|
kmem_cache_free(xfs_buf_cache, bp);
|
2020-01-24 01:01:15 +00:00
|
|
|
return error;
|
2012-06-22 08:50:09 +00:00
|
|
|
}
|
|
|
|
|
2021-08-19 01:48:54 +00:00
|
|
|
bp->b_rhash_key = map[0].bm_bn;
|
2012-06-22 08:50:09 +00:00
|
|
|
bp->b_length = 0;
|
|
|
|
for (i = 0; i < nmaps; i++) {
|
|
|
|
bp->b_maps[i].bm_bn = map[i].bm_bn;
|
|
|
|
bp->b_maps[i].bm_len = map[i].bm_len;
|
|
|
|
bp->b_length += map[i].bm_len;
|
|
|
|
}
|
|
|
|
|
2006-01-11 04:39:08 +00:00
|
|
|
atomic_set(&bp->b_pin_count, 0);
|
|
|
|
init_waitqueue_head(&bp->b_waiters);
|
|
|
|
|
2019-06-29 02:27:29 +00:00
|
|
|
XFS_STATS_INC(bp->b_mount, xb_create);
|
2009-12-14 23:14:59 +00:00
|
|
|
trace_xfs_buf_init(bp, _RET_IP_);
|
2011-10-10 16:52:48 +00:00
|
|
|
|
2020-01-24 01:01:15 +00:00
|
|
|
*bpp = bp;
|
|
|
|
return 0;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
2021-06-01 03:40:36 +00:00
|
|
|
static void
|
|
|
|
xfs_buf_free_pages(
|
2020-12-17 00:07:34 +00:00
|
|
|
struct xfs_buf *bp)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2021-06-01 03:40:36 +00:00
|
|
|
uint i;
|
|
|
|
|
|
|
|
ASSERT(bp->b_flags & _XBF_PAGES);
|
|
|
|
|
|
|
|
if (xfs_buf_is_vmapped(bp))
|
2021-06-07 01:49:50 +00:00
|
|
|
vm_unmap_ram(bp->b_addr, bp->b_page_count);
|
2021-06-01 03:40:36 +00:00
|
|
|
|
|
|
|
for (i = 0; i < bp->b_page_count; i++) {
|
|
|
|
if (bp->b_pages[i])
|
|
|
|
__free_page(bp->b_pages[i]);
|
|
|
|
}
|
2023-04-13 10:40:34 +00:00
|
|
|
mm_account_reclaimed_pages(bp->b_page_count);
|
2021-06-01 03:40:36 +00:00
|
|
|
|
2021-06-01 03:40:36 +00:00
|
|
|
if (bp->b_pages != bp->b_page_array)
|
2024-01-15 22:59:43 +00:00
|
|
|
kfree(bp->b_pages);
|
2021-06-01 03:40:36 +00:00
|
|
|
bp->b_pages = NULL;
|
2021-06-01 03:40:36 +00:00
|
|
|
bp->b_flags &= ~_XBF_PAGES;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
2022-07-14 02:05:07 +00:00
|
|
|
static void
|
|
|
|
xfs_buf_free_callback(
|
|
|
|
struct callback_head *cb)
|
|
|
|
{
|
|
|
|
struct xfs_buf *bp = container_of(cb, struct xfs_buf, b_rcu);
|
|
|
|
|
|
|
|
xfs_buf_free_maps(bp);
|
|
|
|
kmem_cache_free(xfs_buf_cache, bp);
|
|
|
|
}
|
|
|
|
|
2019-10-25 05:25:37 +00:00
|
|
|
static void
|
2006-01-11 04:39:08 +00:00
|
|
|
xfs_buf_free(
|
2020-12-17 00:07:34 +00:00
|
|
|
struct xfs_buf *bp)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2009-12-14 23:14:59 +00:00
|
|
|
trace_xfs_buf_free(bp, _RET_IP_);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2010-12-02 05:30:55 +00:00
|
|
|
ASSERT(list_empty(&bp->b_lru));
|
|
|
|
|
2024-02-22 20:43:21 +00:00
|
|
|
if (xfs_buftarg_is_mem(bp->b_target))
|
|
|
|
xmbuf_unmap_page(bp);
|
|
|
|
else if (bp->b_flags & _XBF_PAGES)
|
2021-06-01 03:40:36 +00:00
|
|
|
xfs_buf_free_pages(bp);
|
|
|
|
else if (bp->b_flags & _XBF_KMEM)
|
2024-01-15 22:59:43 +00:00
|
|
|
kfree(bp->b_addr);
|
2021-06-01 03:40:36 +00:00
|
|
|
|
2022-07-14 02:05:07 +00:00
|
|
|
call_rcu(&bp->b_rcu, xfs_buf_free_callback);
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
2021-06-01 03:40:02 +00:00
|
|
|
static int
|
|
|
|
xfs_buf_alloc_kmem(
|
|
|
|
struct xfs_buf *bp,
|
|
|
|
xfs_buf_flags_t flags)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2024-01-15 22:59:46 +00:00
|
|
|
gfp_t gfp_mask = GFP_KERNEL | __GFP_NOLOCKDEP | __GFP_NOFAIL;
|
2021-06-07 01:50:48 +00:00
|
|
|
size_t size = BBTOB(bp->b_length);
|
2019-10-04 23:38:44 +00:00
|
|
|
|
2021-06-01 03:40:02 +00:00
|
|
|
/* Assure zeroed buffer for non-read cases. */
|
|
|
|
if (!(flags & XBF_READ))
|
2024-01-15 22:59:40 +00:00
|
|
|
gfp_mask |= __GFP_ZERO;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2024-01-15 22:59:40 +00:00
|
|
|
bp->b_addr = kmalloc(size, gfp_mask);
|
2021-06-01 03:40:02 +00:00
|
|
|
if (!bp->b_addr)
|
|
|
|
return -ENOMEM;
|
2011-03-25 22:16:45 +00:00
|
|
|
|
2021-06-01 03:40:02 +00:00
|
|
|
if (((unsigned long)(bp->b_addr + size - 1) & PAGE_MASK) !=
|
|
|
|
((unsigned long)bp->b_addr & PAGE_MASK)) {
|
|
|
|
/* b_addr spans two pages - use alloc_page instead */
|
2024-01-15 22:59:43 +00:00
|
|
|
kfree(bp->b_addr);
|
2021-06-01 03:40:02 +00:00
|
|
|
bp->b_addr = NULL;
|
|
|
|
return -ENOMEM;
|
2011-03-25 22:16:45 +00:00
|
|
|
}
|
2021-06-01 03:40:02 +00:00
|
|
|
bp->b_offset = offset_in_page(bp->b_addr);
|
|
|
|
bp->b_pages = bp->b_page_array;
|
|
|
|
bp->b_pages[0] = kmem_to_page(bp->b_addr);
|
|
|
|
bp->b_page_count = 1;
|
|
|
|
bp->b_flags |= _XBF_KMEM;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
xfs_buf_alloc_pages(
|
|
|
|
struct xfs_buf *bp,
|
|
|
|
xfs_buf_flags_t flags)
|
|
|
|
{
|
2024-01-15 22:59:46 +00:00
|
|
|
gfp_t gfp_mask = GFP_KERNEL | __GFP_NOLOCKDEP | __GFP_NOWARN;
|
2021-06-01 03:40:36 +00:00
|
|
|
long filled = 0;
|
2021-06-01 03:40:02 +00:00
|
|
|
|
2021-06-07 01:50:17 +00:00
|
|
|
if (flags & XBF_READ_AHEAD)
|
|
|
|
gfp_mask |= __GFP_NORETRY;
|
|
|
|
|
2021-06-01 03:40:36 +00:00
|
|
|
/* Make sure that we have a page list */
|
2021-06-07 01:50:00 +00:00
|
|
|
bp->b_page_count = DIV_ROUND_UP(BBTOB(bp->b_length), PAGE_SIZE);
|
2021-06-01 03:40:36 +00:00
|
|
|
if (bp->b_page_count <= XB_PAGES) {
|
|
|
|
bp->b_pages = bp->b_page_array;
|
|
|
|
} else {
|
|
|
|
bp->b_pages = kzalloc(sizeof(struct page *) * bp->b_page_count,
|
|
|
|
gfp_mask);
|
|
|
|
if (!bp->b_pages)
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
bp->b_flags |= _XBF_PAGES;
|
|
|
|
|
2021-06-01 03:40:02 +00:00
|
|
|
/* Assure zeroed buffer for non-read cases. */
|
|
|
|
if (!(flags & XBF_READ))
|
|
|
|
gfp_mask |= __GFP_ZERO;
|
2011-03-25 22:16:45 +00:00
|
|
|
|
2021-06-01 03:40:36 +00:00
|
|
|
/*
|
|
|
|
* Bulk filling of pages can take multiple calls. Not filling the entire
|
|
|
|
* array is not an allocation failure, so don't back off if we get at
|
|
|
|
* least one extra page.
|
|
|
|
*/
|
|
|
|
for (;;) {
|
|
|
|
long last = filled;
|
|
|
|
|
|
|
|
filled = alloc_pages_bulk_array(gfp_mask, bp->b_page_count,
|
|
|
|
bp->b_pages);
|
|
|
|
if (filled == bp->b_page_count) {
|
|
|
|
XFS_STATS_INC(bp->b_mount, xb_page_found);
|
|
|
|
break;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
2021-06-01 03:40:36 +00:00
|
|
|
if (filled != last)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (flags & XBF_READ_AHEAD) {
|
2021-06-01 03:40:36 +00:00
|
|
|
xfs_buf_free_pages(bp);
|
|
|
|
return -ENOMEM;
|
2021-06-01 03:40:36 +00:00
|
|
|
}
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2021-06-01 03:40:36 +00:00
|
|
|
XFS_STATS_INC(bp->b_mount, xb_page_retries);
|
mm: introduce memalloc_retry_wait()
Various places in the kernel - largely in filesystems - respond to a
memory allocation failure by looping around and re-trying. Some of
these cannot conveniently use __GFP_NOFAIL, for reasons such as:
- a GFP_ATOMIC allocation, which __GFP_NOFAIL doesn't work on
- a need to check for the process being signalled between failures
- the possibility that other recovery actions could be performed
- the allocation is quite deep in support code, and passing down an
extra flag to say if __GFP_NOFAIL is wanted would be clumsy.
Many of these currently use congestion_wait() which (in almost all
cases) simply waits the given timeout - congestion isn't tracked for
most devices.
It isn't clear what the best delay is for loops, but it is clear that
the various filesystems shouldn't be responsible for choosing a timeout.
This patch introduces memalloc_retry_wait() with takes on that
responsibility. Code that wants to retry a memory allocation can call
this function passing the GFP flags that were used. It will wait
however is appropriate.
For now, it only considers __GFP_NORETRY and whatever
gfpflags_allow_blocking() tests. If blocking is allowed without
__GFP_NORETRY, then alloc_page either made some reclaim progress, or
waited for a while, before failing. So there is no need for much
further waiting. memalloc_retry_wait() will wait until the current
jiffie ends. If this condition is not met, then alloc_page() won't have
waited much if at all. In that case memalloc_retry_wait() waits about
200ms. This is the delay that most current loops uses.
linux/sched/mm.h needs to be included in some files now,
but linux/backing-dev.h does not.
Link: https://lkml.kernel.org/r/163754371968.13692.1277530886009912421@noble.neil.brown.name
Signed-off-by: NeilBrown <neilb@suse.de>
Cc: Dave Chinner <david@fromorbit.com>
Cc: Michal Hocko <mhocko@suse.com>
Cc: "Theodore Ts'o" <tytso@mit.edu>
Cc: Jaegeuk Kim <jaegeuk@kernel.org>
Cc: Chao Yu <chao@kernel.org>
Cc: Darrick J. Wong <djwong@kernel.org>
Cc: Chuck Lever <chuck.lever@oracle.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2022-01-14 22:07:14 +00:00
|
|
|
memalloc_retry_wait(gfp_mask);
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
2011-03-25 22:16:45 +00:00
|
|
|
return 0;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2011-03-31 01:57:33 +00:00
|
|
|
* Map buffer into kernel address-space if necessary.
|
2005-04-16 22:20:36 +00:00
|
|
|
*/
|
|
|
|
STATIC int
|
2006-01-11 04:39:08 +00:00
|
|
|
_xfs_buf_map_pages(
|
2020-12-17 00:07:34 +00:00
|
|
|
struct xfs_buf *bp,
|
2022-04-20 22:44:59 +00:00
|
|
|
xfs_buf_flags_t flags)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2011-03-25 22:16:45 +00:00
|
|
|
ASSERT(bp->b_flags & _XBF_PAGES);
|
2006-01-11 04:39:08 +00:00
|
|
|
if (bp->b_page_count == 1) {
|
2011-03-25 22:16:45 +00:00
|
|
|
/* A single page buffer is always mappable */
|
2021-06-07 01:49:50 +00:00
|
|
|
bp->b_addr = page_address(bp->b_pages[0]);
|
2012-04-23 05:59:07 +00:00
|
|
|
} else if (flags & XBF_UNMAPPED) {
|
|
|
|
bp->b_addr = NULL;
|
|
|
|
} else {
|
2011-03-25 22:13:42 +00:00
|
|
|
int retried = 0;
|
2017-05-03 21:53:19 +00:00
|
|
|
unsigned nofs_flag;
|
2014-03-07 05:19:14 +00:00
|
|
|
|
|
|
|
/*
|
2019-11-07 21:24:52 +00:00
|
|
|
* vm_map_ram() will allocate auxiliary structures (e.g.
|
2024-01-15 22:59:46 +00:00
|
|
|
* pagetables) with GFP_KERNEL, yet we often under a scoped nofs
|
|
|
|
* context here. Mixing GFP_KERNEL with GFP_NOFS allocations
|
|
|
|
* from the same call site that can be run from both above and
|
|
|
|
* below memory reclaim causes lockdep false positives. Hence we
|
|
|
|
* always need to force this allocation to nofs context because
|
|
|
|
* we can't pass __GFP_NOLOCKDEP down to auxillary structures to
|
|
|
|
* prevent false positive lockdep reports.
|
|
|
|
*
|
|
|
|
* XXX(dgc): I think dquot reclaim is the only place we can get
|
|
|
|
* to this function from memory reclaim context now. If we fix
|
|
|
|
* that like we've fixed inode reclaim to avoid writeback from
|
|
|
|
* reclaim, this nofs wrapping can go away.
|
2014-03-07 05:19:14 +00:00
|
|
|
*/
|
2017-05-03 21:53:19 +00:00
|
|
|
nofs_flag = memalloc_nofs_save();
|
2011-03-25 22:13:42 +00:00
|
|
|
do {
|
|
|
|
bp->b_addr = vm_map_ram(bp->b_pages, bp->b_page_count,
|
2020-06-02 04:51:27 +00:00
|
|
|
-1);
|
2011-03-25 22:13:42 +00:00
|
|
|
if (bp->b_addr)
|
|
|
|
break;
|
|
|
|
vm_unmap_aliases();
|
|
|
|
} while (retried++ <= 1);
|
2017-05-03 21:53:19 +00:00
|
|
|
memalloc_nofs_restore(nofs_flag);
|
2011-03-25 22:13:42 +00:00
|
|
|
|
|
|
|
if (!bp->b_addr)
|
2005-04-16 22:20:36 +00:00
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Finding and Reading Buffers
|
|
|
|
*/
|
2016-12-07 06:36:36 +00:00
|
|
|
static int
|
|
|
|
_xfs_buf_obj_cmp(
|
|
|
|
struct rhashtable_compare_arg *arg,
|
|
|
|
const void *obj)
|
|
|
|
{
|
|
|
|
const struct xfs_buf_map *map = arg->key;
|
|
|
|
const struct xfs_buf *bp = obj;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The key hashing in the lookup path depends on the key being the
|
|
|
|
* first element of the compare_arg, make sure to assert this.
|
|
|
|
*/
|
|
|
|
BUILD_BUG_ON(offsetof(struct xfs_buf_map, bm_bn) != 0);
|
|
|
|
|
2021-08-19 01:48:54 +00:00
|
|
|
if (bp->b_rhash_key != map->bm_bn)
|
2016-12-07 06:36:36 +00:00
|
|
|
return 1;
|
|
|
|
|
|
|
|
if (unlikely(bp->b_length != map->bm_len)) {
|
|
|
|
/*
|
|
|
|
* found a block number match. If the range doesn't
|
|
|
|
* match, the only way this is allowed is if the buffer
|
|
|
|
* in the cache is stale and the transaction that made
|
|
|
|
* it stale has not yet committed. i.e. we are
|
|
|
|
* reallocating a busy extent. Skip this buffer and
|
|
|
|
* continue searching for an exact match.
|
2024-04-15 21:54:45 +00:00
|
|
|
*
|
|
|
|
* Note: If we're scanning for incore buffers to stale, don't
|
|
|
|
* complain if we find non-stale buffers.
|
2016-12-07 06:36:36 +00:00
|
|
|
*/
|
2023-08-10 14:48:03 +00:00
|
|
|
if (!(map->bm_flags & XBM_LIVESCAN))
|
|
|
|
ASSERT(bp->b_flags & XBF_STALE);
|
2016-12-07 06:36:36 +00:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct rhashtable_params xfs_buf_hash_params = {
|
|
|
|
.min_size = 32, /* empty AGs have minimal footprint */
|
|
|
|
.nelem_hint = 16,
|
|
|
|
.key_len = sizeof(xfs_daddr_t),
|
2021-08-19 01:48:54 +00:00
|
|
|
.key_offset = offsetof(struct xfs_buf, b_rhash_key),
|
2016-12-07 06:36:36 +00:00
|
|
|
.head_offset = offsetof(struct xfs_buf, b_rhash_head),
|
|
|
|
.automatic_shrinking = true,
|
|
|
|
.obj_cmpfn = _xfs_buf_obj_cmp,
|
|
|
|
};
|
|
|
|
|
|
|
|
int
|
2024-02-22 20:42:58 +00:00
|
|
|
xfs_buf_cache_init(
|
|
|
|
struct xfs_buf_cache *bch)
|
2016-12-07 06:36:36 +00:00
|
|
|
{
|
2024-02-22 20:42:58 +00:00
|
|
|
spin_lock_init(&bch->bc_lock);
|
|
|
|
return rhashtable_init(&bch->bc_hash, &xfs_buf_hash_params);
|
2016-12-07 06:36:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2024-02-22 20:42:58 +00:00
|
|
|
xfs_buf_cache_destroy(
|
|
|
|
struct xfs_buf_cache *bch)
|
2016-12-07 06:36:36 +00:00
|
|
|
{
|
2024-02-22 20:42:58 +00:00
|
|
|
rhashtable_destroy(&bch->bc_hash);
|
2016-12-07 06:36:36 +00:00
|
|
|
}
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2018-04-18 15:25:21 +00:00
|
|
|
static int
|
2022-07-14 02:02:46 +00:00
|
|
|
xfs_buf_map_verify(
|
2012-04-23 05:58:49 +00:00
|
|
|
struct xfs_buftarg *btp,
|
2022-07-14 02:02:46 +00:00
|
|
|
struct xfs_buf_map *map)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2013-01-21 12:53:52 +00:00
|
|
|
xfs_daddr_t eofs;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
|
|
|
/* Check for IOs smaller than the sector size / not sector aligned */
|
2022-07-14 02:02:46 +00:00
|
|
|
ASSERT(!(BBTOB(map->bm_len) < btp->bt_meta_sectorsize));
|
|
|
|
ASSERT(!(BBTOB(map->bm_bn) & (xfs_off_t)btp->bt_meta_sectormask));
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2013-01-21 12:53:52 +00:00
|
|
|
/*
|
|
|
|
* Corrupted block numbers can get through to here, unfortunately, so we
|
|
|
|
* have to check that the buffer falls within the filesystem bounds.
|
|
|
|
*/
|
|
|
|
eofs = XFS_FSB_TO_BB(btp->bt_mount, btp->bt_mount->m_sb.sb_dblocks);
|
2022-07-14 02:02:46 +00:00
|
|
|
if (map->bm_bn < 0 || map->bm_bn >= eofs) {
|
2013-01-21 12:53:52 +00:00
|
|
|
xfs_alert(btp->bt_mount,
|
2018-01-08 19:39:18 +00:00
|
|
|
"%s: daddr 0x%llx out of range, EOFS 0x%llx",
|
2022-07-14 02:02:46 +00:00
|
|
|
__func__, map->bm_bn, eofs);
|
xfs: rework remote attr CRCs
Note: this changes the on-disk remote attribute format. I assert
that this is OK to do as CRCs are marked experimental and the first
kernel it is included in has not yet reached release yet. Further,
the userspace utilities are still evolving and so anyone using this
stuff right now is a developer or tester using volatile filesystems
for testing this feature. Hence changing the format right now to
save longer term pain is the right thing to do.
The fundamental change is to move from a header per extent in the
attribute to a header per filesytem block in the attribute. This
means there are more header blocks and the parsing of the attribute
data is slightly more complex, but it has the advantage that we
always know the size of the attribute on disk based on the length of
the data it contains.
This is where the header-per-extent method has problems. We don't
know the size of the attribute on disk without first knowing how
many extents are used to hold it. And we can't tell from a
mapping lookup, either, because remote attributes can be allocated
contiguously with other attribute blocks and so there is no obvious
way of determining the actual size of the atribute on disk short of
walking and mapping buffers.
The problem with this approach is that if we map a buffer
incorrectly (e.g. we make the last buffer for the attribute data too
long), we then get buffer cache lookup failure when we map it
correctly. i.e. we get a size mismatch on lookup. This is not
necessarily fatal, but it's a cache coherency problem that can lead
to returning the wrong data to userspace or writing the wrong data
to disk. And debug kernels will assert fail if this occurs.
I found lots of niggly little problems trying to fix this issue on a
4k block size filesystem, finally getting it to pass with lots of
fixes. The thing is, 1024 byte filesystems still failed, and it was
getting really complex handling all the corner cases that were
showing up. And there were clearly more that I hadn't found yet.
It is complex, fragile code, and if we don't fix it now, it will be
complex, fragile code forever more.
Hence the simple fix is to add a header to each filesystem block.
This gives us the same relationship between the attribute data
length and the number of blocks on disk as we have without CRCs -
it's a linear mapping and doesn't require us to guess anything. It
is simple to implement, too - the remote block count calculated at
lookup time can be used by the remote attribute set/get/remove code
without modification for both CRC and non-CRC filesystems. The world
becomes sane again.
Because the copy-in and copy-out now need to iterate over each
filesystem block, I moved them into helper functions so we separate
the block mapping and buffer manupulations from the attribute data
and CRC header manipulations. The code becomes much clearer as a
result, and it is a lot easier to understand and debug. It also
appears to be much more robust - once it worked on 4k block size
filesystems, it has worked without failure on 1k block size
filesystems, too.
Signed-off-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Ben Myers <bpm@sgi.com>
Signed-off-by: Ben Myers <bpm@sgi.com>
(cherry picked from commit ad1858d77771172e08016890f0eb2faedec3ecee)
2013-05-21 08:02:08 +00:00
|
|
|
WARN_ON(1);
|
2018-04-18 15:25:21 +00:00
|
|
|
return -EFSCORRUPTED;
|
2013-01-21 12:53:52 +00:00
|
|
|
}
|
2018-04-18 15:25:21 +00:00
|
|
|
return 0;
|
2022-07-14 02:02:46 +00:00
|
|
|
}
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2022-07-14 02:02:46 +00:00
|
|
|
static int
|
|
|
|
xfs_buf_find_lock(
|
|
|
|
struct xfs_buf *bp,
|
|
|
|
xfs_buf_flags_t flags)
|
|
|
|
{
|
2022-07-14 02:04:38 +00:00
|
|
|
if (flags & XBF_TRYLOCK) {
|
|
|
|
if (!xfs_buf_trylock(bp)) {
|
2022-07-14 02:02:46 +00:00
|
|
|
XFS_STATS_INC(bp->b_mount, xb_busy_locked);
|
2018-04-18 15:25:21 +00:00
|
|
|
return -EAGAIN;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
2022-07-14 02:04:38 +00:00
|
|
|
} else {
|
2011-07-08 12:36:19 +00:00
|
|
|
xfs_buf_lock(bp);
|
2022-07-14 02:02:46 +00:00
|
|
|
XFS_STATS_INC(bp->b_mount, xb_get_locked_waited);
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
2011-03-25 22:16:45 +00:00
|
|
|
/*
|
|
|
|
* if the buffer is stale, clear all the external state associated with
|
|
|
|
* it. We need to keep flags such as how we allocated the buffer memory
|
|
|
|
* intact here.
|
|
|
|
*/
|
2006-01-11 04:39:08 +00:00
|
|
|
if (bp->b_flags & XBF_STALE) {
|
2023-08-10 14:48:03 +00:00
|
|
|
if (flags & XBF_LIVESCAN) {
|
|
|
|
xfs_buf_unlock(bp);
|
|
|
|
return -ENOENT;
|
|
|
|
}
|
2006-01-11 04:39:08 +00:00
|
|
|
ASSERT((bp->b_flags & _XBF_DELWRI_Q) == 0);
|
2012-04-23 05:59:07 +00:00
|
|
|
bp->b_flags &= _XBF_KMEM | _XBF_PAGES;
|
2012-11-14 06:54:40 +00:00
|
|
|
bp->b_ops = NULL;
|
2005-09-04 22:33:35 +00:00
|
|
|
}
|
2018-04-18 15:25:21 +00:00
|
|
|
return 0;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
2022-07-14 02:04:31 +00:00
|
|
|
static inline int
|
2022-07-14 02:02:46 +00:00
|
|
|
xfs_buf_lookup(
|
2024-02-22 20:42:58 +00:00
|
|
|
struct xfs_buf_cache *bch,
|
2022-07-14 02:04:31 +00:00
|
|
|
struct xfs_buf_map *map,
|
|
|
|
xfs_buf_flags_t flags,
|
|
|
|
struct xfs_buf **bpp)
|
2018-04-18 15:25:20 +00:00
|
|
|
{
|
2022-07-14 02:02:46 +00:00
|
|
|
struct xfs_buf *bp;
|
2018-04-18 15:25:21 +00:00
|
|
|
int error;
|
|
|
|
|
2022-07-14 02:05:07 +00:00
|
|
|
rcu_read_lock();
|
2024-02-22 20:42:58 +00:00
|
|
|
bp = rhashtable_lookup(&bch->bc_hash, map, xfs_buf_hash_params);
|
2022-07-14 02:05:07 +00:00
|
|
|
if (!bp || !atomic_inc_not_zero(&bp->b_hold)) {
|
|
|
|
rcu_read_unlock();
|
2022-07-14 02:04:31 +00:00
|
|
|
return -ENOENT;
|
|
|
|
}
|
2022-07-14 02:05:07 +00:00
|
|
|
rcu_read_unlock();
|
2022-07-14 02:02:46 +00:00
|
|
|
|
2022-07-14 02:04:31 +00:00
|
|
|
error = xfs_buf_find_lock(bp, flags);
|
|
|
|
if (error) {
|
|
|
|
xfs_buf_rele(bp);
|
|
|
|
return error;
|
2022-07-14 02:02:46 +00:00
|
|
|
}
|
|
|
|
|
2022-07-14 02:04:31 +00:00
|
|
|
trace_xfs_buf_find(bp, flags, _RET_IP_);
|
|
|
|
*bpp = bp;
|
2022-07-14 02:02:46 +00:00
|
|
|
return 0;
|
2018-04-18 15:25:20 +00:00
|
|
|
}
|
|
|
|
|
2005-04-16 22:20:36 +00:00
|
|
|
/*
|
2022-07-14 02:04:31 +00:00
|
|
|
* Insert the new_bp into the hash table. This consumes the perag reference
|
|
|
|
* taken for the lookup regardless of the result of the insert.
|
2005-04-16 22:20:36 +00:00
|
|
|
*/
|
2018-04-18 15:25:21 +00:00
|
|
|
static int
|
2022-07-14 02:04:31 +00:00
|
|
|
xfs_buf_find_insert(
|
2012-04-23 05:58:49 +00:00
|
|
|
struct xfs_buftarg *btp,
|
2024-02-22 20:42:58 +00:00
|
|
|
struct xfs_buf_cache *bch,
|
2022-07-14 02:04:31 +00:00
|
|
|
struct xfs_perag *pag,
|
|
|
|
struct xfs_buf_map *cmap,
|
2012-06-22 08:50:10 +00:00
|
|
|
struct xfs_buf_map *map,
|
|
|
|
int nmaps,
|
2020-01-24 01:01:15 +00:00
|
|
|
xfs_buf_flags_t flags,
|
|
|
|
struct xfs_buf **bpp)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2011-09-30 04:45:02 +00:00
|
|
|
struct xfs_buf *new_bp;
|
2020-12-17 00:07:34 +00:00
|
|
|
struct xfs_buf *bp;
|
2021-06-18 15:14:31 +00:00
|
|
|
int error;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2022-07-14 02:04:31 +00:00
|
|
|
error = _xfs_buf_alloc(btp, map, nmaps, flags, &new_bp);
|
2020-01-24 01:01:15 +00:00
|
|
|
if (error)
|
2022-07-14 02:04:31 +00:00
|
|
|
goto out_drop_pag;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2024-02-22 20:43:21 +00:00
|
|
|
if (xfs_buftarg_is_mem(new_bp->b_target)) {
|
|
|
|
error = xmbuf_map_page(new_bp);
|
|
|
|
} else if (BBTOB(new_bp->b_length) >= PAGE_SIZE ||
|
|
|
|
xfs_buf_alloc_kmem(new_bp, flags) < 0) {
|
|
|
|
/*
|
|
|
|
* For buffers that fit entirely within a single page, first
|
|
|
|
* attempt to allocate the memory from the heap to minimise
|
|
|
|
* memory usage. If we can't get heap memory for these small
|
|
|
|
* buffers, we fall back to using the page allocator.
|
|
|
|
*/
|
2021-06-07 01:50:48 +00:00
|
|
|
error = xfs_buf_alloc_pages(new_bp, flags);
|
|
|
|
}
|
2024-02-22 20:43:21 +00:00
|
|
|
if (error)
|
|
|
|
goto out_free_buf;
|
2012-04-23 05:58:45 +00:00
|
|
|
|
2024-02-22 20:42:58 +00:00
|
|
|
spin_lock(&bch->bc_lock);
|
|
|
|
bp = rhashtable_lookup_get_insert_fast(&bch->bc_hash,
|
2022-07-14 02:04:43 +00:00
|
|
|
&new_bp->b_rhash_head, xfs_buf_hash_params);
|
|
|
|
if (IS_ERR(bp)) {
|
|
|
|
error = PTR_ERR(bp);
|
2024-02-22 20:42:58 +00:00
|
|
|
spin_unlock(&bch->bc_lock);
|
2021-06-07 01:50:47 +00:00
|
|
|
goto out_free_buf;
|
2022-07-14 02:04:43 +00:00
|
|
|
}
|
2022-07-14 02:04:31 +00:00
|
|
|
if (bp) {
|
2022-07-14 02:04:43 +00:00
|
|
|
/* found an existing buffer */
|
2022-07-14 02:04:31 +00:00
|
|
|
atomic_inc(&bp->b_hold);
|
2024-02-22 20:42:58 +00:00
|
|
|
spin_unlock(&bch->bc_lock);
|
2022-07-14 02:04:31 +00:00
|
|
|
error = xfs_buf_find_lock(bp, flags);
|
|
|
|
if (error)
|
|
|
|
xfs_buf_rele(bp);
|
|
|
|
else
|
|
|
|
*bpp = bp;
|
|
|
|
goto out_free_buf;
|
|
|
|
}
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2022-07-14 02:04:43 +00:00
|
|
|
/* The new buffer keeps the perag reference until it is freed. */
|
2022-07-14 02:04:31 +00:00
|
|
|
new_bp->b_pag = pag;
|
2024-02-22 20:42:58 +00:00
|
|
|
spin_unlock(&bch->bc_lock);
|
2022-07-14 02:04:31 +00:00
|
|
|
*bpp = new_bp;
|
2018-04-18 15:25:21 +00:00
|
|
|
return 0;
|
2011-09-30 04:45:02 +00:00
|
|
|
|
2022-07-14 02:04:31 +00:00
|
|
|
out_free_buf:
|
|
|
|
xfs_buf_free(new_bp);
|
|
|
|
out_drop_pag:
|
2024-02-22 20:42:58 +00:00
|
|
|
if (pag)
|
|
|
|
xfs_perag_put(pag);
|
2022-07-14 02:04:31 +00:00
|
|
|
return error;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
2024-02-22 20:42:58 +00:00
|
|
|
static inline struct xfs_perag *
|
|
|
|
xfs_buftarg_get_pag(
|
|
|
|
struct xfs_buftarg *btp,
|
|
|
|
const struct xfs_buf_map *map)
|
|
|
|
{
|
|
|
|
struct xfs_mount *mp = btp->bt_mount;
|
|
|
|
|
2024-02-22 20:43:21 +00:00
|
|
|
if (xfs_buftarg_is_mem(btp))
|
|
|
|
return NULL;
|
2024-02-22 20:42:58 +00:00
|
|
|
return xfs_perag_get(mp, xfs_daddr_to_agno(mp, map->bm_bn));
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline struct xfs_buf_cache *
|
|
|
|
xfs_buftarg_buf_cache(
|
|
|
|
struct xfs_buftarg *btp,
|
|
|
|
struct xfs_perag *pag)
|
|
|
|
{
|
2024-02-22 20:43:21 +00:00
|
|
|
if (pag)
|
|
|
|
return &pag->pag_bcache;
|
|
|
|
return btp->bt_cache;
|
2024-02-22 20:42:58 +00:00
|
|
|
}
|
|
|
|
|
2005-04-16 22:20:36 +00:00
|
|
|
/*
|
2011-09-30 04:45:02 +00:00
|
|
|
* Assembles a buffer covering the specified range. The code is optimised for
|
|
|
|
* cache hits, as metadata intensive workloads will see 3 orders of magnitude
|
|
|
|
* more hits than misses.
|
2005-04-16 22:20:36 +00:00
|
|
|
*/
|
2020-01-24 01:01:15 +00:00
|
|
|
int
|
2012-06-22 08:50:10 +00:00
|
|
|
xfs_buf_get_map(
|
2022-07-14 02:04:31 +00:00
|
|
|
struct xfs_buftarg *btp,
|
2012-06-22 08:50:10 +00:00
|
|
|
struct xfs_buf_map *map,
|
|
|
|
int nmaps,
|
2020-01-24 01:01:15 +00:00
|
|
|
xfs_buf_flags_t flags,
|
|
|
|
struct xfs_buf **bpp)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2024-02-22 20:42:58 +00:00
|
|
|
struct xfs_buf_cache *bch;
|
2022-07-14 02:04:31 +00:00
|
|
|
struct xfs_perag *pag;
|
|
|
|
struct xfs_buf *bp = NULL;
|
|
|
|
struct xfs_buf_map cmap = { .bm_bn = map[0].bm_bn };
|
2021-06-18 15:14:31 +00:00
|
|
|
int error;
|
2022-07-14 02:04:31 +00:00
|
|
|
int i;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2023-08-10 14:48:03 +00:00
|
|
|
if (flags & XBF_LIVESCAN)
|
|
|
|
cmap.bm_flags |= XBM_LIVESCAN;
|
2022-07-14 02:04:31 +00:00
|
|
|
for (i = 0; i < nmaps; i++)
|
|
|
|
cmap.bm_len += map[i].bm_len;
|
2011-09-30 04:45:02 +00:00
|
|
|
|
2022-07-14 02:04:31 +00:00
|
|
|
error = xfs_buf_map_verify(btp, &cmap);
|
2020-01-24 01:01:15 +00:00
|
|
|
if (error)
|
2020-01-24 01:01:15 +00:00
|
|
|
return error;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2024-02-22 20:42:58 +00:00
|
|
|
pag = xfs_buftarg_get_pag(btp, &cmap);
|
|
|
|
bch = xfs_buftarg_buf_cache(btp, pag);
|
2012-04-23 05:58:45 +00:00
|
|
|
|
2024-02-22 20:42:58 +00:00
|
|
|
error = xfs_buf_lookup(bch, &cmap, flags, &bp);
|
2022-07-14 02:04:31 +00:00
|
|
|
if (error && error != -ENOENT)
|
|
|
|
goto out_put_perag;
|
|
|
|
|
|
|
|
/* cache hits always outnumber misses by at least 10:1 */
|
|
|
|
if (unlikely(!bp)) {
|
|
|
|
XFS_STATS_INC(btp->bt_mount, xb_miss_locked);
|
2011-09-30 04:45:02 +00:00
|
|
|
|
2022-07-14 02:04:31 +00:00
|
|
|
if (flags & XBF_INCORE)
|
|
|
|
goto out_put_perag;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2022-07-14 02:04:31 +00:00
|
|
|
/* xfs_buf_find_insert() consumes the perag reference. */
|
2024-02-22 20:42:58 +00:00
|
|
|
error = xfs_buf_find_insert(btp, bch, pag, &cmap, map, nmaps,
|
2022-07-14 02:04:31 +00:00
|
|
|
flags, &bp);
|
|
|
|
if (error)
|
|
|
|
return error;
|
|
|
|
} else {
|
|
|
|
XFS_STATS_INC(btp->bt_mount, xb_get_locked);
|
2024-02-22 20:42:58 +00:00
|
|
|
if (pag)
|
|
|
|
xfs_perag_put(pag);
|
2022-07-14 02:04:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* We do not hold a perag reference anymore. */
|
2012-04-23 05:59:07 +00:00
|
|
|
if (!bp->b_addr) {
|
2006-01-11 04:39:08 +00:00
|
|
|
error = _xfs_buf_map_pages(bp, flags);
|
2005-04-16 22:20:36 +00:00
|
|
|
if (unlikely(error)) {
|
2022-07-14 02:04:31 +00:00
|
|
|
xfs_warn_ratelimited(btp->bt_mount,
|
2020-02-21 15:40:44 +00:00
|
|
|
"%s: failed to map %u pages", __func__,
|
|
|
|
bp->b_page_count);
|
2012-04-23 05:58:54 +00:00
|
|
|
xfs_buf_relse(bp);
|
2020-01-24 01:01:15 +00:00
|
|
|
return error;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-01-11 20:03:44 +00:00
|
|
|
/*
|
|
|
|
* Clear b_error if this is a lookup from a caller that doesn't expect
|
|
|
|
* valid data to be found in the buffer.
|
|
|
|
*/
|
|
|
|
if (!(flags & XBF_READ))
|
|
|
|
xfs_buf_ioerror(bp, 0);
|
|
|
|
|
2022-07-14 02:04:31 +00:00
|
|
|
XFS_STATS_INC(btp->bt_mount, xb_get);
|
2009-12-14 23:14:59 +00:00
|
|
|
trace_xfs_buf_get(bp, flags, _RET_IP_);
|
2020-01-24 01:01:15 +00:00
|
|
|
*bpp = bp;
|
|
|
|
return 0;
|
2022-07-14 02:04:31 +00:00
|
|
|
|
|
|
|
out_put_perag:
|
2024-02-22 20:42:58 +00:00
|
|
|
if (pag)
|
|
|
|
xfs_perag_put(pag);
|
2021-06-07 01:50:47 +00:00
|
|
|
return error;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
2020-09-01 17:55:47 +00:00
|
|
|
int
|
2008-12-03 11:20:26 +00:00
|
|
|
_xfs_buf_read(
|
2020-12-17 00:07:34 +00:00
|
|
|
struct xfs_buf *bp,
|
2008-12-03 11:20:26 +00:00
|
|
|
xfs_buf_flags_t flags)
|
|
|
|
{
|
xfs: on-stack delayed write buffer lists
Queue delwri buffers on a local on-stack list instead of a per-buftarg one,
and write back the buffers per-process instead of by waking up xfsbufd.
This is now easily doable given that we have very few places left that write
delwri buffers:
- log recovery:
Only done at mount time, and already forcing out the buffers
synchronously using xfs_flush_buftarg
- quotacheck:
Same story.
- dquot reclaim:
Writes out dirty dquots on the LRU under memory pressure. We might
want to look into doing more of this via xfsaild, but it's already
more optimal than the synchronous inode reclaim that writes each
buffer synchronously.
- xfsaild:
This is the main beneficiary of the change. By keeping a local list
of buffers to write we reduce latency of writing out buffers, and
more importably we can remove all the delwri list promotions which
were hitting the buffer cache hard under sustained metadata loads.
The implementation is very straight forward - xfs_buf_delwri_queue now gets
a new list_head pointer that it adds the delwri buffers to, and all callers
need to eventually submit the list using xfs_buf_delwi_submit or
xfs_buf_delwi_submit_nowait. Buffers that already are on a delwri list are
skipped in xfs_buf_delwri_queue, assuming they already are on another delwri
list. The biggest change to pass down the buffer list was done to the AIL
pushing. Now that we operate on buffers the trylock, push and pushbuf log
item methods are merged into a single push routine, which tries to lock the
item, and if possible add the buffer that needs writeback to the buffer list.
This leads to much simpler code than the previous split but requires the
individual IOP_PUSH instances to unlock and reacquire the AIL around calls
to blocking routines.
Given that xfsailds now also handle writing out buffers, the conditions for
log forcing and the sleep times needed some small changes. The most
important one is that we consider an AIL busy as long we still have buffers
to push, and the other one is that we do increment the pushed LSN for
buffers that are under flushing at this moment, but still count them towards
the stuck items for restart purposes. Without this we could hammer on stuck
items without ever forcing the log and not make progress under heavy random
delete workloads on fast flash storage devices.
[ Dave Chinner:
- rebase on previous patches.
- improved comments for XBF_DELWRI_Q handling
- fix XBF_ASYNC handling in queue submission (test 106 failure)
- rename delwri submit function buffer list parameters for clarity
- xfs_efd_item_push() should return XFS_ITEM_PINNED ]
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Mark Tinguely <tinguely@sgi.com>
Signed-off-by: Ben Myers <bpm@sgi.com>
2012-04-23 05:58:39 +00:00
|
|
|
ASSERT(!(flags & XBF_WRITE));
|
2012-12-04 23:18:02 +00:00
|
|
|
ASSERT(bp->b_maps[0].bm_bn != XFS_BUF_DADDR_NULL);
|
2008-12-03 11:20:26 +00:00
|
|
|
|
2020-09-01 17:55:47 +00:00
|
|
|
bp->b_flags &= ~(XBF_WRITE | XBF_ASYNC | XBF_READ_AHEAD | XBF_DONE);
|
2011-07-08 12:36:32 +00:00
|
|
|
bp->b_flags |= flags & (XBF_READ | XBF_ASYNC | XBF_READ_AHEAD);
|
2008-12-03 11:20:26 +00:00
|
|
|
|
2018-07-12 05:26:35 +00:00
|
|
|
return xfs_buf_submit(bp);
|
2008-12-03 11:20:26 +00:00
|
|
|
}
|
|
|
|
|
2018-10-18 06:20:30 +00:00
|
|
|
/*
|
2019-02-06 17:25:29 +00:00
|
|
|
* Reverify a buffer found in cache without an attached ->b_ops.
|
2019-02-03 22:03:59 +00:00
|
|
|
*
|
2019-02-06 17:25:29 +00:00
|
|
|
* If the caller passed an ops structure and the buffer doesn't have ops
|
|
|
|
* assigned, set the ops and use it to verify the contents. If verification
|
|
|
|
* fails, clear XBF_DONE. We assume the buffer has no recorded errors and is
|
|
|
|
* already in XBF_DONE state on entry.
|
2019-02-03 22:03:59 +00:00
|
|
|
*
|
2019-02-06 17:25:29 +00:00
|
|
|
* Under normal operations, every in-core buffer is verified on read I/O
|
|
|
|
* completion. There are two scenarios that can lead to in-core buffers without
|
|
|
|
* an assigned ->b_ops. The first is during log recovery of buffers on a V4
|
|
|
|
* filesystem, though these buffers are purged at the end of recovery. The
|
|
|
|
* other is online repair, which intentionally reads with a NULL buffer ops to
|
|
|
|
* run several verifiers across an in-core buffer in order to establish buffer
|
|
|
|
* type. If repair can't establish that, the buffer will be left in memory
|
|
|
|
* with NULL buffer ops.
|
2018-10-18 06:20:30 +00:00
|
|
|
*/
|
|
|
|
int
|
2019-02-06 17:25:29 +00:00
|
|
|
xfs_buf_reverify(
|
2018-10-18 06:20:30 +00:00
|
|
|
struct xfs_buf *bp,
|
|
|
|
const struct xfs_buf_ops *ops)
|
|
|
|
{
|
|
|
|
ASSERT(bp->b_flags & XBF_DONE);
|
|
|
|
ASSERT(bp->b_error == 0);
|
|
|
|
|
|
|
|
if (!ops || bp->b_ops)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
bp->b_ops = ops;
|
|
|
|
bp->b_ops->verify_read(bp);
|
|
|
|
if (bp->b_error)
|
|
|
|
bp->b_flags &= ~XBF_DONE;
|
|
|
|
return bp->b_error;
|
|
|
|
}
|
|
|
|
|
2020-01-24 01:01:16 +00:00
|
|
|
int
|
2012-06-22 08:50:10 +00:00
|
|
|
xfs_buf_read_map(
|
|
|
|
struct xfs_buftarg *target,
|
|
|
|
struct xfs_buf_map *map,
|
|
|
|
int nmaps,
|
2012-11-12 11:54:01 +00:00
|
|
|
xfs_buf_flags_t flags,
|
2020-01-24 01:01:16 +00:00
|
|
|
struct xfs_buf **bpp,
|
2020-01-24 01:01:20 +00:00
|
|
|
const struct xfs_buf_ops *ops,
|
|
|
|
xfs_failaddr_t fa)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2012-06-22 08:50:10 +00:00
|
|
|
struct xfs_buf *bp;
|
2020-01-24 01:01:15 +00:00
|
|
|
int error;
|
2006-01-11 04:39:08 +00:00
|
|
|
|
|
|
|
flags |= XBF_READ;
|
2020-01-24 01:01:16 +00:00
|
|
|
*bpp = NULL;
|
2006-01-11 04:39:08 +00:00
|
|
|
|
2020-01-24 01:01:15 +00:00
|
|
|
error = xfs_buf_get_map(target, map, nmaps, flags, &bp);
|
|
|
|
if (error)
|
2020-01-24 01:01:16 +00:00
|
|
|
return error;
|
2009-12-14 23:14:59 +00:00
|
|
|
|
2018-10-18 06:20:30 +00:00
|
|
|
trace_xfs_buf_read(bp, flags, _RET_IP_);
|
|
|
|
|
|
|
|
if (!(bp->b_flags & XBF_DONE)) {
|
2020-01-24 01:01:16 +00:00
|
|
|
/* Initiate the buffer read and wait. */
|
2018-10-18 06:20:30 +00:00
|
|
|
XFS_STATS_INC(target->bt_mount, xb_get_read);
|
|
|
|
bp->b_ops = ops;
|
2020-01-24 01:01:16 +00:00
|
|
|
error = _xfs_buf_read(bp, flags);
|
|
|
|
|
|
|
|
/* Readahead iodone already dropped the buffer, so exit. */
|
|
|
|
if (flags & XBF_ASYNC)
|
|
|
|
return 0;
|
|
|
|
} else {
|
|
|
|
/* Buffer already read; all we need to do is check it. */
|
|
|
|
error = xfs_buf_reverify(bp, ops);
|
|
|
|
|
|
|
|
/* Readahead already finished; drop the buffer and exit. */
|
|
|
|
if (flags & XBF_ASYNC) {
|
|
|
|
xfs_buf_relse(bp);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* We do not want read in the flags */
|
|
|
|
bp->b_flags &= ~XBF_READ;
|
|
|
|
ASSERT(bp->b_ops != NULL || ops == NULL);
|
2018-10-18 06:20:30 +00:00
|
|
|
}
|
|
|
|
|
2020-01-24 01:01:16 +00:00
|
|
|
/*
|
|
|
|
* If we've had a read error, then the contents of the buffer are
|
|
|
|
* invalid and should not be used. To ensure that a followup read tries
|
|
|
|
* to pull the buffer from disk again, we clear the XBF_DONE flag and
|
|
|
|
* mark the buffer stale. This ensures that anyone who has a current
|
|
|
|
* reference to the buffer will interpret it's contents correctly and
|
|
|
|
* future cache lookups will also treat it as an empty, uninitialised
|
|
|
|
* buffer.
|
|
|
|
*/
|
|
|
|
if (error) {
|
xfs: xfs_is_shutdown vs xlog_is_shutdown cage fight
I've been chasing a recent resurgence in generic/388 recovery
failure and/or corruption events. The events have largely been
uninitialised inode chunks being tripped over in log recovery
such as:
XFS (pmem1): User initiated shutdown received.
pmem1: writeback error on inode 12621949, offset 1019904, sector 12968096
XFS (pmem1): Log I/O Error (0x6) detected at xfs_fs_goingdown+0xa3/0xf0 (fs/xfs/xfs_fsops.c:500). Shutting down filesystem.
XFS (pmem1): Please unmount the filesystem and rectify the problem(s)
XFS (pmem1): Unmounting Filesystem
XFS (pmem1): Mounting V5 Filesystem
XFS (pmem1): Starting recovery (logdev: internal)
XFS (pmem1): bad inode magic/vsn daddr 8723584 #0 (magic=1818)
XFS (pmem1): Metadata corruption detected at xfs_inode_buf_verify+0x180/0x190, xfs_inode block 0x851c80 xfs_inode_buf_verify
XFS (pmem1): Unmount and run xfs_repair
XFS (pmem1): First 128 bytes of corrupted metadata buffer:
00000000: 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 ................
00000010: 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 ................
00000020: 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 ................
00000030: 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 ................
00000040: 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 ................
00000050: 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 ................
00000060: 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 ................
00000070: 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 ................
XFS (pmem1): metadata I/O error in "xlog_recover_items_pass2+0x52/0xc0" at daddr 0x851c80 len 32 error 117
XFS (pmem1): log mount/recovery failed: error -117
XFS (pmem1): log mount failed
There have been isolated random other issues, too - xfs_repair fails
because it finds some corruption in symlink blocks, rmap
inconsistencies, etc - but they are nowhere near as common as the
uninitialised inode chunk failure.
The problem has clearly happened at runtime before recovery has run;
I can see the ICREATE log item in the log shortly before the
actively recovered range of the log. This means the ICREATE was
definitely created and written to the log, but for some reason the
tail of the log has been moved past the ordered buffer log item that
tracks INODE_ALLOC buffers and, supposedly, prevents the tail of the
log moving past the ICREATE log item before the inode chunk buffer
is written to disk.
Tracing the fsstress processes that are running when the filesystem
shut down immediately pin-pointed the problem:
user shutdown marks xfs_mount as shutdown
godown-213341 [008] 6398.022871: console: [ 6397.915392] XFS (pmem1): User initiated shutdown received.
.....
aild tries to push ordered inode cluster buffer
xfsaild/pmem1-213314 [001] 6398.022974: xfs_buf_trylock: dev 259:1 daddr 0x851c80 bbcount 0x20 hold 16 pincount 0 lock 0 flags DONE|INODES|PAGES caller xfs_inode_item_push+0x8e
xfsaild/pmem1-213314 [001] 6398.022976: xfs_ilock_nowait: dev 259:1 ino 0x851c80 flags ILOCK_SHARED caller xfs_iflush_cluster+0xae
xfs_iflush_cluster() checks xfs_is_shutdown(), returns true,
calls xfs_iflush_abort() to kill writeback of the inode.
Inode is removed from AIL, drops cluster buffer reference.
xfsaild/pmem1-213314 [001] 6398.022977: xfs_ail_delete: dev 259:1 lip 0xffff88880247ed80 old lsn 7/20344 new lsn 7/21000 type XFS_LI_INODE flags IN_AIL
xfsaild/pmem1-213314 [001] 6398.022978: xfs_buf_rele: dev 259:1 daddr 0x851c80 bbcount 0x20 hold 17 pincount 0 lock 0 flags DONE|INODES|PAGES caller xfs_iflush_abort+0xd7
.....
All inodes on cluster buffer are aborted, then the cluster buffer
itself is aborted and removed from the AIL *without writeback*:
xfsaild/pmem1-213314 [001] 6398.023011: xfs_buf_error_relse: dev 259:1 daddr 0x851c80 bbcount 0x20 hold 2 pincount 0 lock 0 flags ASYNC|DONE|STALE|INODES|PAGES caller xfs_buf_ioend_fail+0x33
xfsaild/pmem1-213314 [001] 6398.023012: xfs_ail_delete: dev 259:1 lip 0xffff8888053efde8 old lsn 7/20344 new lsn 7/20344 type XFS_LI_BUF flags IN_AIL
The inode buffer was at 7/20344 when it was removed from the AIL.
xfsaild/pmem1-213314 [001] 6398.023012: xfs_buf_item_relse: dev 259:1 daddr 0x851c80 bbcount 0x20 hold 2 pincount 0 lock 0 flags ASYNC|DONE|STALE|INODES|PAGES caller xfs_buf_item_done+0x31
xfsaild/pmem1-213314 [001] 6398.023012: xfs_buf_rele: dev 259:1 daddr 0x851c80 bbcount 0x20 hold 2 pincount 0 lock 0 flags ASYNC|DONE|STALE|INODES|PAGES caller xfs_buf_item_relse+0x39
.....
Userspace is still running, doing stuff. an fsstress process runs
syncfs() or sync() and we end up in sync_fs_one_sb() which issues
a log force. This pushes on the CIL:
fsstress-213322 [001] 6398.024430: xfs_fs_sync_fs: dev 259:1 m_features 0x20000000019ff6e9 opstate (clean|shutdown|inodegc|blockgc) s_flags 0x70810000 caller sync_fs_one_sb+0x26
fsstress-213322 [001] 6398.024430: xfs_log_force: dev 259:1 lsn 0x0 caller xfs_fs_sync_fs+0x82
fsstress-213322 [001] 6398.024430: xfs_log_force: dev 259:1 lsn 0x5f caller xfs_log_force+0x7c
<...>-194402 [001] 6398.024467: kmem_alloc: size 176 flags 0x14 caller xlog_cil_push_work+0x9f
And the CIL fills up iclogs with pending changes. This picks up
the current tail from the AIL:
<...>-194402 [001] 6398.024497: xlog_iclog_get_space: dev 259:1 state XLOG_STATE_ACTIVE refcnt 1 offset 0 lsn 0x0 flags caller xlog_write+0x149
<...>-194402 [001] 6398.024498: xlog_iclog_switch: dev 259:1 state XLOG_STATE_ACTIVE refcnt 1 offset 0 lsn 0x700005408 flags caller xlog_state_get_iclog_space+0x37e
<...>-194402 [001] 6398.024521: xlog_iclog_release: dev 259:1 state XLOG_STATE_WANT_SYNC refcnt 1 offset 32256 lsn 0x700005408 flags caller xlog_write+0x5f9
<...>-194402 [001] 6398.024522: xfs_log_assign_tail_lsn: dev 259:1 new tail lsn 7/21000, old lsn 7/20344, last sync 7/21448
And it moves the tail of the log to 7/21000 from 7/20344. This
*moves the tail of the log beyond the ICREATE transaction* that was
at 7/20344 and pinned by the inode cluster buffer that was cancelled
above.
....
godown-213341 [008] 6398.027005: xfs_force_shutdown: dev 259:1 tag logerror flags log_io|force_umount file fs/xfs/xfs_fsops.c line_num 500
godown-213341 [008] 6398.027022: console: [ 6397.915406] pmem1: writeback error on inode 12621949, offset 1019904, sector 12968096
godown-213341 [008] 6398.030551: console: [ 6397.919546] XFS (pmem1): Log I/O Error (0x6) detected at xfs_fs_goingdown+0xa3/0xf0 (fs/
And finally the log itself is now shutdown, stopping all further
writes to the log. But this is too late to prevent the corruption
that moving the tail of the log forwards after we start cancelling
writeback causes.
The fundamental problem here is that we are using the wrong shutdown
checks for log items. We've long conflated mount shutdown with log
shutdown state, and I started separating that recently with the
atomic shutdown state changes in commit b36d4651e165 ("xfs: make
forced shutdown processing atomic"). The changes in that commit
series are directly responsible for being able to diagnose this
issue because it clearly separated mount shutdown from log shutdown.
Essentially, once we start cancelling writeback of log items and
removing them from the AIL because the filesystem is shut down, we
*cannot* update the journal because we may have cancelled the items
that pin the tail of the log. That moves the tail of the log
forwards without having written the metadata back, hence we have
corrupt in memory state and writing to the journal propagates that
to the on-disk state.
What commit b36d4651e165 makes clear is that log item state needs to
change relative to log shutdown, not mount shutdown. IOWs, anything
that aborts metadata writeback needs to check log shutdown state
because log items directly affect log consistency. Having them check
mount shutdown state introduces the above race condition where we
cancel metadata writeback before the log shuts down.
To fix this, this patch works through all log items and converts
shutdown checks to use xlog_is_shutdown() rather than
xfs_is_shutdown(), so that we don't start aborting metadata
writeback before we shut off journal writes.
AFAICT, this race condition is a zero day IO error handling bug in
XFS that dates back to the introduction of XLOG_IO_ERROR,
XLOG_STATE_IOERROR and XFS_FORCED_SHUTDOWN back in January 1997.
Signed-off-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Darrick J. Wong <djwong@kernel.org>
Signed-off-by: Darrick J. Wong <djwong@kernel.org>
2022-03-17 16:09:13 +00:00
|
|
|
/*
|
|
|
|
* Check against log shutdown for error reporting because
|
|
|
|
* metadata writeback may require a read first and we need to
|
|
|
|
* report errors in metadata writeback until the log is shut
|
|
|
|
* down. High level transaction read functions already check
|
|
|
|
* against mount shutdown, anyway, so we only need to be
|
|
|
|
* concerned about low level IO interactions here.
|
|
|
|
*/
|
|
|
|
if (!xlog_is_shutdown(target->bt_mount->m_log))
|
2020-01-24 01:01:20 +00:00
|
|
|
xfs_buf_ioerror_alert(bp, fa);
|
2018-10-18 06:20:30 +00:00
|
|
|
|
2020-01-24 01:01:16 +00:00
|
|
|
bp->b_flags &= ~XBF_DONE;
|
|
|
|
xfs_buf_stale(bp);
|
2018-10-18 06:20:30 +00:00
|
|
|
xfs_buf_relse(bp);
|
2020-01-24 01:01:16 +00:00
|
|
|
|
|
|
|
/* bad CRC means corrupted metadata */
|
|
|
|
if (error == -EFSBADCRC)
|
|
|
|
error = -EFSCORRUPTED;
|
|
|
|
return error;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
2020-01-24 01:01:16 +00:00
|
|
|
*bpp = bp;
|
|
|
|
return 0;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2006-01-11 04:39:08 +00:00
|
|
|
* If we are not low on memory then do the readahead in a deadlock
|
|
|
|
* safe manner.
|
2005-04-16 22:20:36 +00:00
|
|
|
*/
|
|
|
|
void
|
2012-06-22 08:50:10 +00:00
|
|
|
xfs_buf_readahead_map(
|
|
|
|
struct xfs_buftarg *target,
|
|
|
|
struct xfs_buf_map *map,
|
2012-11-12 11:54:01 +00:00
|
|
|
int nmaps,
|
2012-11-14 06:54:40 +00:00
|
|
|
const struct xfs_buf_ops *ops)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2020-01-24 01:01:16 +00:00
|
|
|
struct xfs_buf *bp;
|
|
|
|
|
2024-02-22 20:43:21 +00:00
|
|
|
/*
|
|
|
|
* Currently we don't have a good means or justification for performing
|
|
|
|
* xmbuf_map_page asynchronously, so we don't do readahead.
|
|
|
|
*/
|
|
|
|
if (xfs_buftarg_is_mem(target))
|
|
|
|
return;
|
|
|
|
|
2012-06-22 08:50:10 +00:00
|
|
|
xfs_buf_read_map(target, map, nmaps,
|
2020-01-24 01:01:20 +00:00
|
|
|
XBF_TRYLOCK | XBF_ASYNC | XBF_READ_AHEAD, &bp, ops,
|
|
|
|
__this_address);
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
2010-09-24 11:58:31 +00:00
|
|
|
/*
|
|
|
|
* Read an uncached buffer from disk. Allocates and returns a locked
|
2021-08-19 01:48:54 +00:00
|
|
|
* buffer containing the disk contents or nothing. Uncached buffers always have
|
|
|
|
* a cache index of XFS_BUF_DADDR_NULL so we can easily determine if the buffer
|
|
|
|
* is cached or uncached during fault diagnosis.
|
2010-09-24 11:58:31 +00:00
|
|
|
*/
|
2014-10-01 23:05:32 +00:00
|
|
|
int
|
2010-09-24 11:58:31 +00:00
|
|
|
xfs_buf_read_uncached(
|
|
|
|
struct xfs_buftarg *target,
|
|
|
|
xfs_daddr_t daddr,
|
2012-04-23 05:58:49 +00:00
|
|
|
size_t numblks,
|
2022-04-20 22:44:59 +00:00
|
|
|
xfs_buf_flags_t flags,
|
2014-10-01 23:05:32 +00:00
|
|
|
struct xfs_buf **bpp,
|
2012-11-14 06:54:40 +00:00
|
|
|
const struct xfs_buf_ops *ops)
|
2010-09-24 11:58:31 +00:00
|
|
|
{
|
2012-11-12 11:54:02 +00:00
|
|
|
struct xfs_buf *bp;
|
2020-01-24 01:01:17 +00:00
|
|
|
int error;
|
2010-09-24 11:58:31 +00:00
|
|
|
|
2014-10-01 23:05:32 +00:00
|
|
|
*bpp = NULL;
|
|
|
|
|
2020-01-24 01:01:17 +00:00
|
|
|
error = xfs_buf_get_uncached(target, numblks, flags, &bp);
|
|
|
|
if (error)
|
|
|
|
return error;
|
2010-09-24 11:58:31 +00:00
|
|
|
|
|
|
|
/* set up the buffer for a read IO */
|
2012-06-22 08:50:09 +00:00
|
|
|
ASSERT(bp->b_map_count == 1);
|
2021-08-19 01:48:54 +00:00
|
|
|
bp->b_rhash_key = XFS_BUF_DADDR_NULL;
|
2012-06-22 08:50:09 +00:00
|
|
|
bp->b_maps[0].bm_bn = daddr;
|
2012-06-22 08:50:08 +00:00
|
|
|
bp->b_flags |= XBF_READ;
|
2012-11-14 06:54:40 +00:00
|
|
|
bp->b_ops = ops;
|
2010-09-24 11:58:31 +00:00
|
|
|
|
2018-07-12 05:26:35 +00:00
|
|
|
xfs_buf_submit(bp);
|
2014-10-01 23:05:32 +00:00
|
|
|
if (bp->b_error) {
|
2020-01-24 01:01:17 +00:00
|
|
|
error = bp->b_error;
|
2013-12-17 08:03:52 +00:00
|
|
|
xfs_buf_relse(bp);
|
2014-10-01 23:05:32 +00:00
|
|
|
return error;
|
2013-12-17 08:03:52 +00:00
|
|
|
}
|
2014-10-01 23:05:32 +00:00
|
|
|
|
|
|
|
*bpp = bp;
|
|
|
|
return 0;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
2020-01-24 01:01:17 +00:00
|
|
|
int
|
2010-09-24 10:07:47 +00:00
|
|
|
xfs_buf_get_uncached(
|
|
|
|
struct xfs_buftarg *target,
|
2012-04-23 05:58:49 +00:00
|
|
|
size_t numblks,
|
2022-04-20 22:44:59 +00:00
|
|
|
xfs_buf_flags_t flags,
|
2020-01-24 01:01:17 +00:00
|
|
|
struct xfs_buf **bpp)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2021-06-01 03:40:35 +00:00
|
|
|
int error;
|
2012-06-22 08:50:09 +00:00
|
|
|
struct xfs_buf *bp;
|
|
|
|
DEFINE_SINGLE_BUF_MAP(map, XFS_BUF_DADDR_NULL, numblks);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2020-01-24 01:01:17 +00:00
|
|
|
*bpp = NULL;
|
|
|
|
|
2016-07-20 01:13:43 +00:00
|
|
|
/* flags might contain irrelevant bits, pass only what we care about */
|
2020-01-24 01:01:15 +00:00
|
|
|
error = _xfs_buf_alloc(target, &map, 1, flags & XBF_NO_IOACCT, &bp);
|
|
|
|
if (error)
|
2021-06-01 03:40:35 +00:00
|
|
|
return error;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2024-02-22 20:43:21 +00:00
|
|
|
if (xfs_buftarg_is_mem(bp->b_target))
|
|
|
|
error = xmbuf_map_page(bp);
|
|
|
|
else
|
|
|
|
error = xfs_buf_alloc_pages(bp, flags);
|
2007-05-14 08:23:50 +00:00
|
|
|
if (error)
|
2005-04-16 22:20:36 +00:00
|
|
|
goto fail_free_buf;
|
|
|
|
|
2012-04-23 05:59:07 +00:00
|
|
|
error = _xfs_buf_map_pages(bp, 0);
|
2007-05-14 08:23:50 +00:00
|
|
|
if (unlikely(error)) {
|
2011-03-06 23:00:35 +00:00
|
|
|
xfs_warn(target->bt_mount,
|
2013-10-12 01:59:05 +00:00
|
|
|
"%s: failed to map pages", __func__);
|
2021-06-01 03:40:35 +00:00
|
|
|
goto fail_free_buf;
|
2007-05-14 08:23:50 +00:00
|
|
|
}
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2010-09-24 10:07:47 +00:00
|
|
|
trace_xfs_buf_get_uncached(bp, _RET_IP_);
|
2020-01-24 01:01:17 +00:00
|
|
|
*bpp = bp;
|
|
|
|
return 0;
|
2007-05-14 08:23:50 +00:00
|
|
|
|
2021-06-01 03:40:35 +00:00
|
|
|
fail_free_buf:
|
|
|
|
xfs_buf_free(bp);
|
2020-01-24 01:01:17 +00:00
|
|
|
return error;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Increment reference count on buffer, to hold the buffer concurrently
|
|
|
|
* with another thread which may release (free) the buffer asynchronously.
|
|
|
|
* Must hold the buffer already to call this function.
|
|
|
|
*/
|
|
|
|
void
|
2006-01-11 04:39:08 +00:00
|
|
|
xfs_buf_hold(
|
2020-12-17 00:07:34 +00:00
|
|
|
struct xfs_buf *bp)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2009-12-14 23:14:59 +00:00
|
|
|
trace_xfs_buf_hold(bp, _RET_IP_);
|
2006-01-11 04:39:08 +00:00
|
|
|
atomic_inc(&bp->b_hold);
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
2024-02-22 20:41:02 +00:00
|
|
|
static void
|
|
|
|
xfs_buf_rele_uncached(
|
|
|
|
struct xfs_buf *bp)
|
|
|
|
{
|
|
|
|
ASSERT(list_empty(&bp->b_lru));
|
|
|
|
if (atomic_dec_and_test(&bp->b_hold)) {
|
|
|
|
xfs_buf_ioacct_dec(bp);
|
|
|
|
xfs_buf_free(bp);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
xfs_buf_rele_cached(
|
2020-12-17 00:07:34 +00:00
|
|
|
struct xfs_buf *bp)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2024-02-22 20:42:58 +00:00
|
|
|
struct xfs_buftarg *btp = bp->b_target;
|
2010-09-24 09:59:04 +00:00
|
|
|
struct xfs_perag *pag = bp->b_pag;
|
2024-02-22 20:42:58 +00:00
|
|
|
struct xfs_buf_cache *bch = xfs_buftarg_buf_cache(btp, pag);
|
xfs: track and serialize in-flight async buffers against unmount
Newly allocated XFS metadata buffers are added to the LRU once the hold
count is released, which typically occurs after I/O completion. There is
no other mechanism at current that tracks the existence or I/O state of
a new buffer. Further, readahead I/O tends to be submitted
asynchronously by nature, which means the I/O can remain in flight and
actually complete long after the calling context is gone. This means
that file descriptors or any other holds on the filesystem can be
released, allowing the filesystem to be unmounted while I/O is still in
flight. When I/O completion occurs, core data structures may have been
freed, causing completion to run into invalid memory accesses and likely
to panic.
This problem is reproduced on XFS via directory readahead. A filesystem
is mounted, a directory is opened/closed and the filesystem immediately
unmounted. The open/close cycle triggers a directory readahead that if
delayed long enough, runs buffer I/O completion after the unmount has
completed.
To address this problem, add a mechanism to track all in-flight,
asynchronous buffers using per-cpu counters in the buftarg. The buffer
is accounted on the first I/O submission after the current reference is
acquired and unaccounted once the buffer is returned to the LRU or
freed. Update xfs_wait_buftarg() to wait on all in-flight I/O before
walking the LRU list. Once in-flight I/O has completed and the workqueue
has drained, all new buffers should have been released onto the LRU.
Signed-off-by: Brian Foster <bfoster@redhat.com>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Signed-off-by: Dave Chinner <david@fromorbit.com>
2016-07-20 01:15:28 +00:00
|
|
|
bool release;
|
|
|
|
bool freebuf = false;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2009-12-14 23:14:59 +00:00
|
|
|
trace_xfs_buf_rele(bp, _RET_IP_);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2008-08-13 05:42:10 +00:00
|
|
|
ASSERT(atomic_read(&bp->b_hold) > 0);
|
2013-08-28 00:18:06 +00:00
|
|
|
|
2018-10-18 06:21:29 +00:00
|
|
|
/*
|
|
|
|
* We grab the b_lock here first to serialise racing xfs_buf_rele()
|
|
|
|
* calls. The pag_buf_lock being taken on the last reference only
|
|
|
|
* serialises against racing lookups in xfs_buf_find(). IOWs, the second
|
|
|
|
* to last reference we drop here is not serialised against the last
|
|
|
|
* reference until we take bp->b_lock. Hence if we don't grab b_lock
|
|
|
|
* first, the last "release" reference can win the race to the lock and
|
|
|
|
* free the buffer before the second-to-last reference is processed,
|
|
|
|
* leading to a use-after-free scenario.
|
|
|
|
*/
|
xfs: track and serialize in-flight async buffers against unmount
Newly allocated XFS metadata buffers are added to the LRU once the hold
count is released, which typically occurs after I/O completion. There is
no other mechanism at current that tracks the existence or I/O state of
a new buffer. Further, readahead I/O tends to be submitted
asynchronously by nature, which means the I/O can remain in flight and
actually complete long after the calling context is gone. This means
that file descriptors or any other holds on the filesystem can be
released, allowing the filesystem to be unmounted while I/O is still in
flight. When I/O completion occurs, core data structures may have been
freed, causing completion to run into invalid memory accesses and likely
to panic.
This problem is reproduced on XFS via directory readahead. A filesystem
is mounted, a directory is opened/closed and the filesystem immediately
unmounted. The open/close cycle triggers a directory readahead that if
delayed long enough, runs buffer I/O completion after the unmount has
completed.
To address this problem, add a mechanism to track all in-flight,
asynchronous buffers using per-cpu counters in the buftarg. The buffer
is accounted on the first I/O submission after the current reference is
acquired and unaccounted once the buffer is returned to the LRU or
freed. Update xfs_wait_buftarg() to wait on all in-flight I/O before
walking the LRU list. Once in-flight I/O has completed and the workqueue
has drained, all new buffers should have been released onto the LRU.
Signed-off-by: Brian Foster <bfoster@redhat.com>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Signed-off-by: Dave Chinner <david@fromorbit.com>
2016-07-20 01:15:28 +00:00
|
|
|
spin_lock(&bp->b_lock);
|
2024-02-22 20:42:58 +00:00
|
|
|
release = atomic_dec_and_lock(&bp->b_hold, &bch->bc_lock);
|
xfs: track and serialize in-flight async buffers against unmount
Newly allocated XFS metadata buffers are added to the LRU once the hold
count is released, which typically occurs after I/O completion. There is
no other mechanism at current that tracks the existence or I/O state of
a new buffer. Further, readahead I/O tends to be submitted
asynchronously by nature, which means the I/O can remain in flight and
actually complete long after the calling context is gone. This means
that file descriptors or any other holds on the filesystem can be
released, allowing the filesystem to be unmounted while I/O is still in
flight. When I/O completion occurs, core data structures may have been
freed, causing completion to run into invalid memory accesses and likely
to panic.
This problem is reproduced on XFS via directory readahead. A filesystem
is mounted, a directory is opened/closed and the filesystem immediately
unmounted. The open/close cycle triggers a directory readahead that if
delayed long enough, runs buffer I/O completion after the unmount has
completed.
To address this problem, add a mechanism to track all in-flight,
asynchronous buffers using per-cpu counters in the buftarg. The buffer
is accounted on the first I/O submission after the current reference is
acquired and unaccounted once the buffer is returned to the LRU or
freed. Update xfs_wait_buftarg() to wait on all in-flight I/O before
walking the LRU list. Once in-flight I/O has completed and the workqueue
has drained, all new buffers should have been released onto the LRU.
Signed-off-by: Brian Foster <bfoster@redhat.com>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Signed-off-by: Dave Chinner <david@fromorbit.com>
2016-07-20 01:15:28 +00:00
|
|
|
if (!release) {
|
|
|
|
/*
|
|
|
|
* Drop the in-flight state if the buffer is already on the LRU
|
|
|
|
* and it holds the only reference. This is racy because we
|
|
|
|
* haven't acquired the pag lock, but the use of _XBF_IN_FLIGHT
|
|
|
|
* ensures the decrement occurs only once per-buf.
|
|
|
|
*/
|
|
|
|
if ((atomic_read(&bp->b_hold) == 1) && !list_empty(&bp->b_lru))
|
2017-05-31 15:22:52 +00:00
|
|
|
__xfs_buf_ioacct_dec(bp);
|
xfs: track and serialize in-flight async buffers against unmount
Newly allocated XFS metadata buffers are added to the LRU once the hold
count is released, which typically occurs after I/O completion. There is
no other mechanism at current that tracks the existence or I/O state of
a new buffer. Further, readahead I/O tends to be submitted
asynchronously by nature, which means the I/O can remain in flight and
actually complete long after the calling context is gone. This means
that file descriptors or any other holds on the filesystem can be
released, allowing the filesystem to be unmounted while I/O is still in
flight. When I/O completion occurs, core data structures may have been
freed, causing completion to run into invalid memory accesses and likely
to panic.
This problem is reproduced on XFS via directory readahead. A filesystem
is mounted, a directory is opened/closed and the filesystem immediately
unmounted. The open/close cycle triggers a directory readahead that if
delayed long enough, runs buffer I/O completion after the unmount has
completed.
To address this problem, add a mechanism to track all in-flight,
asynchronous buffers using per-cpu counters in the buftarg. The buffer
is accounted on the first I/O submission after the current reference is
acquired and unaccounted once the buffer is returned to the LRU or
freed. Update xfs_wait_buftarg() to wait on all in-flight I/O before
walking the LRU list. Once in-flight I/O has completed and the workqueue
has drained, all new buffers should have been released onto the LRU.
Signed-off-by: Brian Foster <bfoster@redhat.com>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Signed-off-by: Dave Chinner <david@fromorbit.com>
2016-07-20 01:15:28 +00:00
|
|
|
goto out_unlock;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* the last reference has been dropped ... */
|
2017-05-31 15:22:52 +00:00
|
|
|
__xfs_buf_ioacct_dec(bp);
|
xfs: track and serialize in-flight async buffers against unmount
Newly allocated XFS metadata buffers are added to the LRU once the hold
count is released, which typically occurs after I/O completion. There is
no other mechanism at current that tracks the existence or I/O state of
a new buffer. Further, readahead I/O tends to be submitted
asynchronously by nature, which means the I/O can remain in flight and
actually complete long after the calling context is gone. This means
that file descriptors or any other holds on the filesystem can be
released, allowing the filesystem to be unmounted while I/O is still in
flight. When I/O completion occurs, core data structures may have been
freed, causing completion to run into invalid memory accesses and likely
to panic.
This problem is reproduced on XFS via directory readahead. A filesystem
is mounted, a directory is opened/closed and the filesystem immediately
unmounted. The open/close cycle triggers a directory readahead that if
delayed long enough, runs buffer I/O completion after the unmount has
completed.
To address this problem, add a mechanism to track all in-flight,
asynchronous buffers using per-cpu counters in the buftarg. The buffer
is accounted on the first I/O submission after the current reference is
acquired and unaccounted once the buffer is returned to the LRU or
freed. Update xfs_wait_buftarg() to wait on all in-flight I/O before
walking the LRU list. Once in-flight I/O has completed and the workqueue
has drained, all new buffers should have been released onto the LRU.
Signed-off-by: Brian Foster <bfoster@redhat.com>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Signed-off-by: Dave Chinner <david@fromorbit.com>
2016-07-20 01:15:28 +00:00
|
|
|
if (!(bp->b_flags & XBF_STALE) && atomic_read(&bp->b_lru_ref)) {
|
|
|
|
/*
|
|
|
|
* If the buffer is added to the LRU take a new reference to the
|
|
|
|
* buffer for the LRU and clear the (now stale) dispose list
|
|
|
|
* state flag
|
|
|
|
*/
|
2024-02-22 20:42:58 +00:00
|
|
|
if (list_lru_add_obj(&btp->bt_lru, &bp->b_lru)) {
|
xfs: track and serialize in-flight async buffers against unmount
Newly allocated XFS metadata buffers are added to the LRU once the hold
count is released, which typically occurs after I/O completion. There is
no other mechanism at current that tracks the existence or I/O state of
a new buffer. Further, readahead I/O tends to be submitted
asynchronously by nature, which means the I/O can remain in flight and
actually complete long after the calling context is gone. This means
that file descriptors or any other holds on the filesystem can be
released, allowing the filesystem to be unmounted while I/O is still in
flight. When I/O completion occurs, core data structures may have been
freed, causing completion to run into invalid memory accesses and likely
to panic.
This problem is reproduced on XFS via directory readahead. A filesystem
is mounted, a directory is opened/closed and the filesystem immediately
unmounted. The open/close cycle triggers a directory readahead that if
delayed long enough, runs buffer I/O completion after the unmount has
completed.
To address this problem, add a mechanism to track all in-flight,
asynchronous buffers using per-cpu counters in the buftarg. The buffer
is accounted on the first I/O submission after the current reference is
acquired and unaccounted once the buffer is returned to the LRU or
freed. Update xfs_wait_buftarg() to wait on all in-flight I/O before
walking the LRU list. Once in-flight I/O has completed and the workqueue
has drained, all new buffers should have been released onto the LRU.
Signed-off-by: Brian Foster <bfoster@redhat.com>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Signed-off-by: Dave Chinner <david@fromorbit.com>
2016-07-20 01:15:28 +00:00
|
|
|
bp->b_state &= ~XFS_BSTATE_DISPOSE;
|
|
|
|
atomic_inc(&bp->b_hold);
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
2024-02-22 20:42:58 +00:00
|
|
|
spin_unlock(&bch->bc_lock);
|
xfs: track and serialize in-flight async buffers against unmount
Newly allocated XFS metadata buffers are added to the LRU once the hold
count is released, which typically occurs after I/O completion. There is
no other mechanism at current that tracks the existence or I/O state of
a new buffer. Further, readahead I/O tends to be submitted
asynchronously by nature, which means the I/O can remain in flight and
actually complete long after the calling context is gone. This means
that file descriptors or any other holds on the filesystem can be
released, allowing the filesystem to be unmounted while I/O is still in
flight. When I/O completion occurs, core data structures may have been
freed, causing completion to run into invalid memory accesses and likely
to panic.
This problem is reproduced on XFS via directory readahead. A filesystem
is mounted, a directory is opened/closed and the filesystem immediately
unmounted. The open/close cycle triggers a directory readahead that if
delayed long enough, runs buffer I/O completion after the unmount has
completed.
To address this problem, add a mechanism to track all in-flight,
asynchronous buffers using per-cpu counters in the buftarg. The buffer
is accounted on the first I/O submission after the current reference is
acquired and unaccounted once the buffer is returned to the LRU or
freed. Update xfs_wait_buftarg() to wait on all in-flight I/O before
walking the LRU list. Once in-flight I/O has completed and the workqueue
has drained, all new buffers should have been released onto the LRU.
Signed-off-by: Brian Foster <bfoster@redhat.com>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Signed-off-by: Dave Chinner <david@fromorbit.com>
2016-07-20 01:15:28 +00:00
|
|
|
} else {
|
|
|
|
/*
|
|
|
|
* most of the time buffers will already be removed from the
|
|
|
|
* LRU, so optimise that case by checking for the
|
|
|
|
* XFS_BSTATE_DISPOSE flag indicating the last list the buffer
|
|
|
|
* was on was the disposal list
|
|
|
|
*/
|
|
|
|
if (!(bp->b_state & XFS_BSTATE_DISPOSE)) {
|
2024-02-22 20:42:58 +00:00
|
|
|
list_lru_del_obj(&btp->bt_lru, &bp->b_lru);
|
xfs: track and serialize in-flight async buffers against unmount
Newly allocated XFS metadata buffers are added to the LRU once the hold
count is released, which typically occurs after I/O completion. There is
no other mechanism at current that tracks the existence or I/O state of
a new buffer. Further, readahead I/O tends to be submitted
asynchronously by nature, which means the I/O can remain in flight and
actually complete long after the calling context is gone. This means
that file descriptors or any other holds on the filesystem can be
released, allowing the filesystem to be unmounted while I/O is still in
flight. When I/O completion occurs, core data structures may have been
freed, causing completion to run into invalid memory accesses and likely
to panic.
This problem is reproduced on XFS via directory readahead. A filesystem
is mounted, a directory is opened/closed and the filesystem immediately
unmounted. The open/close cycle triggers a directory readahead that if
delayed long enough, runs buffer I/O completion after the unmount has
completed.
To address this problem, add a mechanism to track all in-flight,
asynchronous buffers using per-cpu counters in the buftarg. The buffer
is accounted on the first I/O submission after the current reference is
acquired and unaccounted once the buffer is returned to the LRU or
freed. Update xfs_wait_buftarg() to wait on all in-flight I/O before
walking the LRU list. Once in-flight I/O has completed and the workqueue
has drained, all new buffers should have been released onto the LRU.
Signed-off-by: Brian Foster <bfoster@redhat.com>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Signed-off-by: Dave Chinner <david@fromorbit.com>
2016-07-20 01:15:28 +00:00
|
|
|
} else {
|
|
|
|
ASSERT(list_empty(&bp->b_lru));
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
xfs: track and serialize in-flight async buffers against unmount
Newly allocated XFS metadata buffers are added to the LRU once the hold
count is released, which typically occurs after I/O completion. There is
no other mechanism at current that tracks the existence or I/O state of
a new buffer. Further, readahead I/O tends to be submitted
asynchronously by nature, which means the I/O can remain in flight and
actually complete long after the calling context is gone. This means
that file descriptors or any other holds on the filesystem can be
released, allowing the filesystem to be unmounted while I/O is still in
flight. When I/O completion occurs, core data structures may have been
freed, causing completion to run into invalid memory accesses and likely
to panic.
This problem is reproduced on XFS via directory readahead. A filesystem
is mounted, a directory is opened/closed and the filesystem immediately
unmounted. The open/close cycle triggers a directory readahead that if
delayed long enough, runs buffer I/O completion after the unmount has
completed.
To address this problem, add a mechanism to track all in-flight,
asynchronous buffers using per-cpu counters in the buftarg. The buffer
is accounted on the first I/O submission after the current reference is
acquired and unaccounted once the buffer is returned to the LRU or
freed. Update xfs_wait_buftarg() to wait on all in-flight I/O before
walking the LRU list. Once in-flight I/O has completed and the workqueue
has drained, all new buffers should have been released onto the LRU.
Signed-off-by: Brian Foster <bfoster@redhat.com>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Signed-off-by: Dave Chinner <david@fromorbit.com>
2016-07-20 01:15:28 +00:00
|
|
|
|
|
|
|
ASSERT(!(bp->b_flags & _XBF_DELWRI_Q));
|
2024-02-22 20:42:58 +00:00
|
|
|
rhashtable_remove_fast(&bch->bc_hash, &bp->b_rhash_head,
|
|
|
|
xfs_buf_hash_params);
|
|
|
|
spin_unlock(&bch->bc_lock);
|
|
|
|
if (pag)
|
|
|
|
xfs_perag_put(pag);
|
xfs: track and serialize in-flight async buffers against unmount
Newly allocated XFS metadata buffers are added to the LRU once the hold
count is released, which typically occurs after I/O completion. There is
no other mechanism at current that tracks the existence or I/O state of
a new buffer. Further, readahead I/O tends to be submitted
asynchronously by nature, which means the I/O can remain in flight and
actually complete long after the calling context is gone. This means
that file descriptors or any other holds on the filesystem can be
released, allowing the filesystem to be unmounted while I/O is still in
flight. When I/O completion occurs, core data structures may have been
freed, causing completion to run into invalid memory accesses and likely
to panic.
This problem is reproduced on XFS via directory readahead. A filesystem
is mounted, a directory is opened/closed and the filesystem immediately
unmounted. The open/close cycle triggers a directory readahead that if
delayed long enough, runs buffer I/O completion after the unmount has
completed.
To address this problem, add a mechanism to track all in-flight,
asynchronous buffers using per-cpu counters in the buftarg. The buffer
is accounted on the first I/O submission after the current reference is
acquired and unaccounted once the buffer is returned to the LRU or
freed. Update xfs_wait_buftarg() to wait on all in-flight I/O before
walking the LRU list. Once in-flight I/O has completed and the workqueue
has drained, all new buffers should have been released onto the LRU.
Signed-off-by: Brian Foster <bfoster@redhat.com>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Signed-off-by: Dave Chinner <david@fromorbit.com>
2016-07-20 01:15:28 +00:00
|
|
|
freebuf = true;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
xfs: track and serialize in-flight async buffers against unmount
Newly allocated XFS metadata buffers are added to the LRU once the hold
count is released, which typically occurs after I/O completion. There is
no other mechanism at current that tracks the existence or I/O state of
a new buffer. Further, readahead I/O tends to be submitted
asynchronously by nature, which means the I/O can remain in flight and
actually complete long after the calling context is gone. This means
that file descriptors or any other holds on the filesystem can be
released, allowing the filesystem to be unmounted while I/O is still in
flight. When I/O completion occurs, core data structures may have been
freed, causing completion to run into invalid memory accesses and likely
to panic.
This problem is reproduced on XFS via directory readahead. A filesystem
is mounted, a directory is opened/closed and the filesystem immediately
unmounted. The open/close cycle triggers a directory readahead that if
delayed long enough, runs buffer I/O completion after the unmount has
completed.
To address this problem, add a mechanism to track all in-flight,
asynchronous buffers using per-cpu counters in the buftarg. The buffer
is accounted on the first I/O submission after the current reference is
acquired and unaccounted once the buffer is returned to the LRU or
freed. Update xfs_wait_buftarg() to wait on all in-flight I/O before
walking the LRU list. Once in-flight I/O has completed and the workqueue
has drained, all new buffers should have been released onto the LRU.
Signed-off-by: Brian Foster <bfoster@redhat.com>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Signed-off-by: Dave Chinner <david@fromorbit.com>
2016-07-20 01:15:28 +00:00
|
|
|
|
|
|
|
out_unlock:
|
|
|
|
spin_unlock(&bp->b_lock);
|
|
|
|
|
|
|
|
if (freebuf)
|
|
|
|
xfs_buf_free(bp);
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
2024-02-22 20:41:02 +00:00
|
|
|
/*
|
|
|
|
* Release a hold on the specified buffer.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
xfs_buf_rele(
|
|
|
|
struct xfs_buf *bp)
|
|
|
|
{
|
|
|
|
trace_xfs_buf_rele(bp, _RET_IP_);
|
|
|
|
if (xfs_buf_is_uncached(bp))
|
|
|
|
xfs_buf_rele_uncached(bp);
|
|
|
|
else
|
|
|
|
xfs_buf_rele_cached(bp);
|
|
|
|
}
|
2005-04-16 22:20:36 +00:00
|
|
|
|
|
|
|
/*
|
2011-03-25 22:16:45 +00:00
|
|
|
* Lock a buffer object, if it is not already locked.
|
2010-11-30 04:16:16 +00:00
|
|
|
*
|
|
|
|
* If we come across a stale, pinned, locked buffer, we know that we are
|
|
|
|
* being asked to lock a buffer that has been reallocated. Because it is
|
|
|
|
* pinned, we know that the log has not been pushed to disk and hence it
|
|
|
|
* will still be locked. Rather than continuing to have trylock attempts
|
|
|
|
* fail until someone else pushes the log, push it ourselves before
|
|
|
|
* returning. This means that the xfsaild will not get stuck trying
|
|
|
|
* to push on stale inode buffers.
|
2005-04-16 22:20:36 +00:00
|
|
|
*/
|
|
|
|
int
|
2011-07-08 12:36:19 +00:00
|
|
|
xfs_buf_trylock(
|
|
|
|
struct xfs_buf *bp)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
|
|
|
int locked;
|
|
|
|
|
2006-01-11 04:39:08 +00:00
|
|
|
locked = down_trylock(&bp->b_sema) == 0;
|
2018-08-10 20:56:25 +00:00
|
|
|
if (locked)
|
2016-06-21 01:53:28 +00:00
|
|
|
trace_xfs_buf_trylock(bp, _RET_IP_);
|
2018-08-10 20:56:25 +00:00
|
|
|
else
|
2016-06-21 01:53:28 +00:00
|
|
|
trace_xfs_buf_trylock_fail(bp, _RET_IP_);
|
2011-07-08 12:36:19 +00:00
|
|
|
return locked;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2011-03-25 22:16:45 +00:00
|
|
|
* Lock a buffer object.
|
xfs: Improve scalability of busy extent tracking
When we free a metadata extent, we record it in the per-AG busy
extent array so that it is not re-used before the freeing
transaction hits the disk. This array is fixed size, so when it
overflows we make further allocation transactions synchronous
because we cannot track more freed extents until those transactions
hit the disk and are completed. Under heavy mixed allocation and
freeing workloads with large log buffers, we can overflow this array
quite easily.
Further, the array is sparsely populated, which means that inserts
need to search for a free slot, and array searches often have to
search many more slots that are actually used to check all the
busy extents. Quite inefficient, really.
To enable this aspect of extent freeing to scale better, we need
a structure that can grow dynamically. While in other areas of
XFS we have used radix trees, the extents being freed are at random
locations on disk so are better suited to being indexed by an rbtree.
So, use a per-AG rbtree indexed by block number to track busy
extents. This incures a memory allocation when marking an extent
busy, but should not occur too often in low memory situations. This
should scale to an arbitrary number of extents so should not be a
limitation for features such as in-memory aggregation of
transactions.
However, there are still situations where we can't avoid allocating
busy extents (such as allocation from the AGFL). To minimise the
overhead of such occurences, we need to avoid doing a synchronous
log force while holding the AGF locked to ensure that the previous
transactions are safely on disk before we use the extent. We can do
this by marking the transaction doing the allocation as synchronous
rather issuing a log force.
Because of the locking involved and the ordering of transactions,
the synchronous transaction provides the same guarantees as a
synchronous log force because it ensures that all the prior
transactions are already on disk when the synchronous transaction
hits the disk. i.e. it preserves the free->allocate order of the
extent correctly in recovery.
By doing this, we avoid holding the AGF locked while log writes are
in progress, hence reducing the length of time the lock is held and
therefore we increase the rate at which we can allocate and free
from the allocation group, thereby increasing overall throughput.
The only problem with this approach is that when a metadata buffer is
marked stale (e.g. a directory block is removed), then buffer remains
pinned and locked until the log goes to disk. The issue here is that
if that stale buffer is reallocated in a subsequent transaction, the
attempt to lock that buffer in the transaction will hang waiting
the log to go to disk to unlock and unpin the buffer. Hence if
someone tries to lock a pinned, stale, locked buffer we need to
push on the log to get it unlocked ASAP. Effectively we are trading
off a guaranteed log force for a much less common trigger for log
force to occur.
Ideally we should not reallocate busy extents. That is a much more
complex fix to the problem as it involves direct intervention in the
allocation btree searches in many places. This is left to a future
set of modifications.
Finally, now that we track busy extents in allocated memory, we
don't need the descriptors in the transaction structure to point to
them. We can replace the complex busy chunk infrastructure with a
simple linked list of busy extents. This allows us to remove a large
chunk of code, making the overall change a net reduction in code
size.
Signed-off-by: Dave Chinner <david@fromorbit.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Alex Elder <aelder@sgi.com>
2010-05-21 02:07:08 +00:00
|
|
|
*
|
|
|
|
* If we come across a stale, pinned, locked buffer, we know that we
|
|
|
|
* are being asked to lock a buffer that has been reallocated. Because
|
|
|
|
* it is pinned, we know that the log has not been pushed to disk and
|
|
|
|
* hence it will still be locked. Rather than sleeping until someone
|
|
|
|
* else pushes the log, push it ourselves before trying to get the lock.
|
2005-04-16 22:20:36 +00:00
|
|
|
*/
|
2006-01-11 04:39:08 +00:00
|
|
|
void
|
|
|
|
xfs_buf_lock(
|
2011-07-08 12:36:19 +00:00
|
|
|
struct xfs_buf *bp)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2009-12-14 23:14:59 +00:00
|
|
|
trace_xfs_buf_lock(bp, _RET_IP_);
|
|
|
|
|
xfs: Improve scalability of busy extent tracking
When we free a metadata extent, we record it in the per-AG busy
extent array so that it is not re-used before the freeing
transaction hits the disk. This array is fixed size, so when it
overflows we make further allocation transactions synchronous
because we cannot track more freed extents until those transactions
hit the disk and are completed. Under heavy mixed allocation and
freeing workloads with large log buffers, we can overflow this array
quite easily.
Further, the array is sparsely populated, which means that inserts
need to search for a free slot, and array searches often have to
search many more slots that are actually used to check all the
busy extents. Quite inefficient, really.
To enable this aspect of extent freeing to scale better, we need
a structure that can grow dynamically. While in other areas of
XFS we have used radix trees, the extents being freed are at random
locations on disk so are better suited to being indexed by an rbtree.
So, use a per-AG rbtree indexed by block number to track busy
extents. This incures a memory allocation when marking an extent
busy, but should not occur too often in low memory situations. This
should scale to an arbitrary number of extents so should not be a
limitation for features such as in-memory aggregation of
transactions.
However, there are still situations where we can't avoid allocating
busy extents (such as allocation from the AGFL). To minimise the
overhead of such occurences, we need to avoid doing a synchronous
log force while holding the AGF locked to ensure that the previous
transactions are safely on disk before we use the extent. We can do
this by marking the transaction doing the allocation as synchronous
rather issuing a log force.
Because of the locking involved and the ordering of transactions,
the synchronous transaction provides the same guarantees as a
synchronous log force because it ensures that all the prior
transactions are already on disk when the synchronous transaction
hits the disk. i.e. it preserves the free->allocate order of the
extent correctly in recovery.
By doing this, we avoid holding the AGF locked while log writes are
in progress, hence reducing the length of time the lock is held and
therefore we increase the rate at which we can allocate and free
from the allocation group, thereby increasing overall throughput.
The only problem with this approach is that when a metadata buffer is
marked stale (e.g. a directory block is removed), then buffer remains
pinned and locked until the log goes to disk. The issue here is that
if that stale buffer is reallocated in a subsequent transaction, the
attempt to lock that buffer in the transaction will hang waiting
the log to go to disk to unlock and unpin the buffer. Hence if
someone tries to lock a pinned, stale, locked buffer we need to
push on the log to get it unlocked ASAP. Effectively we are trading
off a guaranteed log force for a much less common trigger for log
force to occur.
Ideally we should not reallocate busy extents. That is a much more
complex fix to the problem as it involves direct intervention in the
allocation btree searches in many places. This is left to a future
set of modifications.
Finally, now that we track busy extents in allocated memory, we
don't need the descriptors in the transaction structure to point to
them. We can replace the complex busy chunk infrastructure with a
simple linked list of busy extents. This allows us to remove a large
chunk of code, making the overall change a net reduction in code
size.
Signed-off-by: Dave Chinner <david@fromorbit.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Alex Elder <aelder@sgi.com>
2010-05-21 02:07:08 +00:00
|
|
|
if (atomic_read(&bp->b_pin_count) && (bp->b_flags & XBF_STALE))
|
2019-06-29 02:27:29 +00:00
|
|
|
xfs_log_force(bp->b_mount, 0);
|
2006-01-11 04:39:08 +00:00
|
|
|
down(&bp->b_sema);
|
2009-12-14 23:14:59 +00:00
|
|
|
|
|
|
|
trace_xfs_buf_lock_done(bp, _RET_IP_);
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2006-01-11 04:39:08 +00:00
|
|
|
xfs_buf_unlock(
|
2011-07-08 12:36:19 +00:00
|
|
|
struct xfs_buf *bp)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2017-04-21 19:40:44 +00:00
|
|
|
ASSERT(xfs_buf_islocked(bp));
|
|
|
|
|
2006-01-11 04:39:08 +00:00
|
|
|
up(&bp->b_sema);
|
2009-12-14 23:14:59 +00:00
|
|
|
trace_xfs_buf_unlock(bp, _RET_IP_);
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
2006-01-11 04:39:08 +00:00
|
|
|
STATIC void
|
|
|
|
xfs_buf_wait_unpin(
|
2020-12-17 00:07:34 +00:00
|
|
|
struct xfs_buf *bp)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
|
|
|
DECLARE_WAITQUEUE (wait, current);
|
|
|
|
|
2006-01-11 04:39:08 +00:00
|
|
|
if (atomic_read(&bp->b_pin_count) == 0)
|
2005-04-16 22:20:36 +00:00
|
|
|
return;
|
|
|
|
|
2006-01-11 04:39:08 +00:00
|
|
|
add_wait_queue(&bp->b_waiters, &wait);
|
2005-04-16 22:20:36 +00:00
|
|
|
for (;;) {
|
|
|
|
set_current_state(TASK_UNINTERRUPTIBLE);
|
2006-01-11 04:39:08 +00:00
|
|
|
if (atomic_read(&bp->b_pin_count) == 0)
|
2005-04-16 22:20:36 +00:00
|
|
|
break;
|
2011-03-10 07:52:07 +00:00
|
|
|
io_schedule();
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
2006-01-11 04:39:08 +00:00
|
|
|
remove_wait_queue(&bp->b_waiters, &wait);
|
2005-04-16 22:20:36 +00:00
|
|
|
set_current_state(TASK_RUNNING);
|
|
|
|
}
|
|
|
|
|
2020-09-01 17:55:44 +00:00
|
|
|
static void
|
|
|
|
xfs_buf_ioerror_alert_ratelimited(
|
2020-09-01 17:55:29 +00:00
|
|
|
struct xfs_buf *bp)
|
|
|
|
{
|
|
|
|
static unsigned long lasttime;
|
|
|
|
static struct xfs_buftarg *lasttarg;
|
|
|
|
|
|
|
|
if (bp->b_target != lasttarg ||
|
|
|
|
time_after(jiffies, (lasttime + 5*HZ))) {
|
|
|
|
lasttime = jiffies;
|
|
|
|
xfs_buf_ioerror_alert(bp, __this_address);
|
|
|
|
}
|
|
|
|
lasttarg = bp->b_target;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Account for this latest trip around the retry handler, and decide if
|
|
|
|
* we've failed enough times to constitute a permanent failure.
|
|
|
|
*/
|
|
|
|
static bool
|
|
|
|
xfs_buf_ioerror_permanent(
|
|
|
|
struct xfs_buf *bp,
|
|
|
|
struct xfs_error_cfg *cfg)
|
|
|
|
{
|
|
|
|
struct xfs_mount *mp = bp->b_mount;
|
|
|
|
|
|
|
|
if (cfg->max_retries != XFS_ERR_RETRY_FOREVER &&
|
|
|
|
++bp->b_retries > cfg->max_retries)
|
|
|
|
return true;
|
|
|
|
if (cfg->retry_timeout != XFS_ERR_RETRY_FOREVER &&
|
|
|
|
time_after(jiffies, cfg->retry_timeout + bp->b_first_retry_time))
|
|
|
|
return true;
|
|
|
|
|
|
|
|
/* At unmount we may treat errors differently */
|
2021-08-19 01:46:52 +00:00
|
|
|
if (xfs_is_unmounting(mp) && mp->m_fail_unmount)
|
2020-09-01 17:55:29 +00:00
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* On a sync write or shutdown we just want to stale the buffer and let the
|
|
|
|
* caller handle the error in bp->b_error appropriately.
|
|
|
|
*
|
|
|
|
* If the write was asynchronous then no one will be looking for the error. If
|
|
|
|
* this is the first failure of this type, clear the error state and write the
|
|
|
|
* buffer out again. This means we always retry an async write failure at least
|
|
|
|
* once, but we also need to set the buffer up to behave correctly now for
|
|
|
|
* repeated failures.
|
|
|
|
*
|
|
|
|
* If we get repeated async write failures, then we take action according to the
|
|
|
|
* error configuration we have been set up to use.
|
|
|
|
*
|
2020-09-01 17:55:45 +00:00
|
|
|
* Returns true if this function took care of error handling and the caller must
|
|
|
|
* not touch the buffer again. Return false if the caller should proceed with
|
|
|
|
* normal I/O completion handling.
|
2020-09-01 17:55:29 +00:00
|
|
|
*/
|
2020-09-01 17:55:45 +00:00
|
|
|
static bool
|
|
|
|
xfs_buf_ioend_handle_error(
|
2020-09-01 17:55:29 +00:00
|
|
|
struct xfs_buf *bp)
|
|
|
|
{
|
|
|
|
struct xfs_mount *mp = bp->b_mount;
|
|
|
|
struct xfs_error_cfg *cfg;
|
|
|
|
|
2020-09-01 17:55:44 +00:00
|
|
|
/*
|
xfs: xfs_is_shutdown vs xlog_is_shutdown cage fight
I've been chasing a recent resurgence in generic/388 recovery
failure and/or corruption events. The events have largely been
uninitialised inode chunks being tripped over in log recovery
such as:
XFS (pmem1): User initiated shutdown received.
pmem1: writeback error on inode 12621949, offset 1019904, sector 12968096
XFS (pmem1): Log I/O Error (0x6) detected at xfs_fs_goingdown+0xa3/0xf0 (fs/xfs/xfs_fsops.c:500). Shutting down filesystem.
XFS (pmem1): Please unmount the filesystem and rectify the problem(s)
XFS (pmem1): Unmounting Filesystem
XFS (pmem1): Mounting V5 Filesystem
XFS (pmem1): Starting recovery (logdev: internal)
XFS (pmem1): bad inode magic/vsn daddr 8723584 #0 (magic=1818)
XFS (pmem1): Metadata corruption detected at xfs_inode_buf_verify+0x180/0x190, xfs_inode block 0x851c80 xfs_inode_buf_verify
XFS (pmem1): Unmount and run xfs_repair
XFS (pmem1): First 128 bytes of corrupted metadata buffer:
00000000: 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 ................
00000010: 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 ................
00000020: 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 ................
00000030: 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 ................
00000040: 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 ................
00000050: 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 ................
00000060: 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 ................
00000070: 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 ................
XFS (pmem1): metadata I/O error in "xlog_recover_items_pass2+0x52/0xc0" at daddr 0x851c80 len 32 error 117
XFS (pmem1): log mount/recovery failed: error -117
XFS (pmem1): log mount failed
There have been isolated random other issues, too - xfs_repair fails
because it finds some corruption in symlink blocks, rmap
inconsistencies, etc - but they are nowhere near as common as the
uninitialised inode chunk failure.
The problem has clearly happened at runtime before recovery has run;
I can see the ICREATE log item in the log shortly before the
actively recovered range of the log. This means the ICREATE was
definitely created and written to the log, but for some reason the
tail of the log has been moved past the ordered buffer log item that
tracks INODE_ALLOC buffers and, supposedly, prevents the tail of the
log moving past the ICREATE log item before the inode chunk buffer
is written to disk.
Tracing the fsstress processes that are running when the filesystem
shut down immediately pin-pointed the problem:
user shutdown marks xfs_mount as shutdown
godown-213341 [008] 6398.022871: console: [ 6397.915392] XFS (pmem1): User initiated shutdown received.
.....
aild tries to push ordered inode cluster buffer
xfsaild/pmem1-213314 [001] 6398.022974: xfs_buf_trylock: dev 259:1 daddr 0x851c80 bbcount 0x20 hold 16 pincount 0 lock 0 flags DONE|INODES|PAGES caller xfs_inode_item_push+0x8e
xfsaild/pmem1-213314 [001] 6398.022976: xfs_ilock_nowait: dev 259:1 ino 0x851c80 flags ILOCK_SHARED caller xfs_iflush_cluster+0xae
xfs_iflush_cluster() checks xfs_is_shutdown(), returns true,
calls xfs_iflush_abort() to kill writeback of the inode.
Inode is removed from AIL, drops cluster buffer reference.
xfsaild/pmem1-213314 [001] 6398.022977: xfs_ail_delete: dev 259:1 lip 0xffff88880247ed80 old lsn 7/20344 new lsn 7/21000 type XFS_LI_INODE flags IN_AIL
xfsaild/pmem1-213314 [001] 6398.022978: xfs_buf_rele: dev 259:1 daddr 0x851c80 bbcount 0x20 hold 17 pincount 0 lock 0 flags DONE|INODES|PAGES caller xfs_iflush_abort+0xd7
.....
All inodes on cluster buffer are aborted, then the cluster buffer
itself is aborted and removed from the AIL *without writeback*:
xfsaild/pmem1-213314 [001] 6398.023011: xfs_buf_error_relse: dev 259:1 daddr 0x851c80 bbcount 0x20 hold 2 pincount 0 lock 0 flags ASYNC|DONE|STALE|INODES|PAGES caller xfs_buf_ioend_fail+0x33
xfsaild/pmem1-213314 [001] 6398.023012: xfs_ail_delete: dev 259:1 lip 0xffff8888053efde8 old lsn 7/20344 new lsn 7/20344 type XFS_LI_BUF flags IN_AIL
The inode buffer was at 7/20344 when it was removed from the AIL.
xfsaild/pmem1-213314 [001] 6398.023012: xfs_buf_item_relse: dev 259:1 daddr 0x851c80 bbcount 0x20 hold 2 pincount 0 lock 0 flags ASYNC|DONE|STALE|INODES|PAGES caller xfs_buf_item_done+0x31
xfsaild/pmem1-213314 [001] 6398.023012: xfs_buf_rele: dev 259:1 daddr 0x851c80 bbcount 0x20 hold 2 pincount 0 lock 0 flags ASYNC|DONE|STALE|INODES|PAGES caller xfs_buf_item_relse+0x39
.....
Userspace is still running, doing stuff. an fsstress process runs
syncfs() or sync() and we end up in sync_fs_one_sb() which issues
a log force. This pushes on the CIL:
fsstress-213322 [001] 6398.024430: xfs_fs_sync_fs: dev 259:1 m_features 0x20000000019ff6e9 opstate (clean|shutdown|inodegc|blockgc) s_flags 0x70810000 caller sync_fs_one_sb+0x26
fsstress-213322 [001] 6398.024430: xfs_log_force: dev 259:1 lsn 0x0 caller xfs_fs_sync_fs+0x82
fsstress-213322 [001] 6398.024430: xfs_log_force: dev 259:1 lsn 0x5f caller xfs_log_force+0x7c
<...>-194402 [001] 6398.024467: kmem_alloc: size 176 flags 0x14 caller xlog_cil_push_work+0x9f
And the CIL fills up iclogs with pending changes. This picks up
the current tail from the AIL:
<...>-194402 [001] 6398.024497: xlog_iclog_get_space: dev 259:1 state XLOG_STATE_ACTIVE refcnt 1 offset 0 lsn 0x0 flags caller xlog_write+0x149
<...>-194402 [001] 6398.024498: xlog_iclog_switch: dev 259:1 state XLOG_STATE_ACTIVE refcnt 1 offset 0 lsn 0x700005408 flags caller xlog_state_get_iclog_space+0x37e
<...>-194402 [001] 6398.024521: xlog_iclog_release: dev 259:1 state XLOG_STATE_WANT_SYNC refcnt 1 offset 32256 lsn 0x700005408 flags caller xlog_write+0x5f9
<...>-194402 [001] 6398.024522: xfs_log_assign_tail_lsn: dev 259:1 new tail lsn 7/21000, old lsn 7/20344, last sync 7/21448
And it moves the tail of the log to 7/21000 from 7/20344. This
*moves the tail of the log beyond the ICREATE transaction* that was
at 7/20344 and pinned by the inode cluster buffer that was cancelled
above.
....
godown-213341 [008] 6398.027005: xfs_force_shutdown: dev 259:1 tag logerror flags log_io|force_umount file fs/xfs/xfs_fsops.c line_num 500
godown-213341 [008] 6398.027022: console: [ 6397.915406] pmem1: writeback error on inode 12621949, offset 1019904, sector 12968096
godown-213341 [008] 6398.030551: console: [ 6397.919546] XFS (pmem1): Log I/O Error (0x6) detected at xfs_fs_goingdown+0xa3/0xf0 (fs/
And finally the log itself is now shutdown, stopping all further
writes to the log. But this is too late to prevent the corruption
that moving the tail of the log forwards after we start cancelling
writeback causes.
The fundamental problem here is that we are using the wrong shutdown
checks for log items. We've long conflated mount shutdown with log
shutdown state, and I started separating that recently with the
atomic shutdown state changes in commit b36d4651e165 ("xfs: make
forced shutdown processing atomic"). The changes in that commit
series are directly responsible for being able to diagnose this
issue because it clearly separated mount shutdown from log shutdown.
Essentially, once we start cancelling writeback of log items and
removing them from the AIL because the filesystem is shut down, we
*cannot* update the journal because we may have cancelled the items
that pin the tail of the log. That moves the tail of the log
forwards without having written the metadata back, hence we have
corrupt in memory state and writing to the journal propagates that
to the on-disk state.
What commit b36d4651e165 makes clear is that log item state needs to
change relative to log shutdown, not mount shutdown. IOWs, anything
that aborts metadata writeback needs to check log shutdown state
because log items directly affect log consistency. Having them check
mount shutdown state introduces the above race condition where we
cancel metadata writeback before the log shuts down.
To fix this, this patch works through all log items and converts
shutdown checks to use xlog_is_shutdown() rather than
xfs_is_shutdown(), so that we don't start aborting metadata
writeback before we shut off journal writes.
AFAICT, this race condition is a zero day IO error handling bug in
XFS that dates back to the introduction of XLOG_IO_ERROR,
XLOG_STATE_IOERROR and XFS_FORCED_SHUTDOWN back in January 1997.
Signed-off-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Darrick J. Wong <djwong@kernel.org>
Signed-off-by: Darrick J. Wong <djwong@kernel.org>
2022-03-17 16:09:13 +00:00
|
|
|
* If we've already shutdown the journal because of I/O errors, there's
|
|
|
|
* no point in giving this a retry.
|
2020-09-01 17:55:44 +00:00
|
|
|
*/
|
xfs: xfs_is_shutdown vs xlog_is_shutdown cage fight
I've been chasing a recent resurgence in generic/388 recovery
failure and/or corruption events. The events have largely been
uninitialised inode chunks being tripped over in log recovery
such as:
XFS (pmem1): User initiated shutdown received.
pmem1: writeback error on inode 12621949, offset 1019904, sector 12968096
XFS (pmem1): Log I/O Error (0x6) detected at xfs_fs_goingdown+0xa3/0xf0 (fs/xfs/xfs_fsops.c:500). Shutting down filesystem.
XFS (pmem1): Please unmount the filesystem and rectify the problem(s)
XFS (pmem1): Unmounting Filesystem
XFS (pmem1): Mounting V5 Filesystem
XFS (pmem1): Starting recovery (logdev: internal)
XFS (pmem1): bad inode magic/vsn daddr 8723584 #0 (magic=1818)
XFS (pmem1): Metadata corruption detected at xfs_inode_buf_verify+0x180/0x190, xfs_inode block 0x851c80 xfs_inode_buf_verify
XFS (pmem1): Unmount and run xfs_repair
XFS (pmem1): First 128 bytes of corrupted metadata buffer:
00000000: 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 ................
00000010: 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 ................
00000020: 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 ................
00000030: 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 ................
00000040: 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 ................
00000050: 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 ................
00000060: 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 ................
00000070: 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 ................
XFS (pmem1): metadata I/O error in "xlog_recover_items_pass2+0x52/0xc0" at daddr 0x851c80 len 32 error 117
XFS (pmem1): log mount/recovery failed: error -117
XFS (pmem1): log mount failed
There have been isolated random other issues, too - xfs_repair fails
because it finds some corruption in symlink blocks, rmap
inconsistencies, etc - but they are nowhere near as common as the
uninitialised inode chunk failure.
The problem has clearly happened at runtime before recovery has run;
I can see the ICREATE log item in the log shortly before the
actively recovered range of the log. This means the ICREATE was
definitely created and written to the log, but for some reason the
tail of the log has been moved past the ordered buffer log item that
tracks INODE_ALLOC buffers and, supposedly, prevents the tail of the
log moving past the ICREATE log item before the inode chunk buffer
is written to disk.
Tracing the fsstress processes that are running when the filesystem
shut down immediately pin-pointed the problem:
user shutdown marks xfs_mount as shutdown
godown-213341 [008] 6398.022871: console: [ 6397.915392] XFS (pmem1): User initiated shutdown received.
.....
aild tries to push ordered inode cluster buffer
xfsaild/pmem1-213314 [001] 6398.022974: xfs_buf_trylock: dev 259:1 daddr 0x851c80 bbcount 0x20 hold 16 pincount 0 lock 0 flags DONE|INODES|PAGES caller xfs_inode_item_push+0x8e
xfsaild/pmem1-213314 [001] 6398.022976: xfs_ilock_nowait: dev 259:1 ino 0x851c80 flags ILOCK_SHARED caller xfs_iflush_cluster+0xae
xfs_iflush_cluster() checks xfs_is_shutdown(), returns true,
calls xfs_iflush_abort() to kill writeback of the inode.
Inode is removed from AIL, drops cluster buffer reference.
xfsaild/pmem1-213314 [001] 6398.022977: xfs_ail_delete: dev 259:1 lip 0xffff88880247ed80 old lsn 7/20344 new lsn 7/21000 type XFS_LI_INODE flags IN_AIL
xfsaild/pmem1-213314 [001] 6398.022978: xfs_buf_rele: dev 259:1 daddr 0x851c80 bbcount 0x20 hold 17 pincount 0 lock 0 flags DONE|INODES|PAGES caller xfs_iflush_abort+0xd7
.....
All inodes on cluster buffer are aborted, then the cluster buffer
itself is aborted and removed from the AIL *without writeback*:
xfsaild/pmem1-213314 [001] 6398.023011: xfs_buf_error_relse: dev 259:1 daddr 0x851c80 bbcount 0x20 hold 2 pincount 0 lock 0 flags ASYNC|DONE|STALE|INODES|PAGES caller xfs_buf_ioend_fail+0x33
xfsaild/pmem1-213314 [001] 6398.023012: xfs_ail_delete: dev 259:1 lip 0xffff8888053efde8 old lsn 7/20344 new lsn 7/20344 type XFS_LI_BUF flags IN_AIL
The inode buffer was at 7/20344 when it was removed from the AIL.
xfsaild/pmem1-213314 [001] 6398.023012: xfs_buf_item_relse: dev 259:1 daddr 0x851c80 bbcount 0x20 hold 2 pincount 0 lock 0 flags ASYNC|DONE|STALE|INODES|PAGES caller xfs_buf_item_done+0x31
xfsaild/pmem1-213314 [001] 6398.023012: xfs_buf_rele: dev 259:1 daddr 0x851c80 bbcount 0x20 hold 2 pincount 0 lock 0 flags ASYNC|DONE|STALE|INODES|PAGES caller xfs_buf_item_relse+0x39
.....
Userspace is still running, doing stuff. an fsstress process runs
syncfs() or sync() and we end up in sync_fs_one_sb() which issues
a log force. This pushes on the CIL:
fsstress-213322 [001] 6398.024430: xfs_fs_sync_fs: dev 259:1 m_features 0x20000000019ff6e9 opstate (clean|shutdown|inodegc|blockgc) s_flags 0x70810000 caller sync_fs_one_sb+0x26
fsstress-213322 [001] 6398.024430: xfs_log_force: dev 259:1 lsn 0x0 caller xfs_fs_sync_fs+0x82
fsstress-213322 [001] 6398.024430: xfs_log_force: dev 259:1 lsn 0x5f caller xfs_log_force+0x7c
<...>-194402 [001] 6398.024467: kmem_alloc: size 176 flags 0x14 caller xlog_cil_push_work+0x9f
And the CIL fills up iclogs with pending changes. This picks up
the current tail from the AIL:
<...>-194402 [001] 6398.024497: xlog_iclog_get_space: dev 259:1 state XLOG_STATE_ACTIVE refcnt 1 offset 0 lsn 0x0 flags caller xlog_write+0x149
<...>-194402 [001] 6398.024498: xlog_iclog_switch: dev 259:1 state XLOG_STATE_ACTIVE refcnt 1 offset 0 lsn 0x700005408 flags caller xlog_state_get_iclog_space+0x37e
<...>-194402 [001] 6398.024521: xlog_iclog_release: dev 259:1 state XLOG_STATE_WANT_SYNC refcnt 1 offset 32256 lsn 0x700005408 flags caller xlog_write+0x5f9
<...>-194402 [001] 6398.024522: xfs_log_assign_tail_lsn: dev 259:1 new tail lsn 7/21000, old lsn 7/20344, last sync 7/21448
And it moves the tail of the log to 7/21000 from 7/20344. This
*moves the tail of the log beyond the ICREATE transaction* that was
at 7/20344 and pinned by the inode cluster buffer that was cancelled
above.
....
godown-213341 [008] 6398.027005: xfs_force_shutdown: dev 259:1 tag logerror flags log_io|force_umount file fs/xfs/xfs_fsops.c line_num 500
godown-213341 [008] 6398.027022: console: [ 6397.915406] pmem1: writeback error on inode 12621949, offset 1019904, sector 12968096
godown-213341 [008] 6398.030551: console: [ 6397.919546] XFS (pmem1): Log I/O Error (0x6) detected at xfs_fs_goingdown+0xa3/0xf0 (fs/
And finally the log itself is now shutdown, stopping all further
writes to the log. But this is too late to prevent the corruption
that moving the tail of the log forwards after we start cancelling
writeback causes.
The fundamental problem here is that we are using the wrong shutdown
checks for log items. We've long conflated mount shutdown with log
shutdown state, and I started separating that recently with the
atomic shutdown state changes in commit b36d4651e165 ("xfs: make
forced shutdown processing atomic"). The changes in that commit
series are directly responsible for being able to diagnose this
issue because it clearly separated mount shutdown from log shutdown.
Essentially, once we start cancelling writeback of log items and
removing them from the AIL because the filesystem is shut down, we
*cannot* update the journal because we may have cancelled the items
that pin the tail of the log. That moves the tail of the log
forwards without having written the metadata back, hence we have
corrupt in memory state and writing to the journal propagates that
to the on-disk state.
What commit b36d4651e165 makes clear is that log item state needs to
change relative to log shutdown, not mount shutdown. IOWs, anything
that aborts metadata writeback needs to check log shutdown state
because log items directly affect log consistency. Having them check
mount shutdown state introduces the above race condition where we
cancel metadata writeback before the log shuts down.
To fix this, this patch works through all log items and converts
shutdown checks to use xlog_is_shutdown() rather than
xfs_is_shutdown(), so that we don't start aborting metadata
writeback before we shut off journal writes.
AFAICT, this race condition is a zero day IO error handling bug in
XFS that dates back to the introduction of XLOG_IO_ERROR,
XLOG_STATE_IOERROR and XFS_FORCED_SHUTDOWN back in January 1997.
Signed-off-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Darrick J. Wong <djwong@kernel.org>
Signed-off-by: Darrick J. Wong <djwong@kernel.org>
2022-03-17 16:09:13 +00:00
|
|
|
if (xlog_is_shutdown(mp->m_log))
|
2020-09-01 17:55:44 +00:00
|
|
|
goto out_stale;
|
|
|
|
|
|
|
|
xfs_buf_ioerror_alert_ratelimited(bp);
|
|
|
|
|
2020-09-01 17:55:46 +00:00
|
|
|
/*
|
|
|
|
* We're not going to bother about retrying this during recovery.
|
|
|
|
* One strike!
|
|
|
|
*/
|
|
|
|
if (bp->b_flags & _XBF_LOGRECOVERY) {
|
|
|
|
xfs_force_shutdown(mp, SHUTDOWN_META_IO_ERROR);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-09-01 17:55:44 +00:00
|
|
|
/*
|
|
|
|
* Synchronous writes will have callers process the error.
|
|
|
|
*/
|
|
|
|
if (!(bp->b_flags & XBF_ASYNC))
|
2020-09-01 17:55:29 +00:00
|
|
|
goto out_stale;
|
|
|
|
|
|
|
|
trace_xfs_buf_iodone_async(bp, _RET_IP_);
|
|
|
|
|
|
|
|
cfg = xfs_error_get_cfg(mp, XFS_ERR_METADATA, bp->b_error);
|
2020-09-01 17:55:45 +00:00
|
|
|
if (bp->b_last_error != bp->b_error ||
|
|
|
|
!(bp->b_flags & (XBF_STALE | XBF_WRITE_FAIL))) {
|
|
|
|
bp->b_last_error = bp->b_error;
|
|
|
|
if (cfg->retry_timeout != XFS_ERR_RETRY_FOREVER &&
|
|
|
|
!bp->b_first_retry_time)
|
|
|
|
bp->b_first_retry_time = jiffies;
|
|
|
|
goto resubmit;
|
2020-09-01 17:55:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Permanent error - we need to trigger a shutdown if we haven't already
|
|
|
|
* to indicate that inconsistency will result from this action.
|
|
|
|
*/
|
|
|
|
if (xfs_buf_ioerror_permanent(bp, cfg)) {
|
|
|
|
xfs_force_shutdown(mp, SHUTDOWN_META_IO_ERROR);
|
|
|
|
goto out_stale;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Still considered a transient error. Caller will schedule retries. */
|
2020-09-01 17:55:45 +00:00
|
|
|
if (bp->b_flags & _XBF_INODES)
|
|
|
|
xfs_buf_inode_io_fail(bp);
|
|
|
|
else if (bp->b_flags & _XBF_DQUOTS)
|
|
|
|
xfs_buf_dquot_io_fail(bp);
|
|
|
|
else
|
|
|
|
ASSERT(list_empty(&bp->b_li_list));
|
|
|
|
xfs_buf_ioerror(bp, 0);
|
|
|
|
xfs_buf_relse(bp);
|
2020-09-01 17:55:45 +00:00
|
|
|
return true;
|
2020-09-01 17:55:29 +00:00
|
|
|
|
2020-09-01 17:55:45 +00:00
|
|
|
resubmit:
|
|
|
|
xfs_buf_ioerror(bp, 0);
|
2020-09-01 17:55:46 +00:00
|
|
|
bp->b_flags |= (XBF_DONE | XBF_WRITE_FAIL);
|
2020-09-01 17:55:45 +00:00
|
|
|
xfs_buf_submit(bp);
|
2020-09-01 17:55:45 +00:00
|
|
|
return true;
|
2020-09-01 17:55:29 +00:00
|
|
|
out_stale:
|
|
|
|
xfs_buf_stale(bp);
|
|
|
|
bp->b_flags |= XBF_DONE;
|
2020-09-01 17:55:46 +00:00
|
|
|
bp->b_flags &= ~XBF_WRITE;
|
2020-09-01 17:55:29 +00:00
|
|
|
trace_xfs_buf_error_relse(bp, _RET_IP_);
|
2020-09-01 17:55:45 +00:00
|
|
|
return false;
|
2020-09-01 17:55:29 +00:00
|
|
|
}
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2020-09-01 17:55:20 +00:00
|
|
|
static void
|
2014-10-01 23:04:22 +00:00
|
|
|
xfs_buf_ioend(
|
|
|
|
struct xfs_buf *bp)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2014-10-01 23:04:22 +00:00
|
|
|
trace_xfs_buf_iodone(bp, _RET_IP_);
|
2012-11-14 06:54:40 +00:00
|
|
|
|
2014-10-01 23:04:31 +00:00
|
|
|
/*
|
|
|
|
* Pull in IO completion errors now. We are guaranteed to be running
|
|
|
|
* single threaded, so we don't need the lock to read b_io_error.
|
|
|
|
*/
|
|
|
|
if (!bp->b_error && bp->b_io_error)
|
|
|
|
xfs_buf_ioerror(bp, bp->b_io_error);
|
|
|
|
|
2020-09-01 17:55:46 +00:00
|
|
|
if (bp->b_flags & XBF_READ) {
|
2020-06-29 21:48:47 +00:00
|
|
|
if (!bp->b_error && bp->b_ops)
|
|
|
|
bp->b_ops->verify_read(bp);
|
|
|
|
if (!bp->b_error)
|
|
|
|
bp->b_flags |= XBF_DONE;
|
2020-09-01 17:55:20 +00:00
|
|
|
} else {
|
|
|
|
if (!bp->b_error) {
|
|
|
|
bp->b_flags &= ~XBF_WRITE_FAIL;
|
|
|
|
bp->b_flags |= XBF_DONE;
|
|
|
|
}
|
2020-06-29 21:48:46 +00:00
|
|
|
|
2020-09-01 17:55:45 +00:00
|
|
|
if (unlikely(bp->b_error) && xfs_buf_ioend_handle_error(bp))
|
2020-09-01 17:55:29 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
/* clear the retry state */
|
|
|
|
bp->b_last_error = 0;
|
|
|
|
bp->b_retries = 0;
|
|
|
|
bp->b_first_retry_time = 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Note that for things like remote attribute buffers, there may
|
|
|
|
* not be a buffer log item here, so processing the buffer log
|
|
|
|
* item must remain optional.
|
|
|
|
*/
|
|
|
|
if (bp->b_log_item)
|
|
|
|
xfs_buf_item_done(bp);
|
|
|
|
|
2020-09-01 17:55:20 +00:00
|
|
|
if (bp->b_flags & _XBF_INODES)
|
|
|
|
xfs_buf_inode_iodone(bp);
|
|
|
|
else if (bp->b_flags & _XBF_DQUOTS)
|
|
|
|
xfs_buf_dquot_iodone(bp);
|
2020-09-01 17:55:46 +00:00
|
|
|
|
2020-06-29 21:48:46 +00:00
|
|
|
}
|
2020-09-01 17:55:44 +00:00
|
|
|
|
2020-09-01 17:55:46 +00:00
|
|
|
bp->b_flags &= ~(XBF_READ | XBF_WRITE | XBF_READ_AHEAD |
|
|
|
|
_XBF_LOGRECOVERY);
|
2020-09-01 17:55:46 +00:00
|
|
|
|
2020-09-01 17:55:44 +00:00
|
|
|
if (bp->b_flags & XBF_ASYNC)
|
|
|
|
xfs_buf_relse(bp);
|
|
|
|
else
|
|
|
|
complete(&bp->b_iowait);
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
2014-10-01 23:04:22 +00:00
|
|
|
static void
|
|
|
|
xfs_buf_ioend_work(
|
|
|
|
struct work_struct *work)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2014-10-01 23:04:22 +00:00
|
|
|
struct xfs_buf *bp =
|
2020-12-17 00:07:34 +00:00
|
|
|
container_of(work, struct xfs_buf, b_ioend_work);
|
2009-12-14 23:14:59 +00:00
|
|
|
|
2014-10-01 23:04:22 +00:00
|
|
|
xfs_buf_ioend(bp);
|
|
|
|
}
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2016-01-04 05:10:42 +00:00
|
|
|
static void
|
2014-10-01 23:04:22 +00:00
|
|
|
xfs_buf_ioend_async(
|
|
|
|
struct xfs_buf *bp)
|
|
|
|
{
|
2014-12-03 22:43:17 +00:00
|
|
|
INIT_WORK(&bp->b_ioend_work, xfs_buf_ioend_work);
|
2019-06-29 02:27:29 +00:00
|
|
|
queue_work(bp->b_mount->m_buf_workqueue, &bp->b_ioend_work);
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2018-01-08 18:51:02 +00:00
|
|
|
__xfs_buf_ioerror(
|
2020-12-17 00:07:34 +00:00
|
|
|
struct xfs_buf *bp,
|
2018-01-08 18:51:02 +00:00
|
|
|
int error,
|
|
|
|
xfs_failaddr_t failaddr)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2014-06-25 04:58:08 +00:00
|
|
|
ASSERT(error <= 0 && error >= -1000);
|
|
|
|
bp->b_error = error;
|
2018-01-08 18:51:02 +00:00
|
|
|
trace_xfs_buf_ioerror(bp, error, failaddr);
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
2011-10-10 16:52:49 +00:00
|
|
|
void
|
|
|
|
xfs_buf_ioerror_alert(
|
|
|
|
struct xfs_buf *bp,
|
2020-01-24 01:01:20 +00:00
|
|
|
xfs_failaddr_t func)
|
2011-10-10 16:52:49 +00:00
|
|
|
{
|
2020-05-06 20:25:21 +00:00
|
|
|
xfs_buf_alert_ratelimited(bp, "XFS: metadata IO error",
|
|
|
|
"metadata I/O error in \"%pS\" at daddr 0x%llx len %d error %d",
|
2021-08-19 01:46:57 +00:00
|
|
|
func, (uint64_t)xfs_buf_daddr(bp),
|
2020-05-06 20:25:21 +00:00
|
|
|
bp->b_length, -bp->b_error);
|
2011-10-10 16:52:49 +00:00
|
|
|
}
|
|
|
|
|
2020-05-06 20:25:19 +00:00
|
|
|
/*
|
|
|
|
* To simulate an I/O failure, the buffer must be locked and held with at least
|
|
|
|
* three references. The LRU reference is dropped by the stale call. The buf
|
|
|
|
* item reference is dropped via ioend processing. The third reference is owned
|
|
|
|
* by the caller and is dropped on I/O completion if the buffer is XBF_ASYNC.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
xfs_buf_ioend_fail(
|
|
|
|
struct xfs_buf *bp)
|
|
|
|
{
|
|
|
|
bp->b_flags &= ~XBF_DONE;
|
|
|
|
xfs_buf_stale(bp);
|
|
|
|
xfs_buf_ioerror(bp, -EIO);
|
|
|
|
xfs_buf_ioend(bp);
|
2011-10-10 16:52:49 +00:00
|
|
|
}
|
|
|
|
|
2012-07-13 06:24:10 +00:00
|
|
|
int
|
|
|
|
xfs_bwrite(
|
|
|
|
struct xfs_buf *bp)
|
|
|
|
{
|
|
|
|
int error;
|
|
|
|
|
|
|
|
ASSERT(xfs_buf_islocked(bp));
|
|
|
|
|
|
|
|
bp->b_flags |= XBF_WRITE;
|
2014-10-01 23:04:56 +00:00
|
|
|
bp->b_flags &= ~(XBF_ASYNC | XBF_READ | _XBF_DELWRI_Q |
|
2020-05-06 20:25:20 +00:00
|
|
|
XBF_DONE);
|
2012-07-13 06:24:10 +00:00
|
|
|
|
2018-07-12 05:26:35 +00:00
|
|
|
error = xfs_buf_submit(bp);
|
2019-06-29 02:27:29 +00:00
|
|
|
if (error)
|
|
|
|
xfs_force_shutdown(bp->b_mount, SHUTDOWN_META_IO_ERROR);
|
2012-07-13 06:24:10 +00:00
|
|
|
return error;
|
|
|
|
}
|
|
|
|
|
2016-05-18 00:56:41 +00:00
|
|
|
static void
|
2006-01-11 04:39:08 +00:00
|
|
|
xfs_buf_bio_end_io(
|
2015-07-20 13:29:37 +00:00
|
|
|
struct bio *bio)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2016-05-18 00:56:41 +00:00
|
|
|
struct xfs_buf *bp = (struct xfs_buf *)bio->bi_private;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2020-05-06 20:29:19 +00:00
|
|
|
if (!bio->bi_status &&
|
|
|
|
(bp->b_flags & XBF_WRITE) && (bp->b_flags & XBF_ASYNC) &&
|
2020-05-08 15:50:52 +00:00
|
|
|
XFS_TEST_ERROR(false, bp->b_mount, XFS_ERRTAG_BUF_IOERROR))
|
2020-05-06 20:29:19 +00:00
|
|
|
bio->bi_status = BLK_STS_IOERR;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2012-11-12 11:09:46 +00:00
|
|
|
/*
|
|
|
|
* don't overwrite existing errors - otherwise we can lose errors on
|
|
|
|
* buffers that require multiple bios to complete.
|
|
|
|
*/
|
2017-06-03 07:38:06 +00:00
|
|
|
if (bio->bi_status) {
|
|
|
|
int error = blk_status_to_errno(bio->bi_status);
|
|
|
|
|
|
|
|
cmpxchg(&bp->b_io_error, 0, error);
|
|
|
|
}
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2012-11-12 11:09:46 +00:00
|
|
|
if (!bp->b_error && xfs_buf_is_vmapped(bp) && (bp->b_flags & XBF_READ))
|
2010-01-25 17:42:24 +00:00
|
|
|
invalidate_kernel_vmap_range(bp->b_addr, xfs_buf_vmap_len(bp));
|
|
|
|
|
2014-10-01 23:04:22 +00:00
|
|
|
if (atomic_dec_and_test(&bp->b_io_remaining) == 1)
|
|
|
|
xfs_buf_ioend_async(bp);
|
2005-04-16 22:20:36 +00:00
|
|
|
bio_put(bio);
|
|
|
|
}
|
|
|
|
|
2012-06-22 08:50:09 +00:00
|
|
|
static void
|
|
|
|
xfs_buf_ioapply_map(
|
|
|
|
struct xfs_buf *bp,
|
|
|
|
int map,
|
|
|
|
int *buf_offset,
|
|
|
|
int *count,
|
2022-07-14 18:07:28 +00:00
|
|
|
blk_opf_t op)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2012-06-22 08:50:09 +00:00
|
|
|
int page_index;
|
2021-01-29 04:38:57 +00:00
|
|
|
unsigned int total_nr_pages = bp->b_page_count;
|
2012-06-22 08:50:09 +00:00
|
|
|
int nr_pages;
|
|
|
|
struct bio *bio;
|
|
|
|
sector_t sector = bp->b_maps[map].bm_bn;
|
|
|
|
int size;
|
|
|
|
int offset;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2012-06-22 08:50:09 +00:00
|
|
|
/* skip the pages in the buffer before the start offset */
|
|
|
|
page_index = 0;
|
|
|
|
offset = *buf_offset;
|
|
|
|
while (offset >= PAGE_SIZE) {
|
|
|
|
page_index++;
|
|
|
|
offset -= PAGE_SIZE;
|
2005-11-01 23:26:59 +00:00
|
|
|
}
|
|
|
|
|
2012-06-22 08:50:09 +00:00
|
|
|
/*
|
|
|
|
* Limit the IO size to the length of the current vector, and update the
|
|
|
|
* remaining IO count for the next time around.
|
|
|
|
*/
|
|
|
|
size = min_t(int, BBTOB(bp->b_maps[map].bm_len), *count);
|
|
|
|
*count -= size;
|
|
|
|
*buf_offset += size;
|
2011-07-26 15:06:44 +00:00
|
|
|
|
2005-04-16 22:20:36 +00:00
|
|
|
next_chunk:
|
2006-01-11 04:39:08 +00:00
|
|
|
atomic_inc(&bp->b_io_remaining);
|
2021-01-29 04:38:57 +00:00
|
|
|
nr_pages = bio_max_segs(total_nr_pages);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2022-01-24 09:11:05 +00:00
|
|
|
bio = bio_alloc(bp->b_target->bt_bdev, nr_pages, op, GFP_NOIO);
|
2013-10-11 22:44:27 +00:00
|
|
|
bio->bi_iter.bi_sector = sector;
|
2006-01-11 04:39:08 +00:00
|
|
|
bio->bi_end_io = xfs_buf_bio_end_io;
|
|
|
|
bio->bi_private = bp;
|
2011-03-25 22:16:45 +00:00
|
|
|
|
2012-06-22 08:50:09 +00:00
|
|
|
for (; size && nr_pages; nr_pages--, page_index++) {
|
2011-03-25 22:16:45 +00:00
|
|
|
int rbytes, nbytes = PAGE_SIZE - offset;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
|
|
|
if (nbytes > size)
|
|
|
|
nbytes = size;
|
|
|
|
|
2012-06-22 08:50:09 +00:00
|
|
|
rbytes = bio_add_page(bio, bp->b_pages[page_index], nbytes,
|
|
|
|
offset);
|
2006-01-11 04:39:08 +00:00
|
|
|
if (rbytes < nbytes)
|
2005-04-16 22:20:36 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
offset = 0;
|
2012-04-23 05:58:52 +00:00
|
|
|
sector += BTOBB(nbytes);
|
2005-04-16 22:20:36 +00:00
|
|
|
size -= nbytes;
|
|
|
|
total_nr_pages--;
|
|
|
|
}
|
|
|
|
|
2013-10-11 22:44:27 +00:00
|
|
|
if (likely(bio->bi_iter.bi_size)) {
|
2010-01-25 17:42:24 +00:00
|
|
|
if (xfs_buf_is_vmapped(bp)) {
|
|
|
|
flush_kernel_vmap_range(bp->b_addr,
|
|
|
|
xfs_buf_vmap_len(bp));
|
|
|
|
}
|
2016-06-05 19:31:41 +00:00
|
|
|
submit_bio(bio);
|
2005-04-16 22:20:36 +00:00
|
|
|
if (size)
|
|
|
|
goto next_chunk;
|
|
|
|
} else {
|
2012-11-12 11:09:46 +00:00
|
|
|
/*
|
|
|
|
* This is guaranteed not to be the last io reference count
|
2014-10-01 23:05:14 +00:00
|
|
|
* because the caller (xfs_buf_submit) holds a count itself.
|
2012-11-12 11:09:46 +00:00
|
|
|
*/
|
|
|
|
atomic_dec(&bp->b_io_remaining);
|
2014-06-25 04:58:08 +00:00
|
|
|
xfs_buf_ioerror(bp, -EIO);
|
2010-07-20 07:52:59 +00:00
|
|
|
bio_put(bio);
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
2012-06-22 08:50:09 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
STATIC void
|
|
|
|
_xfs_buf_ioapply(
|
|
|
|
struct xfs_buf *bp)
|
|
|
|
{
|
|
|
|
struct blk_plug plug;
|
2022-07-14 18:07:28 +00:00
|
|
|
blk_opf_t op;
|
2012-06-22 08:50:09 +00:00
|
|
|
int offset;
|
|
|
|
int size;
|
|
|
|
int i;
|
|
|
|
|
2013-03-12 12:30:34 +00:00
|
|
|
/*
|
|
|
|
* Make sure we capture only current IO errors rather than stale errors
|
|
|
|
* left over from previous use of the buffer (e.g. failed readahead).
|
|
|
|
*/
|
|
|
|
bp->b_error = 0;
|
|
|
|
|
2012-06-22 08:50:09 +00:00
|
|
|
if (bp->b_flags & XBF_WRITE) {
|
2016-06-05 19:31:57 +00:00
|
|
|
op = REQ_OP_WRITE;
|
2012-11-14 06:54:40 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Run the write verifier callback function if it exists. If
|
|
|
|
* this function fails it will mark the buffer with an error and
|
|
|
|
* the IO should not be dispatched.
|
|
|
|
*/
|
|
|
|
if (bp->b_ops) {
|
|
|
|
bp->b_ops->verify_write(bp);
|
|
|
|
if (bp->b_error) {
|
2019-06-29 02:27:29 +00:00
|
|
|
xfs_force_shutdown(bp->b_mount,
|
2012-11-14 06:54:40 +00:00
|
|
|
SHUTDOWN_CORRUPT_INCORE);
|
|
|
|
return;
|
|
|
|
}
|
2021-08-19 01:48:54 +00:00
|
|
|
} else if (bp->b_rhash_key != XFS_BUF_DADDR_NULL) {
|
2019-06-29 02:27:29 +00:00
|
|
|
struct xfs_mount *mp = bp->b_mount;
|
2014-08-04 02:42:40 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* non-crc filesystems don't attach verifiers during
|
|
|
|
* log recovery, so don't warn for such filesystems.
|
|
|
|
*/
|
2021-08-19 01:46:37 +00:00
|
|
|
if (xfs_has_crc(mp)) {
|
2014-08-04 02:42:40 +00:00
|
|
|
xfs_warn(mp,
|
2018-01-08 19:39:18 +00:00
|
|
|
"%s: no buf ops on daddr 0x%llx len %d",
|
2021-08-19 01:48:54 +00:00
|
|
|
__func__, xfs_buf_daddr(bp),
|
|
|
|
bp->b_length);
|
2018-01-08 18:51:26 +00:00
|
|
|
xfs_hex_dump(bp->b_addr,
|
|
|
|
XFS_CORRUPTION_DUMP_LEN);
|
2014-08-04 02:42:40 +00:00
|
|
|
dump_stack();
|
|
|
|
}
|
2012-11-14 06:54:40 +00:00
|
|
|
}
|
2012-06-22 08:50:09 +00:00
|
|
|
} else {
|
2016-06-05 19:31:57 +00:00
|
|
|
op = REQ_OP_READ;
|
2019-10-28 15:41:42 +00:00
|
|
|
if (bp->b_flags & XBF_READ_AHEAD)
|
|
|
|
op |= REQ_RAHEAD;
|
2012-06-22 08:50:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* we only use the buffer cache for meta-data */
|
2019-10-28 15:41:42 +00:00
|
|
|
op |= REQ_META;
|
2012-06-22 08:50:09 +00:00
|
|
|
|
2024-02-22 20:43:21 +00:00
|
|
|
/* in-memory targets are directly mapped, no IO required. */
|
|
|
|
if (xfs_buftarg_is_mem(bp->b_target)) {
|
|
|
|
xfs_buf_ioend(bp);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-06-22 08:50:09 +00:00
|
|
|
/*
|
|
|
|
* Walk all the vectors issuing IO on them. Set up the initial offset
|
|
|
|
* into the buffer and the desired IO size before we start -
|
|
|
|
* _xfs_buf_ioapply_vec() will modify them appropriately for each
|
|
|
|
* subsequent call.
|
|
|
|
*/
|
|
|
|
offset = bp->b_offset;
|
2019-06-29 02:27:28 +00:00
|
|
|
size = BBTOB(bp->b_length);
|
2012-06-22 08:50:09 +00:00
|
|
|
blk_start_plug(&plug);
|
|
|
|
for (i = 0; i < bp->b_map_count; i++) {
|
2019-10-28 15:41:42 +00:00
|
|
|
xfs_buf_ioapply_map(bp, i, &offset, &size, op);
|
2012-06-22 08:50:09 +00:00
|
|
|
if (bp->b_error)
|
|
|
|
break;
|
|
|
|
if (size <= 0)
|
|
|
|
break; /* all done */
|
|
|
|
}
|
|
|
|
blk_finish_plug(&plug);
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
2014-10-01 23:05:14 +00:00
|
|
|
/*
|
2018-07-12 05:26:35 +00:00
|
|
|
* Wait for I/O completion of a sync buffer and return the I/O error code.
|
2014-10-01 23:05:14 +00:00
|
|
|
*/
|
2018-07-12 05:26:34 +00:00
|
|
|
static int
|
2018-07-12 05:26:35 +00:00
|
|
|
xfs_buf_iowait(
|
2014-10-01 23:05:14 +00:00
|
|
|
struct xfs_buf *bp)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2018-07-12 05:26:35 +00:00
|
|
|
ASSERT(!(bp->b_flags & XBF_ASYNC));
|
|
|
|
|
|
|
|
trace_xfs_buf_iowait(bp, _RET_IP_);
|
|
|
|
wait_for_completion(&bp->b_iowait);
|
|
|
|
trace_xfs_buf_iowait_done(bp, _RET_IP_);
|
|
|
|
|
|
|
|
return bp->b_error;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Buffer I/O submission path, read or write. Asynchronous submission transfers
|
|
|
|
* the buffer lock ownership and the current reference to the IO. It is not
|
|
|
|
* safe to reference the buffer after a call to this function unless the caller
|
|
|
|
* holds an additional reference itself.
|
|
|
|
*/
|
2020-09-01 17:55:47 +00:00
|
|
|
static int
|
2018-07-12 05:26:35 +00:00
|
|
|
__xfs_buf_submit(
|
|
|
|
struct xfs_buf *bp,
|
|
|
|
bool wait)
|
|
|
|
{
|
|
|
|
int error = 0;
|
|
|
|
|
2014-10-01 23:05:14 +00:00
|
|
|
trace_xfs_buf_submit(bp, _RET_IP_);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
xfs: on-stack delayed write buffer lists
Queue delwri buffers on a local on-stack list instead of a per-buftarg one,
and write back the buffers per-process instead of by waking up xfsbufd.
This is now easily doable given that we have very few places left that write
delwri buffers:
- log recovery:
Only done at mount time, and already forcing out the buffers
synchronously using xfs_flush_buftarg
- quotacheck:
Same story.
- dquot reclaim:
Writes out dirty dquots on the LRU under memory pressure. We might
want to look into doing more of this via xfsaild, but it's already
more optimal than the synchronous inode reclaim that writes each
buffer synchronously.
- xfsaild:
This is the main beneficiary of the change. By keeping a local list
of buffers to write we reduce latency of writing out buffers, and
more importably we can remove all the delwri list promotions which
were hitting the buffer cache hard under sustained metadata loads.
The implementation is very straight forward - xfs_buf_delwri_queue now gets
a new list_head pointer that it adds the delwri buffers to, and all callers
need to eventually submit the list using xfs_buf_delwi_submit or
xfs_buf_delwi_submit_nowait. Buffers that already are on a delwri list are
skipped in xfs_buf_delwri_queue, assuming they already are on another delwri
list. The biggest change to pass down the buffer list was done to the AIL
pushing. Now that we operate on buffers the trylock, push and pushbuf log
item methods are merged into a single push routine, which tries to lock the
item, and if possible add the buffer that needs writeback to the buffer list.
This leads to much simpler code than the previous split but requires the
individual IOP_PUSH instances to unlock and reacquire the AIL around calls
to blocking routines.
Given that xfsailds now also handle writing out buffers, the conditions for
log forcing and the sleep times needed some small changes. The most
important one is that we consider an AIL busy as long we still have buffers
to push, and the other one is that we do increment the pushed LSN for
buffers that are under flushing at this moment, but still count them towards
the stuck items for restart purposes. Without this we could hammer on stuck
items without ever forcing the log and not make progress under heavy random
delete workloads on fast flash storage devices.
[ Dave Chinner:
- rebase on previous patches.
- improved comments for XBF_DELWRI_Q handling
- fix XBF_ASYNC handling in queue submission (test 106 failure)
- rename delwri submit function buffer list parameters for clarity
- xfs_efd_item_push() should return XFS_ITEM_PINNED ]
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Mark Tinguely <tinguely@sgi.com>
Signed-off-by: Ben Myers <bpm@sgi.com>
2012-04-23 05:58:39 +00:00
|
|
|
ASSERT(!(bp->b_flags & _XBF_DELWRI_Q));
|
2014-10-01 23:05:14 +00:00
|
|
|
|
xfs: xfs_is_shutdown vs xlog_is_shutdown cage fight
I've been chasing a recent resurgence in generic/388 recovery
failure and/or corruption events. The events have largely been
uninitialised inode chunks being tripped over in log recovery
such as:
XFS (pmem1): User initiated shutdown received.
pmem1: writeback error on inode 12621949, offset 1019904, sector 12968096
XFS (pmem1): Log I/O Error (0x6) detected at xfs_fs_goingdown+0xa3/0xf0 (fs/xfs/xfs_fsops.c:500). Shutting down filesystem.
XFS (pmem1): Please unmount the filesystem and rectify the problem(s)
XFS (pmem1): Unmounting Filesystem
XFS (pmem1): Mounting V5 Filesystem
XFS (pmem1): Starting recovery (logdev: internal)
XFS (pmem1): bad inode magic/vsn daddr 8723584 #0 (magic=1818)
XFS (pmem1): Metadata corruption detected at xfs_inode_buf_verify+0x180/0x190, xfs_inode block 0x851c80 xfs_inode_buf_verify
XFS (pmem1): Unmount and run xfs_repair
XFS (pmem1): First 128 bytes of corrupted metadata buffer:
00000000: 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 ................
00000010: 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 ................
00000020: 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 ................
00000030: 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 ................
00000040: 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 ................
00000050: 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 ................
00000060: 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 ................
00000070: 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 ................
XFS (pmem1): metadata I/O error in "xlog_recover_items_pass2+0x52/0xc0" at daddr 0x851c80 len 32 error 117
XFS (pmem1): log mount/recovery failed: error -117
XFS (pmem1): log mount failed
There have been isolated random other issues, too - xfs_repair fails
because it finds some corruption in symlink blocks, rmap
inconsistencies, etc - but they are nowhere near as common as the
uninitialised inode chunk failure.
The problem has clearly happened at runtime before recovery has run;
I can see the ICREATE log item in the log shortly before the
actively recovered range of the log. This means the ICREATE was
definitely created and written to the log, but for some reason the
tail of the log has been moved past the ordered buffer log item that
tracks INODE_ALLOC buffers and, supposedly, prevents the tail of the
log moving past the ICREATE log item before the inode chunk buffer
is written to disk.
Tracing the fsstress processes that are running when the filesystem
shut down immediately pin-pointed the problem:
user shutdown marks xfs_mount as shutdown
godown-213341 [008] 6398.022871: console: [ 6397.915392] XFS (pmem1): User initiated shutdown received.
.....
aild tries to push ordered inode cluster buffer
xfsaild/pmem1-213314 [001] 6398.022974: xfs_buf_trylock: dev 259:1 daddr 0x851c80 bbcount 0x20 hold 16 pincount 0 lock 0 flags DONE|INODES|PAGES caller xfs_inode_item_push+0x8e
xfsaild/pmem1-213314 [001] 6398.022976: xfs_ilock_nowait: dev 259:1 ino 0x851c80 flags ILOCK_SHARED caller xfs_iflush_cluster+0xae
xfs_iflush_cluster() checks xfs_is_shutdown(), returns true,
calls xfs_iflush_abort() to kill writeback of the inode.
Inode is removed from AIL, drops cluster buffer reference.
xfsaild/pmem1-213314 [001] 6398.022977: xfs_ail_delete: dev 259:1 lip 0xffff88880247ed80 old lsn 7/20344 new lsn 7/21000 type XFS_LI_INODE flags IN_AIL
xfsaild/pmem1-213314 [001] 6398.022978: xfs_buf_rele: dev 259:1 daddr 0x851c80 bbcount 0x20 hold 17 pincount 0 lock 0 flags DONE|INODES|PAGES caller xfs_iflush_abort+0xd7
.....
All inodes on cluster buffer are aborted, then the cluster buffer
itself is aborted and removed from the AIL *without writeback*:
xfsaild/pmem1-213314 [001] 6398.023011: xfs_buf_error_relse: dev 259:1 daddr 0x851c80 bbcount 0x20 hold 2 pincount 0 lock 0 flags ASYNC|DONE|STALE|INODES|PAGES caller xfs_buf_ioend_fail+0x33
xfsaild/pmem1-213314 [001] 6398.023012: xfs_ail_delete: dev 259:1 lip 0xffff8888053efde8 old lsn 7/20344 new lsn 7/20344 type XFS_LI_BUF flags IN_AIL
The inode buffer was at 7/20344 when it was removed from the AIL.
xfsaild/pmem1-213314 [001] 6398.023012: xfs_buf_item_relse: dev 259:1 daddr 0x851c80 bbcount 0x20 hold 2 pincount 0 lock 0 flags ASYNC|DONE|STALE|INODES|PAGES caller xfs_buf_item_done+0x31
xfsaild/pmem1-213314 [001] 6398.023012: xfs_buf_rele: dev 259:1 daddr 0x851c80 bbcount 0x20 hold 2 pincount 0 lock 0 flags ASYNC|DONE|STALE|INODES|PAGES caller xfs_buf_item_relse+0x39
.....
Userspace is still running, doing stuff. an fsstress process runs
syncfs() or sync() and we end up in sync_fs_one_sb() which issues
a log force. This pushes on the CIL:
fsstress-213322 [001] 6398.024430: xfs_fs_sync_fs: dev 259:1 m_features 0x20000000019ff6e9 opstate (clean|shutdown|inodegc|blockgc) s_flags 0x70810000 caller sync_fs_one_sb+0x26
fsstress-213322 [001] 6398.024430: xfs_log_force: dev 259:1 lsn 0x0 caller xfs_fs_sync_fs+0x82
fsstress-213322 [001] 6398.024430: xfs_log_force: dev 259:1 lsn 0x5f caller xfs_log_force+0x7c
<...>-194402 [001] 6398.024467: kmem_alloc: size 176 flags 0x14 caller xlog_cil_push_work+0x9f
And the CIL fills up iclogs with pending changes. This picks up
the current tail from the AIL:
<...>-194402 [001] 6398.024497: xlog_iclog_get_space: dev 259:1 state XLOG_STATE_ACTIVE refcnt 1 offset 0 lsn 0x0 flags caller xlog_write+0x149
<...>-194402 [001] 6398.024498: xlog_iclog_switch: dev 259:1 state XLOG_STATE_ACTIVE refcnt 1 offset 0 lsn 0x700005408 flags caller xlog_state_get_iclog_space+0x37e
<...>-194402 [001] 6398.024521: xlog_iclog_release: dev 259:1 state XLOG_STATE_WANT_SYNC refcnt 1 offset 32256 lsn 0x700005408 flags caller xlog_write+0x5f9
<...>-194402 [001] 6398.024522: xfs_log_assign_tail_lsn: dev 259:1 new tail lsn 7/21000, old lsn 7/20344, last sync 7/21448
And it moves the tail of the log to 7/21000 from 7/20344. This
*moves the tail of the log beyond the ICREATE transaction* that was
at 7/20344 and pinned by the inode cluster buffer that was cancelled
above.
....
godown-213341 [008] 6398.027005: xfs_force_shutdown: dev 259:1 tag logerror flags log_io|force_umount file fs/xfs/xfs_fsops.c line_num 500
godown-213341 [008] 6398.027022: console: [ 6397.915406] pmem1: writeback error on inode 12621949, offset 1019904, sector 12968096
godown-213341 [008] 6398.030551: console: [ 6397.919546] XFS (pmem1): Log I/O Error (0x6) detected at xfs_fs_goingdown+0xa3/0xf0 (fs/
And finally the log itself is now shutdown, stopping all further
writes to the log. But this is too late to prevent the corruption
that moving the tail of the log forwards after we start cancelling
writeback causes.
The fundamental problem here is that we are using the wrong shutdown
checks for log items. We've long conflated mount shutdown with log
shutdown state, and I started separating that recently with the
atomic shutdown state changes in commit b36d4651e165 ("xfs: make
forced shutdown processing atomic"). The changes in that commit
series are directly responsible for being able to diagnose this
issue because it clearly separated mount shutdown from log shutdown.
Essentially, once we start cancelling writeback of log items and
removing them from the AIL because the filesystem is shut down, we
*cannot* update the journal because we may have cancelled the items
that pin the tail of the log. That moves the tail of the log
forwards without having written the metadata back, hence we have
corrupt in memory state and writing to the journal propagates that
to the on-disk state.
What commit b36d4651e165 makes clear is that log item state needs to
change relative to log shutdown, not mount shutdown. IOWs, anything
that aborts metadata writeback needs to check log shutdown state
because log items directly affect log consistency. Having them check
mount shutdown state introduces the above race condition where we
cancel metadata writeback before the log shuts down.
To fix this, this patch works through all log items and converts
shutdown checks to use xlog_is_shutdown() rather than
xfs_is_shutdown(), so that we don't start aborting metadata
writeback before we shut off journal writes.
AFAICT, this race condition is a zero day IO error handling bug in
XFS that dates back to the introduction of XLOG_IO_ERROR,
XLOG_STATE_IOERROR and XFS_FORCED_SHUTDOWN back in January 1997.
Signed-off-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Darrick J. Wong <djwong@kernel.org>
Signed-off-by: Darrick J. Wong <djwong@kernel.org>
2022-03-17 16:09:13 +00:00
|
|
|
/*
|
|
|
|
* On log shutdown we stale and complete the buffer immediately. We can
|
|
|
|
* be called to read the superblock before the log has been set up, so
|
|
|
|
* be careful checking the log state.
|
|
|
|
*
|
|
|
|
* Checking the mount shutdown state here can result in the log tail
|
|
|
|
* moving inappropriately on disk as the log may not yet be shut down.
|
|
|
|
* i.e. failing this buffer on mount shutdown can remove it from the AIL
|
|
|
|
* and move the tail of the log forwards without having written this
|
|
|
|
* buffer to disk. This corrupts the log tail state in memory, and
|
|
|
|
* because the log may not be shut down yet, it can then be propagated
|
|
|
|
* to disk before the log is shutdown. Hence we check log shutdown
|
|
|
|
* state here rather than mount state to avoid corrupting the log tail
|
|
|
|
* on shutdown.
|
|
|
|
*/
|
|
|
|
if (bp->b_mount->m_log &&
|
|
|
|
xlog_is_shutdown(bp->b_mount->m_log)) {
|
2020-05-06 20:25:19 +00:00
|
|
|
xfs_buf_ioend_fail(bp);
|
2018-07-12 05:26:34 +00:00
|
|
|
return -EIO;
|
2014-10-01 23:05:14 +00:00
|
|
|
}
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2018-07-12 05:26:35 +00:00
|
|
|
/*
|
|
|
|
* Grab a reference so the buffer does not go away underneath us. For
|
|
|
|
* async buffers, I/O completion drops the callers reference, which
|
|
|
|
* could occur before submission returns.
|
|
|
|
*/
|
|
|
|
xfs_buf_hold(bp);
|
|
|
|
|
2011-08-23 08:28:03 +00:00
|
|
|
if (bp->b_flags & XBF_WRITE)
|
2006-01-11 04:39:08 +00:00
|
|
|
xfs_buf_wait_unpin(bp);
|
2014-10-01 23:04:11 +00:00
|
|
|
|
2014-10-01 23:04:31 +00:00
|
|
|
/* clear the internal error state to avoid spurious errors */
|
|
|
|
bp->b_io_error = 0;
|
|
|
|
|
2014-04-16 22:15:28 +00:00
|
|
|
/*
|
2014-10-01 23:04:11 +00:00
|
|
|
* Set the count to 1 initially, this will stop an I/O completion
|
|
|
|
* callout which happens before we have started all the I/O from calling
|
|
|
|
* xfs_buf_ioend too early.
|
2005-04-16 22:20:36 +00:00
|
|
|
*/
|
2006-01-11 04:39:08 +00:00
|
|
|
atomic_set(&bp->b_io_remaining, 1);
|
2018-07-12 05:26:34 +00:00
|
|
|
if (bp->b_flags & XBF_ASYNC)
|
|
|
|
xfs_buf_ioacct_inc(bp);
|
2006-01-11 04:39:08 +00:00
|
|
|
_xfs_buf_ioapply(bp);
|
2014-10-01 23:04:11 +00:00
|
|
|
|
2014-04-16 22:15:28 +00:00
|
|
|
/*
|
2014-10-01 23:05:14 +00:00
|
|
|
* If _xfs_buf_ioapply failed, we can get back here with only the IO
|
|
|
|
* reference we took above. If we drop it to zero, run completion so
|
|
|
|
* that we don't return to the caller with completion still pending.
|
2014-04-16 22:15:28 +00:00
|
|
|
*/
|
2014-10-01 23:04:22 +00:00
|
|
|
if (atomic_dec_and_test(&bp->b_io_remaining) == 1) {
|
2018-07-12 05:26:34 +00:00
|
|
|
if (bp->b_error || !(bp->b_flags & XBF_ASYNC))
|
2014-10-01 23:04:22 +00:00
|
|
|
xfs_buf_ioend(bp);
|
|
|
|
else
|
|
|
|
xfs_buf_ioend_async(bp);
|
|
|
|
}
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2018-07-12 05:26:35 +00:00
|
|
|
if (wait)
|
|
|
|
error = xfs_buf_iowait(bp);
|
2018-07-12 05:26:35 +00:00
|
|
|
|
2014-10-01 23:05:14 +00:00
|
|
|
/*
|
2018-07-12 05:26:35 +00:00
|
|
|
* Release the hold that keeps the buffer referenced for the entire
|
|
|
|
* I/O. Note that if the buffer is async, it is not safe to reference
|
|
|
|
* after this release.
|
2014-10-01 23:05:14 +00:00
|
|
|
*/
|
|
|
|
xfs_buf_rele(bp);
|
|
|
|
return error;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
2015-06-21 23:44:29 +00:00
|
|
|
void *
|
2006-01-11 04:39:08 +00:00
|
|
|
xfs_buf_offset(
|
2015-06-21 23:44:29 +00:00
|
|
|
struct xfs_buf *bp,
|
2005-04-16 22:20:36 +00:00
|
|
|
size_t offset)
|
|
|
|
{
|
|
|
|
struct page *page;
|
|
|
|
|
2012-04-23 05:59:07 +00:00
|
|
|
if (bp->b_addr)
|
2011-07-22 23:40:15 +00:00
|
|
|
return bp->b_addr + offset;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2011-03-25 22:16:45 +00:00
|
|
|
page = bp->b_pages[offset >> PAGE_SHIFT];
|
2015-06-21 23:44:29 +00:00
|
|
|
return page_address(page) + (offset & (PAGE_SIZE-1));
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2019-06-12 15:59:59 +00:00
|
|
|
xfs_buf_zero(
|
|
|
|
struct xfs_buf *bp,
|
|
|
|
size_t boff,
|
|
|
|
size_t bsize)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2012-04-23 05:58:53 +00:00
|
|
|
size_t bend;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
|
|
|
bend = boff + bsize;
|
|
|
|
while (boff < bend) {
|
2012-04-23 05:58:53 +00:00
|
|
|
struct page *page;
|
|
|
|
int page_index, page_offset, csize;
|
|
|
|
|
|
|
|
page_index = (boff + bp->b_offset) >> PAGE_SHIFT;
|
|
|
|
page_offset = (boff + bp->b_offset) & ~PAGE_MASK;
|
|
|
|
page = bp->b_pages[page_index];
|
|
|
|
csize = min_t(size_t, PAGE_SIZE - page_offset,
|
2019-06-29 02:27:28 +00:00
|
|
|
BBTOB(bp->b_length) - boff);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2012-04-23 05:58:53 +00:00
|
|
|
ASSERT((csize + page_offset) <= PAGE_SIZE);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2019-06-12 15:59:59 +00:00
|
|
|
memset(page_address(page) + page_offset, 0, csize);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
|
|
|
boff += csize;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-11 17:37:54 +00:00
|
|
|
/*
|
|
|
|
* Log a message about and stale a buffer that a caller has decided is corrupt.
|
|
|
|
*
|
|
|
|
* This function should be called for the kinds of metadata corruption that
|
|
|
|
* cannot be detect from a verifier, such as incorrect inter-block relationship
|
|
|
|
* data. Do /not/ call this function from a verifier function.
|
|
|
|
*
|
|
|
|
* The buffer must be XBF_DONE prior to the call. Afterwards, the buffer will
|
|
|
|
* be marked stale, but b_error will not be set. The caller is responsible for
|
|
|
|
* releasing the buffer or fixing it.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
__xfs_buf_mark_corrupt(
|
|
|
|
struct xfs_buf *bp,
|
|
|
|
xfs_failaddr_t fa)
|
|
|
|
{
|
|
|
|
ASSERT(bp->b_flags & XBF_DONE);
|
|
|
|
|
2020-03-11 17:37:54 +00:00
|
|
|
xfs_buf_corruption_error(bp, fa);
|
2020-03-11 17:37:54 +00:00
|
|
|
xfs_buf_stale(bp);
|
|
|
|
}
|
|
|
|
|
2005-04-16 22:20:36 +00:00
|
|
|
/*
|
2006-01-11 04:39:08 +00:00
|
|
|
* Handling of buffer targets (buftargs).
|
2005-04-16 22:20:36 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
2010-12-02 05:30:55 +00:00
|
|
|
* Wait for any bufs with callbacks that have been submitted but have not yet
|
|
|
|
* returned. These buffers will have an elevated hold count, so wait on those
|
|
|
|
* while freeing all the buffers only held by the LRU.
|
2005-04-16 22:20:36 +00:00
|
|
|
*/
|
2013-08-28 00:18:05 +00:00
|
|
|
static enum lru_status
|
2021-01-23 00:48:19 +00:00
|
|
|
xfs_buftarg_drain_rele(
|
2013-08-28 00:18:05 +00:00
|
|
|
struct list_head *item,
|
2015-02-12 22:59:35 +00:00
|
|
|
struct list_lru_one *lru,
|
2013-08-28 00:18:05 +00:00
|
|
|
spinlock_t *lru_lock,
|
|
|
|
void *arg)
|
|
|
|
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2013-08-28 00:18:05 +00:00
|
|
|
struct xfs_buf *bp = container_of(item, struct xfs_buf, b_lru);
|
2013-08-28 00:18:06 +00:00
|
|
|
struct list_head *dispose = arg;
|
2010-12-02 05:30:55 +00:00
|
|
|
|
2013-08-28 00:18:05 +00:00
|
|
|
if (atomic_read(&bp->b_hold) > 1) {
|
2013-08-28 00:18:06 +00:00
|
|
|
/* need to wait, so skip it this pass */
|
2021-01-23 00:48:19 +00:00
|
|
|
trace_xfs_buf_drain_buftarg(bp, _RET_IP_);
|
2013-08-28 00:18:06 +00:00
|
|
|
return LRU_SKIP;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
2013-08-28 00:18:06 +00:00
|
|
|
if (!spin_trylock(&bp->b_lock))
|
|
|
|
return LRU_SKIP;
|
2013-08-28 00:18:05 +00:00
|
|
|
|
2013-08-28 00:18:06 +00:00
|
|
|
/*
|
|
|
|
* clear the LRU reference count so the buffer doesn't get
|
|
|
|
* ignored in xfs_buf_rele().
|
|
|
|
*/
|
|
|
|
atomic_set(&bp->b_lru_ref, 0);
|
|
|
|
bp->b_state |= XFS_BSTATE_DISPOSE;
|
2015-02-12 22:59:35 +00:00
|
|
|
list_lru_isolate_move(lru, item, dispose);
|
2013-08-28 00:18:06 +00:00
|
|
|
spin_unlock(&bp->b_lock);
|
|
|
|
return LRU_REMOVED;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
2021-01-23 00:48:20 +00:00
|
|
|
/*
|
|
|
|
* Wait for outstanding I/O on the buftarg to complete.
|
|
|
|
*/
|
2013-08-28 00:18:05 +00:00
|
|
|
void
|
2021-01-23 00:48:20 +00:00
|
|
|
xfs_buftarg_wait(
|
2013-08-28 00:18:05 +00:00
|
|
|
struct xfs_buftarg *btp)
|
|
|
|
{
|
xfs: log mount failures don't wait for buffers to be released
Recently I've been seeing xfs/051 fail on 1k block size filesystems.
Trying to trace the events during the test lead to the problem going
away, indicating that it was a race condition that lead to this
ASSERT failure:
XFS: Assertion failed: atomic_read(&pag->pag_ref) == 0, file: fs/xfs/xfs_mount.c, line: 156
.....
[<ffffffff814e1257>] xfs_free_perag+0x87/0xb0
[<ffffffff814e21b9>] xfs_mountfs+0x4d9/0x900
[<ffffffff814e5dff>] xfs_fs_fill_super+0x3bf/0x4d0
[<ffffffff811d8800>] mount_bdev+0x180/0x1b0
[<ffffffff814e3ff5>] xfs_fs_mount+0x15/0x20
[<ffffffff811d90a8>] mount_fs+0x38/0x170
[<ffffffff811f4347>] vfs_kern_mount+0x67/0x120
[<ffffffff811f7018>] do_mount+0x218/0xd60
[<ffffffff811f7e5b>] SyS_mount+0x8b/0xd0
When I finally caught it with tracing enabled, I saw that AG 2 had
an elevated reference count and a buffer was responsible for it. I
tracked down the specific buffer, and found that it was missing the
final reference count release that would put it back on the LRU and
hence be found by xfs_wait_buftarg() calls in the log mount failure
handling.
The last four traces for the buffer before the assert were (trimmed
for relevance)
kworker/0:1-5259 xfs_buf_iodone: hold 2 lock 0 flags ASYNC
kworker/0:1-5259 xfs_buf_ioerror: hold 2 lock 0 error -5
mount-7163 xfs_buf_lock_done: hold 2 lock 0 flags ASYNC
mount-7163 xfs_buf_unlock: hold 2 lock 1 flags ASYNC
This is an async write that is completing, so there's nobody waiting
for it directly. Hence we call xfs_buf_relse() once all the
processing is complete. That does:
static inline void xfs_buf_relse(xfs_buf_t *bp)
{
xfs_buf_unlock(bp);
xfs_buf_rele(bp);
}
Now, it's clear that mount is waiting on the buffer lock, and that
it has been released by xfs_buf_relse() and gained by mount. This is
expected, because at this point the mount process is in
xfs_buf_delwri_submit() waiting for all the IO it submitted to
complete.
The mount process, however, is waiting on the lock for the buffer
because it is in xfs_buf_delwri_submit(). This waits for IO
completion, but it doesn't wait for the buffer reference owned by
the IO to go away. The mount process collects all the completions,
fails the log recovery, and the higher level code then calls
xfs_wait_buftarg() to free all the remaining buffers in the
filesystem.
The issue is that on unlocking the buffer, the scheduler has decided
that the mount process has higher priority than the the kworker
thread that is running the IO completion, and so immediately
switched contexts to the mount process from the semaphore unlock
code, hence preventing the kworker thread from finishing the IO
completion and releasing the IO reference to the buffer.
Hence by the time that xfs_wait_buftarg() is run, the buffer still
has an active reference and so isn't on the LRU list that the
function walks to free the remaining buffers. Hence we miss that
buffer and continue onwards to tear down the mount structures,
at which time we get find a stray reference count on the perag
structure. On a non-debug kernel, this will be ignored and the
structure torn down and freed. Hence when the kworker thread is then
rescheduled and the buffer released and freed, it will access a
freed perag structure.
The problem here is that when the log mount fails, we still need to
quiesce the log to ensure that the IO workqueues have returned to
idle before we run xfs_wait_buftarg(). By synchronising the
workqueues, we ensure that all IO completions are fully processed,
not just to the point where buffers have been unlocked. This ensures
we don't end up in the situation above.
cc: <stable@vger.kernel.org> # 3.18
Signed-off-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Brian Foster <bfoster@redhat.com>
Signed-off-by: Dave Chinner <david@fromorbit.com>
2016-01-18 21:28:10 +00:00
|
|
|
/*
|
xfs: track and serialize in-flight async buffers against unmount
Newly allocated XFS metadata buffers are added to the LRU once the hold
count is released, which typically occurs after I/O completion. There is
no other mechanism at current that tracks the existence or I/O state of
a new buffer. Further, readahead I/O tends to be submitted
asynchronously by nature, which means the I/O can remain in flight and
actually complete long after the calling context is gone. This means
that file descriptors or any other holds on the filesystem can be
released, allowing the filesystem to be unmounted while I/O is still in
flight. When I/O completion occurs, core data structures may have been
freed, causing completion to run into invalid memory accesses and likely
to panic.
This problem is reproduced on XFS via directory readahead. A filesystem
is mounted, a directory is opened/closed and the filesystem immediately
unmounted. The open/close cycle triggers a directory readahead that if
delayed long enough, runs buffer I/O completion after the unmount has
completed.
To address this problem, add a mechanism to track all in-flight,
asynchronous buffers using per-cpu counters in the buftarg. The buffer
is accounted on the first I/O submission after the current reference is
acquired and unaccounted once the buffer is returned to the LRU or
freed. Update xfs_wait_buftarg() to wait on all in-flight I/O before
walking the LRU list. Once in-flight I/O has completed and the workqueue
has drained, all new buffers should have been released onto the LRU.
Signed-off-by: Brian Foster <bfoster@redhat.com>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Signed-off-by: Dave Chinner <david@fromorbit.com>
2016-07-20 01:15:28 +00:00
|
|
|
* First wait on the buftarg I/O count for all in-flight buffers to be
|
|
|
|
* released. This is critical as new buffers do not make the LRU until
|
|
|
|
* they are released.
|
|
|
|
*
|
|
|
|
* Next, flush the buffer workqueue to ensure all completion processing
|
|
|
|
* has finished. Just waiting on buffer locks is not sufficient for
|
|
|
|
* async IO as the reference count held over IO is not released until
|
|
|
|
* after the buffer lock is dropped. Hence we need to ensure here that
|
|
|
|
* all reference counts have been dropped before we start walking the
|
|
|
|
* LRU list.
|
xfs: log mount failures don't wait for buffers to be released
Recently I've been seeing xfs/051 fail on 1k block size filesystems.
Trying to trace the events during the test lead to the problem going
away, indicating that it was a race condition that lead to this
ASSERT failure:
XFS: Assertion failed: atomic_read(&pag->pag_ref) == 0, file: fs/xfs/xfs_mount.c, line: 156
.....
[<ffffffff814e1257>] xfs_free_perag+0x87/0xb0
[<ffffffff814e21b9>] xfs_mountfs+0x4d9/0x900
[<ffffffff814e5dff>] xfs_fs_fill_super+0x3bf/0x4d0
[<ffffffff811d8800>] mount_bdev+0x180/0x1b0
[<ffffffff814e3ff5>] xfs_fs_mount+0x15/0x20
[<ffffffff811d90a8>] mount_fs+0x38/0x170
[<ffffffff811f4347>] vfs_kern_mount+0x67/0x120
[<ffffffff811f7018>] do_mount+0x218/0xd60
[<ffffffff811f7e5b>] SyS_mount+0x8b/0xd0
When I finally caught it with tracing enabled, I saw that AG 2 had
an elevated reference count and a buffer was responsible for it. I
tracked down the specific buffer, and found that it was missing the
final reference count release that would put it back on the LRU and
hence be found by xfs_wait_buftarg() calls in the log mount failure
handling.
The last four traces for the buffer before the assert were (trimmed
for relevance)
kworker/0:1-5259 xfs_buf_iodone: hold 2 lock 0 flags ASYNC
kworker/0:1-5259 xfs_buf_ioerror: hold 2 lock 0 error -5
mount-7163 xfs_buf_lock_done: hold 2 lock 0 flags ASYNC
mount-7163 xfs_buf_unlock: hold 2 lock 1 flags ASYNC
This is an async write that is completing, so there's nobody waiting
for it directly. Hence we call xfs_buf_relse() once all the
processing is complete. That does:
static inline void xfs_buf_relse(xfs_buf_t *bp)
{
xfs_buf_unlock(bp);
xfs_buf_rele(bp);
}
Now, it's clear that mount is waiting on the buffer lock, and that
it has been released by xfs_buf_relse() and gained by mount. This is
expected, because at this point the mount process is in
xfs_buf_delwri_submit() waiting for all the IO it submitted to
complete.
The mount process, however, is waiting on the lock for the buffer
because it is in xfs_buf_delwri_submit(). This waits for IO
completion, but it doesn't wait for the buffer reference owned by
the IO to go away. The mount process collects all the completions,
fails the log recovery, and the higher level code then calls
xfs_wait_buftarg() to free all the remaining buffers in the
filesystem.
The issue is that on unlocking the buffer, the scheduler has decided
that the mount process has higher priority than the the kworker
thread that is running the IO completion, and so immediately
switched contexts to the mount process from the semaphore unlock
code, hence preventing the kworker thread from finishing the IO
completion and releasing the IO reference to the buffer.
Hence by the time that xfs_wait_buftarg() is run, the buffer still
has an active reference and so isn't on the LRU list that the
function walks to free the remaining buffers. Hence we miss that
buffer and continue onwards to tear down the mount structures,
at which time we get find a stray reference count on the perag
structure. On a non-debug kernel, this will be ignored and the
structure torn down and freed. Hence when the kworker thread is then
rescheduled and the buffer released and freed, it will access a
freed perag structure.
The problem here is that when the log mount fails, we still need to
quiesce the log to ensure that the IO workqueues have returned to
idle before we run xfs_wait_buftarg(). By synchronising the
workqueues, we ensure that all IO completions are fully processed,
not just to the point where buffers have been unlocked. This ensures
we don't end up in the situation above.
cc: <stable@vger.kernel.org> # 3.18
Signed-off-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Brian Foster <bfoster@redhat.com>
Signed-off-by: Dave Chinner <david@fromorbit.com>
2016-01-18 21:28:10 +00:00
|
|
|
*/
|
xfs: track and serialize in-flight async buffers against unmount
Newly allocated XFS metadata buffers are added to the LRU once the hold
count is released, which typically occurs after I/O completion. There is
no other mechanism at current that tracks the existence or I/O state of
a new buffer. Further, readahead I/O tends to be submitted
asynchronously by nature, which means the I/O can remain in flight and
actually complete long after the calling context is gone. This means
that file descriptors or any other holds on the filesystem can be
released, allowing the filesystem to be unmounted while I/O is still in
flight. When I/O completion occurs, core data structures may have been
freed, causing completion to run into invalid memory accesses and likely
to panic.
This problem is reproduced on XFS via directory readahead. A filesystem
is mounted, a directory is opened/closed and the filesystem immediately
unmounted. The open/close cycle triggers a directory readahead that if
delayed long enough, runs buffer I/O completion after the unmount has
completed.
To address this problem, add a mechanism to track all in-flight,
asynchronous buffers using per-cpu counters in the buftarg. The buffer
is accounted on the first I/O submission after the current reference is
acquired and unaccounted once the buffer is returned to the LRU or
freed. Update xfs_wait_buftarg() to wait on all in-flight I/O before
walking the LRU list. Once in-flight I/O has completed and the workqueue
has drained, all new buffers should have been released onto the LRU.
Signed-off-by: Brian Foster <bfoster@redhat.com>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Signed-off-by: Dave Chinner <david@fromorbit.com>
2016-07-20 01:15:28 +00:00
|
|
|
while (percpu_counter_sum(&btp->bt_io_count))
|
|
|
|
delay(100);
|
2016-08-26 06:01:59 +00:00
|
|
|
flush_workqueue(btp->bt_mount->m_buf_workqueue);
|
2021-01-23 00:48:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
xfs_buftarg_drain(
|
|
|
|
struct xfs_buftarg *btp)
|
|
|
|
{
|
|
|
|
LIST_HEAD(dispose);
|
|
|
|
int loop = 0;
|
|
|
|
bool write_fail = false;
|
|
|
|
|
|
|
|
xfs_buftarg_wait(btp);
|
xfs: log mount failures don't wait for buffers to be released
Recently I've been seeing xfs/051 fail on 1k block size filesystems.
Trying to trace the events during the test lead to the problem going
away, indicating that it was a race condition that lead to this
ASSERT failure:
XFS: Assertion failed: atomic_read(&pag->pag_ref) == 0, file: fs/xfs/xfs_mount.c, line: 156
.....
[<ffffffff814e1257>] xfs_free_perag+0x87/0xb0
[<ffffffff814e21b9>] xfs_mountfs+0x4d9/0x900
[<ffffffff814e5dff>] xfs_fs_fill_super+0x3bf/0x4d0
[<ffffffff811d8800>] mount_bdev+0x180/0x1b0
[<ffffffff814e3ff5>] xfs_fs_mount+0x15/0x20
[<ffffffff811d90a8>] mount_fs+0x38/0x170
[<ffffffff811f4347>] vfs_kern_mount+0x67/0x120
[<ffffffff811f7018>] do_mount+0x218/0xd60
[<ffffffff811f7e5b>] SyS_mount+0x8b/0xd0
When I finally caught it with tracing enabled, I saw that AG 2 had
an elevated reference count and a buffer was responsible for it. I
tracked down the specific buffer, and found that it was missing the
final reference count release that would put it back on the LRU and
hence be found by xfs_wait_buftarg() calls in the log mount failure
handling.
The last four traces for the buffer before the assert were (trimmed
for relevance)
kworker/0:1-5259 xfs_buf_iodone: hold 2 lock 0 flags ASYNC
kworker/0:1-5259 xfs_buf_ioerror: hold 2 lock 0 error -5
mount-7163 xfs_buf_lock_done: hold 2 lock 0 flags ASYNC
mount-7163 xfs_buf_unlock: hold 2 lock 1 flags ASYNC
This is an async write that is completing, so there's nobody waiting
for it directly. Hence we call xfs_buf_relse() once all the
processing is complete. That does:
static inline void xfs_buf_relse(xfs_buf_t *bp)
{
xfs_buf_unlock(bp);
xfs_buf_rele(bp);
}
Now, it's clear that mount is waiting on the buffer lock, and that
it has been released by xfs_buf_relse() and gained by mount. This is
expected, because at this point the mount process is in
xfs_buf_delwri_submit() waiting for all the IO it submitted to
complete.
The mount process, however, is waiting on the lock for the buffer
because it is in xfs_buf_delwri_submit(). This waits for IO
completion, but it doesn't wait for the buffer reference owned by
the IO to go away. The mount process collects all the completions,
fails the log recovery, and the higher level code then calls
xfs_wait_buftarg() to free all the remaining buffers in the
filesystem.
The issue is that on unlocking the buffer, the scheduler has decided
that the mount process has higher priority than the the kworker
thread that is running the IO completion, and so immediately
switched contexts to the mount process from the semaphore unlock
code, hence preventing the kworker thread from finishing the IO
completion and releasing the IO reference to the buffer.
Hence by the time that xfs_wait_buftarg() is run, the buffer still
has an active reference and so isn't on the LRU list that the
function walks to free the remaining buffers. Hence we miss that
buffer and continue onwards to tear down the mount structures,
at which time we get find a stray reference count on the perag
structure. On a non-debug kernel, this will be ignored and the
structure torn down and freed. Hence when the kworker thread is then
rescheduled and the buffer released and freed, it will access a
freed perag structure.
The problem here is that when the log mount fails, we still need to
quiesce the log to ensure that the IO workqueues have returned to
idle before we run xfs_wait_buftarg(). By synchronising the
workqueues, we ensure that all IO completions are fully processed,
not just to the point where buffers have been unlocked. This ensures
we don't end up in the situation above.
cc: <stable@vger.kernel.org> # 3.18
Signed-off-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Brian Foster <bfoster@redhat.com>
Signed-off-by: Dave Chinner <david@fromorbit.com>
2016-01-18 21:28:10 +00:00
|
|
|
|
2013-08-28 00:18:06 +00:00
|
|
|
/* loop until there is nothing left on the lru list. */
|
|
|
|
while (list_lru_count(&btp->bt_lru)) {
|
2021-01-23 00:48:19 +00:00
|
|
|
list_lru_walk(&btp->bt_lru, xfs_buftarg_drain_rele,
|
2013-08-28 00:18:06 +00:00
|
|
|
&dispose, LONG_MAX);
|
|
|
|
|
|
|
|
while (!list_empty(&dispose)) {
|
|
|
|
struct xfs_buf *bp;
|
|
|
|
bp = list_first_entry(&dispose, struct xfs_buf, b_lru);
|
|
|
|
list_del_init(&bp->b_lru);
|
xfs: abort metadata writeback on permanent errors
If we are doing aysnc writeback of metadata, we can get write errors
but have nobody to report them to. At the moment, we simply attempt
to reissue the write from io completion in the hope that it's a
transient error.
When it's not a transient error, the buffer is stuck forever in
this loop, and we cannot break out of it. Eventually, unmount will
hang because the AIL cannot be emptied and everything goes downhill
from them.
To solve this problem, only retry the write IO once before aborting
it. We don't throw the buffer away because some transient errors can
last minutes (e.g. FC path failover) or even hours (thin
provisioned devices that have run out of backing space) before they
go away. Hence we really want to keep trying until we can't try any
more.
Because the buffer was not cleaned, however, it does not get removed
from the AIL and hence the next pass across the AIL will start IO on
it again. As such, we still get the "retry forever" semantics that
we currently have, but we allow other access to the buffer in the
mean time. Meanwhile the filesystem can continue to modify the
buffer and relog it, so the IO errors won't hang the log or the
filesystem.
Now when we are pushing the AIL, we can see all these "permanent IO
error" buffers and we can issue a warning about failures before we
retry the IO. We can also catch these buffers when unmounting an
issue a corruption warning, too.
Signed-off-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Ben Myers <bpm@sgi.com>
2013-12-12 05:34:38 +00:00
|
|
|
if (bp->b_flags & XBF_WRITE_FAIL) {
|
2020-05-06 20:25:21 +00:00
|
|
|
write_fail = true;
|
|
|
|
xfs_buf_alert_ratelimited(bp,
|
|
|
|
"XFS: Corruption Alert",
|
2018-01-08 19:39:18 +00:00
|
|
|
"Corruption Alert: Buffer at daddr 0x%llx had permanent write failures!",
|
2021-08-19 01:48:54 +00:00
|
|
|
(long long)xfs_buf_daddr(bp));
|
xfs: abort metadata writeback on permanent errors
If we are doing aysnc writeback of metadata, we can get write errors
but have nobody to report them to. At the moment, we simply attempt
to reissue the write from io completion in the hope that it's a
transient error.
When it's not a transient error, the buffer is stuck forever in
this loop, and we cannot break out of it. Eventually, unmount will
hang because the AIL cannot be emptied and everything goes downhill
from them.
To solve this problem, only retry the write IO once before aborting
it. We don't throw the buffer away because some transient errors can
last minutes (e.g. FC path failover) or even hours (thin
provisioned devices that have run out of backing space) before they
go away. Hence we really want to keep trying until we can't try any
more.
Because the buffer was not cleaned, however, it does not get removed
from the AIL and hence the next pass across the AIL will start IO on
it again. As such, we still get the "retry forever" semantics that
we currently have, but we allow other access to the buffer in the
mean time. Meanwhile the filesystem can continue to modify the
buffer and relog it, so the IO errors won't hang the log or the
filesystem.
Now when we are pushing the AIL, we can see all these "permanent IO
error" buffers and we can issue a warning about failures before we
retry the IO. We can also catch these buffers when unmounting an
issue a corruption warning, too.
Signed-off-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Ben Myers <bpm@sgi.com>
2013-12-12 05:34:38 +00:00
|
|
|
}
|
2013-08-28 00:18:06 +00:00
|
|
|
xfs_buf_rele(bp);
|
|
|
|
}
|
|
|
|
if (loop++ != 0)
|
|
|
|
delay(100);
|
|
|
|
}
|
2020-05-06 20:25:21 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* If one or more failed buffers were freed, that means dirty metadata
|
|
|
|
* was thrown away. This should only ever happen after I/O completion
|
|
|
|
* handling has elevated I/O error(s) to permanent failures and shuts
|
xfs: xfs_is_shutdown vs xlog_is_shutdown cage fight
I've been chasing a recent resurgence in generic/388 recovery
failure and/or corruption events. The events have largely been
uninitialised inode chunks being tripped over in log recovery
such as:
XFS (pmem1): User initiated shutdown received.
pmem1: writeback error on inode 12621949, offset 1019904, sector 12968096
XFS (pmem1): Log I/O Error (0x6) detected at xfs_fs_goingdown+0xa3/0xf0 (fs/xfs/xfs_fsops.c:500). Shutting down filesystem.
XFS (pmem1): Please unmount the filesystem and rectify the problem(s)
XFS (pmem1): Unmounting Filesystem
XFS (pmem1): Mounting V5 Filesystem
XFS (pmem1): Starting recovery (logdev: internal)
XFS (pmem1): bad inode magic/vsn daddr 8723584 #0 (magic=1818)
XFS (pmem1): Metadata corruption detected at xfs_inode_buf_verify+0x180/0x190, xfs_inode block 0x851c80 xfs_inode_buf_verify
XFS (pmem1): Unmount and run xfs_repair
XFS (pmem1): First 128 bytes of corrupted metadata buffer:
00000000: 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 ................
00000010: 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 ................
00000020: 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 ................
00000030: 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 ................
00000040: 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 ................
00000050: 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 ................
00000060: 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 ................
00000070: 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 ................
XFS (pmem1): metadata I/O error in "xlog_recover_items_pass2+0x52/0xc0" at daddr 0x851c80 len 32 error 117
XFS (pmem1): log mount/recovery failed: error -117
XFS (pmem1): log mount failed
There have been isolated random other issues, too - xfs_repair fails
because it finds some corruption in symlink blocks, rmap
inconsistencies, etc - but they are nowhere near as common as the
uninitialised inode chunk failure.
The problem has clearly happened at runtime before recovery has run;
I can see the ICREATE log item in the log shortly before the
actively recovered range of the log. This means the ICREATE was
definitely created and written to the log, but for some reason the
tail of the log has been moved past the ordered buffer log item that
tracks INODE_ALLOC buffers and, supposedly, prevents the tail of the
log moving past the ICREATE log item before the inode chunk buffer
is written to disk.
Tracing the fsstress processes that are running when the filesystem
shut down immediately pin-pointed the problem:
user shutdown marks xfs_mount as shutdown
godown-213341 [008] 6398.022871: console: [ 6397.915392] XFS (pmem1): User initiated shutdown received.
.....
aild tries to push ordered inode cluster buffer
xfsaild/pmem1-213314 [001] 6398.022974: xfs_buf_trylock: dev 259:1 daddr 0x851c80 bbcount 0x20 hold 16 pincount 0 lock 0 flags DONE|INODES|PAGES caller xfs_inode_item_push+0x8e
xfsaild/pmem1-213314 [001] 6398.022976: xfs_ilock_nowait: dev 259:1 ino 0x851c80 flags ILOCK_SHARED caller xfs_iflush_cluster+0xae
xfs_iflush_cluster() checks xfs_is_shutdown(), returns true,
calls xfs_iflush_abort() to kill writeback of the inode.
Inode is removed from AIL, drops cluster buffer reference.
xfsaild/pmem1-213314 [001] 6398.022977: xfs_ail_delete: dev 259:1 lip 0xffff88880247ed80 old lsn 7/20344 new lsn 7/21000 type XFS_LI_INODE flags IN_AIL
xfsaild/pmem1-213314 [001] 6398.022978: xfs_buf_rele: dev 259:1 daddr 0x851c80 bbcount 0x20 hold 17 pincount 0 lock 0 flags DONE|INODES|PAGES caller xfs_iflush_abort+0xd7
.....
All inodes on cluster buffer are aborted, then the cluster buffer
itself is aborted and removed from the AIL *without writeback*:
xfsaild/pmem1-213314 [001] 6398.023011: xfs_buf_error_relse: dev 259:1 daddr 0x851c80 bbcount 0x20 hold 2 pincount 0 lock 0 flags ASYNC|DONE|STALE|INODES|PAGES caller xfs_buf_ioend_fail+0x33
xfsaild/pmem1-213314 [001] 6398.023012: xfs_ail_delete: dev 259:1 lip 0xffff8888053efde8 old lsn 7/20344 new lsn 7/20344 type XFS_LI_BUF flags IN_AIL
The inode buffer was at 7/20344 when it was removed from the AIL.
xfsaild/pmem1-213314 [001] 6398.023012: xfs_buf_item_relse: dev 259:1 daddr 0x851c80 bbcount 0x20 hold 2 pincount 0 lock 0 flags ASYNC|DONE|STALE|INODES|PAGES caller xfs_buf_item_done+0x31
xfsaild/pmem1-213314 [001] 6398.023012: xfs_buf_rele: dev 259:1 daddr 0x851c80 bbcount 0x20 hold 2 pincount 0 lock 0 flags ASYNC|DONE|STALE|INODES|PAGES caller xfs_buf_item_relse+0x39
.....
Userspace is still running, doing stuff. an fsstress process runs
syncfs() or sync() and we end up in sync_fs_one_sb() which issues
a log force. This pushes on the CIL:
fsstress-213322 [001] 6398.024430: xfs_fs_sync_fs: dev 259:1 m_features 0x20000000019ff6e9 opstate (clean|shutdown|inodegc|blockgc) s_flags 0x70810000 caller sync_fs_one_sb+0x26
fsstress-213322 [001] 6398.024430: xfs_log_force: dev 259:1 lsn 0x0 caller xfs_fs_sync_fs+0x82
fsstress-213322 [001] 6398.024430: xfs_log_force: dev 259:1 lsn 0x5f caller xfs_log_force+0x7c
<...>-194402 [001] 6398.024467: kmem_alloc: size 176 flags 0x14 caller xlog_cil_push_work+0x9f
And the CIL fills up iclogs with pending changes. This picks up
the current tail from the AIL:
<...>-194402 [001] 6398.024497: xlog_iclog_get_space: dev 259:1 state XLOG_STATE_ACTIVE refcnt 1 offset 0 lsn 0x0 flags caller xlog_write+0x149
<...>-194402 [001] 6398.024498: xlog_iclog_switch: dev 259:1 state XLOG_STATE_ACTIVE refcnt 1 offset 0 lsn 0x700005408 flags caller xlog_state_get_iclog_space+0x37e
<...>-194402 [001] 6398.024521: xlog_iclog_release: dev 259:1 state XLOG_STATE_WANT_SYNC refcnt 1 offset 32256 lsn 0x700005408 flags caller xlog_write+0x5f9
<...>-194402 [001] 6398.024522: xfs_log_assign_tail_lsn: dev 259:1 new tail lsn 7/21000, old lsn 7/20344, last sync 7/21448
And it moves the tail of the log to 7/21000 from 7/20344. This
*moves the tail of the log beyond the ICREATE transaction* that was
at 7/20344 and pinned by the inode cluster buffer that was cancelled
above.
....
godown-213341 [008] 6398.027005: xfs_force_shutdown: dev 259:1 tag logerror flags log_io|force_umount file fs/xfs/xfs_fsops.c line_num 500
godown-213341 [008] 6398.027022: console: [ 6397.915406] pmem1: writeback error on inode 12621949, offset 1019904, sector 12968096
godown-213341 [008] 6398.030551: console: [ 6397.919546] XFS (pmem1): Log I/O Error (0x6) detected at xfs_fs_goingdown+0xa3/0xf0 (fs/
And finally the log itself is now shutdown, stopping all further
writes to the log. But this is too late to prevent the corruption
that moving the tail of the log forwards after we start cancelling
writeback causes.
The fundamental problem here is that we are using the wrong shutdown
checks for log items. We've long conflated mount shutdown with log
shutdown state, and I started separating that recently with the
atomic shutdown state changes in commit b36d4651e165 ("xfs: make
forced shutdown processing atomic"). The changes in that commit
series are directly responsible for being able to diagnose this
issue because it clearly separated mount shutdown from log shutdown.
Essentially, once we start cancelling writeback of log items and
removing them from the AIL because the filesystem is shut down, we
*cannot* update the journal because we may have cancelled the items
that pin the tail of the log. That moves the tail of the log
forwards without having written the metadata back, hence we have
corrupt in memory state and writing to the journal propagates that
to the on-disk state.
What commit b36d4651e165 makes clear is that log item state needs to
change relative to log shutdown, not mount shutdown. IOWs, anything
that aborts metadata writeback needs to check log shutdown state
because log items directly affect log consistency. Having them check
mount shutdown state introduces the above race condition where we
cancel metadata writeback before the log shuts down.
To fix this, this patch works through all log items and converts
shutdown checks to use xlog_is_shutdown() rather than
xfs_is_shutdown(), so that we don't start aborting metadata
writeback before we shut off journal writes.
AFAICT, this race condition is a zero day IO error handling bug in
XFS that dates back to the introduction of XLOG_IO_ERROR,
XLOG_STATE_IOERROR and XFS_FORCED_SHUTDOWN back in January 1997.
Signed-off-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Darrick J. Wong <djwong@kernel.org>
Signed-off-by: Darrick J. Wong <djwong@kernel.org>
2022-03-17 16:09:13 +00:00
|
|
|
* down the journal.
|
2020-05-06 20:25:21 +00:00
|
|
|
*/
|
|
|
|
if (write_fail) {
|
xfs: xfs_is_shutdown vs xlog_is_shutdown cage fight
I've been chasing a recent resurgence in generic/388 recovery
failure and/or corruption events. The events have largely been
uninitialised inode chunks being tripped over in log recovery
such as:
XFS (pmem1): User initiated shutdown received.
pmem1: writeback error on inode 12621949, offset 1019904, sector 12968096
XFS (pmem1): Log I/O Error (0x6) detected at xfs_fs_goingdown+0xa3/0xf0 (fs/xfs/xfs_fsops.c:500). Shutting down filesystem.
XFS (pmem1): Please unmount the filesystem and rectify the problem(s)
XFS (pmem1): Unmounting Filesystem
XFS (pmem1): Mounting V5 Filesystem
XFS (pmem1): Starting recovery (logdev: internal)
XFS (pmem1): bad inode magic/vsn daddr 8723584 #0 (magic=1818)
XFS (pmem1): Metadata corruption detected at xfs_inode_buf_verify+0x180/0x190, xfs_inode block 0x851c80 xfs_inode_buf_verify
XFS (pmem1): Unmount and run xfs_repair
XFS (pmem1): First 128 bytes of corrupted metadata buffer:
00000000: 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 ................
00000010: 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 ................
00000020: 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 ................
00000030: 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 ................
00000040: 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 ................
00000050: 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 ................
00000060: 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 ................
00000070: 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 ................
XFS (pmem1): metadata I/O error in "xlog_recover_items_pass2+0x52/0xc0" at daddr 0x851c80 len 32 error 117
XFS (pmem1): log mount/recovery failed: error -117
XFS (pmem1): log mount failed
There have been isolated random other issues, too - xfs_repair fails
because it finds some corruption in symlink blocks, rmap
inconsistencies, etc - but they are nowhere near as common as the
uninitialised inode chunk failure.
The problem has clearly happened at runtime before recovery has run;
I can see the ICREATE log item in the log shortly before the
actively recovered range of the log. This means the ICREATE was
definitely created and written to the log, but for some reason the
tail of the log has been moved past the ordered buffer log item that
tracks INODE_ALLOC buffers and, supposedly, prevents the tail of the
log moving past the ICREATE log item before the inode chunk buffer
is written to disk.
Tracing the fsstress processes that are running when the filesystem
shut down immediately pin-pointed the problem:
user shutdown marks xfs_mount as shutdown
godown-213341 [008] 6398.022871: console: [ 6397.915392] XFS (pmem1): User initiated shutdown received.
.....
aild tries to push ordered inode cluster buffer
xfsaild/pmem1-213314 [001] 6398.022974: xfs_buf_trylock: dev 259:1 daddr 0x851c80 bbcount 0x20 hold 16 pincount 0 lock 0 flags DONE|INODES|PAGES caller xfs_inode_item_push+0x8e
xfsaild/pmem1-213314 [001] 6398.022976: xfs_ilock_nowait: dev 259:1 ino 0x851c80 flags ILOCK_SHARED caller xfs_iflush_cluster+0xae
xfs_iflush_cluster() checks xfs_is_shutdown(), returns true,
calls xfs_iflush_abort() to kill writeback of the inode.
Inode is removed from AIL, drops cluster buffer reference.
xfsaild/pmem1-213314 [001] 6398.022977: xfs_ail_delete: dev 259:1 lip 0xffff88880247ed80 old lsn 7/20344 new lsn 7/21000 type XFS_LI_INODE flags IN_AIL
xfsaild/pmem1-213314 [001] 6398.022978: xfs_buf_rele: dev 259:1 daddr 0x851c80 bbcount 0x20 hold 17 pincount 0 lock 0 flags DONE|INODES|PAGES caller xfs_iflush_abort+0xd7
.....
All inodes on cluster buffer are aborted, then the cluster buffer
itself is aborted and removed from the AIL *without writeback*:
xfsaild/pmem1-213314 [001] 6398.023011: xfs_buf_error_relse: dev 259:1 daddr 0x851c80 bbcount 0x20 hold 2 pincount 0 lock 0 flags ASYNC|DONE|STALE|INODES|PAGES caller xfs_buf_ioend_fail+0x33
xfsaild/pmem1-213314 [001] 6398.023012: xfs_ail_delete: dev 259:1 lip 0xffff8888053efde8 old lsn 7/20344 new lsn 7/20344 type XFS_LI_BUF flags IN_AIL
The inode buffer was at 7/20344 when it was removed from the AIL.
xfsaild/pmem1-213314 [001] 6398.023012: xfs_buf_item_relse: dev 259:1 daddr 0x851c80 bbcount 0x20 hold 2 pincount 0 lock 0 flags ASYNC|DONE|STALE|INODES|PAGES caller xfs_buf_item_done+0x31
xfsaild/pmem1-213314 [001] 6398.023012: xfs_buf_rele: dev 259:1 daddr 0x851c80 bbcount 0x20 hold 2 pincount 0 lock 0 flags ASYNC|DONE|STALE|INODES|PAGES caller xfs_buf_item_relse+0x39
.....
Userspace is still running, doing stuff. an fsstress process runs
syncfs() or sync() and we end up in sync_fs_one_sb() which issues
a log force. This pushes on the CIL:
fsstress-213322 [001] 6398.024430: xfs_fs_sync_fs: dev 259:1 m_features 0x20000000019ff6e9 opstate (clean|shutdown|inodegc|blockgc) s_flags 0x70810000 caller sync_fs_one_sb+0x26
fsstress-213322 [001] 6398.024430: xfs_log_force: dev 259:1 lsn 0x0 caller xfs_fs_sync_fs+0x82
fsstress-213322 [001] 6398.024430: xfs_log_force: dev 259:1 lsn 0x5f caller xfs_log_force+0x7c
<...>-194402 [001] 6398.024467: kmem_alloc: size 176 flags 0x14 caller xlog_cil_push_work+0x9f
And the CIL fills up iclogs with pending changes. This picks up
the current tail from the AIL:
<...>-194402 [001] 6398.024497: xlog_iclog_get_space: dev 259:1 state XLOG_STATE_ACTIVE refcnt 1 offset 0 lsn 0x0 flags caller xlog_write+0x149
<...>-194402 [001] 6398.024498: xlog_iclog_switch: dev 259:1 state XLOG_STATE_ACTIVE refcnt 1 offset 0 lsn 0x700005408 flags caller xlog_state_get_iclog_space+0x37e
<...>-194402 [001] 6398.024521: xlog_iclog_release: dev 259:1 state XLOG_STATE_WANT_SYNC refcnt 1 offset 32256 lsn 0x700005408 flags caller xlog_write+0x5f9
<...>-194402 [001] 6398.024522: xfs_log_assign_tail_lsn: dev 259:1 new tail lsn 7/21000, old lsn 7/20344, last sync 7/21448
And it moves the tail of the log to 7/21000 from 7/20344. This
*moves the tail of the log beyond the ICREATE transaction* that was
at 7/20344 and pinned by the inode cluster buffer that was cancelled
above.
....
godown-213341 [008] 6398.027005: xfs_force_shutdown: dev 259:1 tag logerror flags log_io|force_umount file fs/xfs/xfs_fsops.c line_num 500
godown-213341 [008] 6398.027022: console: [ 6397.915406] pmem1: writeback error on inode 12621949, offset 1019904, sector 12968096
godown-213341 [008] 6398.030551: console: [ 6397.919546] XFS (pmem1): Log I/O Error (0x6) detected at xfs_fs_goingdown+0xa3/0xf0 (fs/
And finally the log itself is now shutdown, stopping all further
writes to the log. But this is too late to prevent the corruption
that moving the tail of the log forwards after we start cancelling
writeback causes.
The fundamental problem here is that we are using the wrong shutdown
checks for log items. We've long conflated mount shutdown with log
shutdown state, and I started separating that recently with the
atomic shutdown state changes in commit b36d4651e165 ("xfs: make
forced shutdown processing atomic"). The changes in that commit
series are directly responsible for being able to diagnose this
issue because it clearly separated mount shutdown from log shutdown.
Essentially, once we start cancelling writeback of log items and
removing them from the AIL because the filesystem is shut down, we
*cannot* update the journal because we may have cancelled the items
that pin the tail of the log. That moves the tail of the log
forwards without having written the metadata back, hence we have
corrupt in memory state and writing to the journal propagates that
to the on-disk state.
What commit b36d4651e165 makes clear is that log item state needs to
change relative to log shutdown, not mount shutdown. IOWs, anything
that aborts metadata writeback needs to check log shutdown state
because log items directly affect log consistency. Having them check
mount shutdown state introduces the above race condition where we
cancel metadata writeback before the log shuts down.
To fix this, this patch works through all log items and converts
shutdown checks to use xlog_is_shutdown() rather than
xfs_is_shutdown(), so that we don't start aborting metadata
writeback before we shut off journal writes.
AFAICT, this race condition is a zero day IO error handling bug in
XFS that dates back to the introduction of XLOG_IO_ERROR,
XLOG_STATE_IOERROR and XFS_FORCED_SHUTDOWN back in January 1997.
Signed-off-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Darrick J. Wong <djwong@kernel.org>
Signed-off-by: Darrick J. Wong <djwong@kernel.org>
2022-03-17 16:09:13 +00:00
|
|
|
ASSERT(xlog_is_shutdown(btp->bt_mount->m_log));
|
2020-05-06 20:25:21 +00:00
|
|
|
xfs_alert(btp->bt_mount,
|
|
|
|
"Please run xfs_repair to determine the extent of the problem.");
|
|
|
|
}
|
2013-08-28 00:18:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static enum lru_status
|
|
|
|
xfs_buftarg_isolate(
|
|
|
|
struct list_head *item,
|
2015-02-12 22:59:35 +00:00
|
|
|
struct list_lru_one *lru,
|
2013-08-28 00:18:05 +00:00
|
|
|
spinlock_t *lru_lock,
|
|
|
|
void *arg)
|
|
|
|
{
|
|
|
|
struct xfs_buf *bp = container_of(item, struct xfs_buf, b_lru);
|
|
|
|
struct list_head *dispose = arg;
|
|
|
|
|
2013-08-28 00:18:06 +00:00
|
|
|
/*
|
|
|
|
* we are inverting the lru lock/bp->b_lock here, so use a trylock.
|
|
|
|
* If we fail to get the lock, just skip it.
|
|
|
|
*/
|
|
|
|
if (!spin_trylock(&bp->b_lock))
|
|
|
|
return LRU_SKIP;
|
2013-08-28 00:18:05 +00:00
|
|
|
/*
|
|
|
|
* Decrement the b_lru_ref count unless the value is already
|
|
|
|
* zero. If the value is already zero, we need to reclaim the
|
|
|
|
* buffer, otherwise it gets another trip through the LRU.
|
|
|
|
*/
|
2018-03-07 01:07:44 +00:00
|
|
|
if (atomic_add_unless(&bp->b_lru_ref, -1, 0)) {
|
2013-08-28 00:18:06 +00:00
|
|
|
spin_unlock(&bp->b_lock);
|
2013-08-28 00:18:05 +00:00
|
|
|
return LRU_ROTATE;
|
2013-08-28 00:18:06 +00:00
|
|
|
}
|
2013-08-28 00:18:05 +00:00
|
|
|
|
2013-08-28 00:18:06 +00:00
|
|
|
bp->b_state |= XFS_BSTATE_DISPOSE;
|
2015-02-12 22:59:35 +00:00
|
|
|
list_lru_isolate_move(lru, item, dispose);
|
2013-08-28 00:18:06 +00:00
|
|
|
spin_unlock(&bp->b_lock);
|
2013-08-28 00:18:05 +00:00
|
|
|
return LRU_REMOVED;
|
|
|
|
}
|
|
|
|
|
2013-08-28 00:18:06 +00:00
|
|
|
static unsigned long
|
2013-08-28 00:18:05 +00:00
|
|
|
xfs_buftarg_shrink_scan(
|
2010-11-30 06:27:57 +00:00
|
|
|
struct shrinker *shrink,
|
2011-05-25 00:12:27 +00:00
|
|
|
struct shrink_control *sc)
|
2006-01-11 04:37:58 +00:00
|
|
|
{
|
2023-09-11 09:44:33 +00:00
|
|
|
struct xfs_buftarg *btp = shrink->private_data;
|
2010-12-02 05:30:55 +00:00
|
|
|
LIST_HEAD(dispose);
|
2013-08-28 00:18:06 +00:00
|
|
|
unsigned long freed;
|
2010-12-02 05:30:55 +00:00
|
|
|
|
list_lru: introduce list_lru_shrink_{count,walk}
Kmem accounting of memcg is unusable now, because it lacks slab shrinker
support. That means when we hit the limit we will get ENOMEM w/o any
chance to recover. What we should do then is to call shrink_slab, which
would reclaim old inode/dentry caches from this cgroup. This is what
this patch set is intended to do.
Basically, it does two things. First, it introduces the notion of
per-memcg slab shrinker. A shrinker that wants to reclaim objects per
cgroup should mark itself as SHRINKER_MEMCG_AWARE. Then it will be
passed the memory cgroup to scan from in shrink_control->memcg. For
such shrinkers shrink_slab iterates over the whole cgroup subtree under
the target cgroup and calls the shrinker for each kmem-active memory
cgroup.
Secondly, this patch set makes the list_lru structure per-memcg. It's
done transparently to list_lru users - everything they have to do is to
tell list_lru_init that they want memcg-aware list_lru. Then the
list_lru will automatically distribute objects among per-memcg lists
basing on which cgroup the object is accounted to. This way to make FS
shrinkers (icache, dcache) memcg-aware we only need to make them use
memcg-aware list_lru, and this is what this patch set does.
As before, this patch set only enables per-memcg kmem reclaim when the
pressure goes from memory.limit, not from memory.kmem.limit. Handling
memory.kmem.limit is going to be tricky due to GFP_NOFS allocations, and
it is still unclear whether we will have this knob in the unified
hierarchy.
This patch (of 9):
NUMA aware slab shrinkers use the list_lru structure to distribute
objects coming from different NUMA nodes to different lists. Whenever
such a shrinker needs to count or scan objects from a particular node,
it issues commands like this:
count = list_lru_count_node(lru, sc->nid);
freed = list_lru_walk_node(lru, sc->nid, isolate_func,
isolate_arg, &sc->nr_to_scan);
where sc is an instance of the shrink_control structure passed to it
from vmscan.
To simplify this, let's add special list_lru functions to be used by
shrinkers, list_lru_shrink_count() and list_lru_shrink_walk(), which
consolidate the nid and nr_to_scan arguments in the shrink_control
structure.
This will also allow us to avoid patching shrinkers that use list_lru
when we make shrink_slab() per-memcg - all we will have to do is extend
the shrink_control structure to include the target memcg and make
list_lru_shrink_{count,walk} handle this appropriately.
Signed-off-by: Vladimir Davydov <vdavydov@parallels.com>
Suggested-by: Dave Chinner <david@fromorbit.com>
Cc: Johannes Weiner <hannes@cmpxchg.org>
Cc: Michal Hocko <mhocko@suse.cz>
Cc: Greg Thelen <gthelen@google.com>
Cc: Glauber Costa <glommer@gmail.com>
Cc: Alexander Viro <viro@zeniv.linux.org.uk>
Cc: Christoph Lameter <cl@linux.com>
Cc: Pekka Enberg <penberg@kernel.org>
Cc: David Rientjes <rientjes@google.com>
Cc: Joonsoo Kim <iamjoonsoo.kim@lge.com>
Cc: Tejun Heo <tj@kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2015-02-12 22:58:47 +00:00
|
|
|
freed = list_lru_shrink_walk(&btp->bt_lru, sc,
|
|
|
|
xfs_buftarg_isolate, &dispose);
|
2010-12-02 05:30:55 +00:00
|
|
|
|
|
|
|
while (!list_empty(&dispose)) {
|
2013-08-28 00:18:05 +00:00
|
|
|
struct xfs_buf *bp;
|
2010-12-02 05:30:55 +00:00
|
|
|
bp = list_first_entry(&dispose, struct xfs_buf, b_lru);
|
|
|
|
list_del_init(&bp->b_lru);
|
|
|
|
xfs_buf_rele(bp);
|
|
|
|
}
|
|
|
|
|
2013-08-28 00:18:05 +00:00
|
|
|
return freed;
|
|
|
|
}
|
|
|
|
|
2013-08-28 00:18:06 +00:00
|
|
|
static unsigned long
|
2013-08-28 00:18:05 +00:00
|
|
|
xfs_buftarg_shrink_count(
|
|
|
|
struct shrinker *shrink,
|
|
|
|
struct shrink_control *sc)
|
|
|
|
{
|
2023-09-11 09:44:33 +00:00
|
|
|
struct xfs_buftarg *btp = shrink->private_data;
|
list_lru: introduce list_lru_shrink_{count,walk}
Kmem accounting of memcg is unusable now, because it lacks slab shrinker
support. That means when we hit the limit we will get ENOMEM w/o any
chance to recover. What we should do then is to call shrink_slab, which
would reclaim old inode/dentry caches from this cgroup. This is what
this patch set is intended to do.
Basically, it does two things. First, it introduces the notion of
per-memcg slab shrinker. A shrinker that wants to reclaim objects per
cgroup should mark itself as SHRINKER_MEMCG_AWARE. Then it will be
passed the memory cgroup to scan from in shrink_control->memcg. For
such shrinkers shrink_slab iterates over the whole cgroup subtree under
the target cgroup and calls the shrinker for each kmem-active memory
cgroup.
Secondly, this patch set makes the list_lru structure per-memcg. It's
done transparently to list_lru users - everything they have to do is to
tell list_lru_init that they want memcg-aware list_lru. Then the
list_lru will automatically distribute objects among per-memcg lists
basing on which cgroup the object is accounted to. This way to make FS
shrinkers (icache, dcache) memcg-aware we only need to make them use
memcg-aware list_lru, and this is what this patch set does.
As before, this patch set only enables per-memcg kmem reclaim when the
pressure goes from memory.limit, not from memory.kmem.limit. Handling
memory.kmem.limit is going to be tricky due to GFP_NOFS allocations, and
it is still unclear whether we will have this knob in the unified
hierarchy.
This patch (of 9):
NUMA aware slab shrinkers use the list_lru structure to distribute
objects coming from different NUMA nodes to different lists. Whenever
such a shrinker needs to count or scan objects from a particular node,
it issues commands like this:
count = list_lru_count_node(lru, sc->nid);
freed = list_lru_walk_node(lru, sc->nid, isolate_func,
isolate_arg, &sc->nr_to_scan);
where sc is an instance of the shrink_control structure passed to it
from vmscan.
To simplify this, let's add special list_lru functions to be used by
shrinkers, list_lru_shrink_count() and list_lru_shrink_walk(), which
consolidate the nid and nr_to_scan arguments in the shrink_control
structure.
This will also allow us to avoid patching shrinkers that use list_lru
when we make shrink_slab() per-memcg - all we will have to do is extend
the shrink_control structure to include the target memcg and make
list_lru_shrink_{count,walk} handle this appropriately.
Signed-off-by: Vladimir Davydov <vdavydov@parallels.com>
Suggested-by: Dave Chinner <david@fromorbit.com>
Cc: Johannes Weiner <hannes@cmpxchg.org>
Cc: Michal Hocko <mhocko@suse.cz>
Cc: Greg Thelen <gthelen@google.com>
Cc: Glauber Costa <glommer@gmail.com>
Cc: Alexander Viro <viro@zeniv.linux.org.uk>
Cc: Christoph Lameter <cl@linux.com>
Cc: Pekka Enberg <penberg@kernel.org>
Cc: David Rientjes <rientjes@google.com>
Cc: Joonsoo Kim <iamjoonsoo.kim@lge.com>
Cc: Tejun Heo <tj@kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2015-02-12 22:58:47 +00:00
|
|
|
return list_lru_shrink_count(&btp->bt_lru, sc);
|
2006-01-11 04:37:58 +00:00
|
|
|
}
|
|
|
|
|
2005-04-16 22:20:36 +00:00
|
|
|
void
|
2024-02-22 20:43:21 +00:00
|
|
|
xfs_destroy_buftarg(
|
2009-03-03 19:48:37 +00:00
|
|
|
struct xfs_buftarg *btp)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2023-09-11 09:44:33 +00:00
|
|
|
shrinker_free(btp->bt_shrinker);
|
xfs: track and serialize in-flight async buffers against unmount
Newly allocated XFS metadata buffers are added to the LRU once the hold
count is released, which typically occurs after I/O completion. There is
no other mechanism at current that tracks the existence or I/O state of
a new buffer. Further, readahead I/O tends to be submitted
asynchronously by nature, which means the I/O can remain in flight and
actually complete long after the calling context is gone. This means
that file descriptors or any other holds on the filesystem can be
released, allowing the filesystem to be unmounted while I/O is still in
flight. When I/O completion occurs, core data structures may have been
freed, causing completion to run into invalid memory accesses and likely
to panic.
This problem is reproduced on XFS via directory readahead. A filesystem
is mounted, a directory is opened/closed and the filesystem immediately
unmounted. The open/close cycle triggers a directory readahead that if
delayed long enough, runs buffer I/O completion after the unmount has
completed.
To address this problem, add a mechanism to track all in-flight,
asynchronous buffers using per-cpu counters in the buftarg. The buffer
is accounted on the first I/O submission after the current reference is
acquired and unaccounted once the buffer is returned to the LRU or
freed. Update xfs_wait_buftarg() to wait on all in-flight I/O before
walking the LRU list. Once in-flight I/O has completed and the workqueue
has drained, all new buffers should have been released onto the LRU.
Signed-off-by: Brian Foster <bfoster@redhat.com>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Signed-off-by: Dave Chinner <david@fromorbit.com>
2016-07-20 01:15:28 +00:00
|
|
|
ASSERT(percpu_counter_sum(&btp->bt_io_count) == 0);
|
|
|
|
percpu_counter_destroy(&btp->bt_io_count);
|
2013-08-28 00:18:18 +00:00
|
|
|
list_lru_destroy(&btp->bt_lru);
|
2024-02-22 20:43:21 +00:00
|
|
|
}
|
2010-11-30 06:27:57 +00:00
|
|
|
|
2024-02-22 20:43:21 +00:00
|
|
|
void
|
|
|
|
xfs_free_buftarg(
|
|
|
|
struct xfs_buftarg *btp)
|
|
|
|
{
|
|
|
|
xfs_destroy_buftarg(btp);
|
2022-06-03 05:37:30 +00:00
|
|
|
fs_put_dax(btp->bt_daxdev, btp->bt_mount);
|
2023-08-09 22:05:37 +00:00
|
|
|
/* the main block device is closed by kill_block_super */
|
2023-09-27 09:34:34 +00:00
|
|
|
if (btp->bt_bdev != btp->bt_mount->m_super->s_bdev)
|
2024-03-26 12:47:22 +00:00
|
|
|
bdev_fput(btp->bt_bdev_file);
|
2024-01-15 22:59:43 +00:00
|
|
|
kfree(btp);
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
2013-11-13 20:53:45 +00:00
|
|
|
int
|
|
|
|
xfs_setsize_buftarg(
|
2024-02-22 20:42:44 +00:00
|
|
|
struct xfs_buftarg *btp,
|
2013-11-13 20:53:45 +00:00
|
|
|
unsigned int sectorsize)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
xfs: allow logical-sector sized O_DIRECT
Some time ago, mkfs.xfs started picking the storage physical
sector size as the default filesystem "sector size" in order
to avoid RMW costs incurred by doing IOs at logical sector
size alignments.
However, this means that for a filesystem made with i.e.
a 4k sector size on an "advanced format" 4k/512 disk,
512-byte direct IOs are no longer allowed. This means
that XFS has essentially turned this AF drive into a hard
4K device, from the filesystem on up.
XFS's mkfs-specified "sector size" is really just controlling
the minimum size & alignment of filesystem metadata.
There is no real need to tightly couple XFS's minimal
metadata size to the minimum allowed direct IO size;
XFS can continue doing metadata in optimal sizes, but
still allow smaller DIOs for apps which issue them,
for whatever reason.
This patch adds a new field to the xfs_buftarg, so that
we now track 2 sizes:
1) The metadata sector size, which is the minimum unit and
alignment of IO which will be performed by metadata operations.
2) The device logical sector size
The first is used internally by the file system for metadata
alignment and IOs.
The second is used for the minimum allowed direct IO alignment.
This has passed xfstests on filesystems made with 4k sectors,
including when run under the patch I sent to ignore
XFS_IOC_DIOINFO, and issue 512 DIOs anyway. I also directly
tested end of block behavior on preallocated, sparse, and
existing files when we do a 512 IO into a 4k file on a
4k-sector filesystem, to be sure there were no unexpected
behaviors.
Signed-off-by: Eric Sandeen <sandeen@redhat.com>
Reviewed-by: Brian Foster <bfoster@redhat.com>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Signed-off-by: Ben Myers <bpm@sgi.com>
2014-01-21 22:46:23 +00:00
|
|
|
/* Set up metadata sector size info */
|
2014-01-21 22:45:52 +00:00
|
|
|
btp->bt_meta_sectorsize = sectorsize;
|
|
|
|
btp->bt_meta_sectormask = sectorsize - 1;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2024-04-18 04:34:31 +00:00
|
|
|
if (set_blocksize(btp->bt_bdev_file, sectorsize)) {
|
2011-03-06 23:00:35 +00:00
|
|
|
xfs_warn(btp->bt_mount,
|
2015-04-13 12:31:37 +00:00
|
|
|
"Cannot set_blocksize to %u on device %pg",
|
|
|
|
sectorsize, btp->bt_bdev);
|
2014-06-25 04:58:08 +00:00
|
|
|
return -EINVAL;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2024-02-22 20:43:21 +00:00
|
|
|
int
|
|
|
|
xfs_init_buftarg(
|
|
|
|
struct xfs_buftarg *btp,
|
|
|
|
size_t logical_sectorsize,
|
|
|
|
const char *descr)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2024-02-22 20:43:21 +00:00
|
|
|
/* Set up device logical sector size mask */
|
|
|
|
btp->bt_logical_sectorsize = logical_sectorsize;
|
|
|
|
btp->bt_logical_sectormask = logical_sectorsize - 1;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Buffer IO error rate limiting. Limit it to no more than 10 messages
|
|
|
|
* per 30 seconds so as to not spam logs too much on repeated errors.
|
|
|
|
*/
|
|
|
|
ratelimit_state_init(&btp->bt_ioerror_rl, 30 * HZ,
|
|
|
|
DEFAULT_RATELIMIT_BURST);
|
|
|
|
|
|
|
|
if (list_lru_init(&btp->bt_lru))
|
|
|
|
return -ENOMEM;
|
|
|
|
if (percpu_counter_init(&btp->bt_io_count, 0, GFP_KERNEL))
|
|
|
|
goto out_destroy_lru;
|
|
|
|
|
|
|
|
btp->bt_shrinker =
|
|
|
|
shrinker_alloc(SHRINKER_NUMA_AWARE, "xfs-buf:%s", descr);
|
|
|
|
if (!btp->bt_shrinker)
|
|
|
|
goto out_destroy_io_count;
|
|
|
|
btp->bt_shrinker->count_objects = xfs_buftarg_shrink_count;
|
|
|
|
btp->bt_shrinker->scan_objects = xfs_buftarg_shrink_scan;
|
|
|
|
btp->bt_shrinker->private_data = btp;
|
|
|
|
shrinker_register(btp->bt_shrinker);
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
out_destroy_io_count:
|
|
|
|
percpu_counter_destroy(&btp->bt_io_count);
|
|
|
|
out_destroy_lru:
|
|
|
|
list_lru_destroy(&btp->bt_lru);
|
|
|
|
return -ENOMEM;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
2021-11-29 10:21:55 +00:00
|
|
|
struct xfs_buftarg *
|
2005-04-16 22:20:36 +00:00
|
|
|
xfs_alloc_buftarg(
|
2010-09-22 00:47:20 +00:00
|
|
|
struct xfs_mount *mp,
|
2024-01-23 13:26:24 +00:00
|
|
|
struct file *bdev_file)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2024-02-22 20:42:44 +00:00
|
|
|
struct xfs_buftarg *btp;
|
2022-06-03 05:37:30 +00:00
|
|
|
const struct dax_holder_operations *ops = NULL;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2022-06-03 05:37:30 +00:00
|
|
|
#if defined(CONFIG_FS_DAX) && defined(CONFIG_MEMORY_FAILURE)
|
|
|
|
ops = &xfs_dax_holder_operations;
|
|
|
|
#endif
|
2024-01-15 22:59:49 +00:00
|
|
|
btp = kzalloc(sizeof(*btp), GFP_KERNEL | __GFP_NOFAIL);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2010-09-22 00:47:20 +00:00
|
|
|
btp->bt_mount = mp;
|
2024-01-23 13:26:24 +00:00
|
|
|
btp->bt_bdev_file = bdev_file;
|
|
|
|
btp->bt_bdev = file_bdev(bdev_file);
|
|
|
|
btp->bt_dev = btp->bt_bdev->bd_dev;
|
2023-09-27 09:34:34 +00:00
|
|
|
btp->bt_daxdev = fs_dax_get_by_bdev(btp->bt_bdev, &btp->bt_dax_part_off,
|
2022-06-03 05:37:30 +00:00
|
|
|
mp, ops);
|
2011-03-25 22:16:45 +00:00
|
|
|
|
2020-05-06 20:25:21 +00:00
|
|
|
/*
|
2024-02-22 20:42:45 +00:00
|
|
|
* When allocating the buftargs we have not yet read the super block and
|
|
|
|
* thus don't know the file system sector size yet.
|
2020-05-06 20:25:21 +00:00
|
|
|
*/
|
2024-02-22 20:42:45 +00:00
|
|
|
if (xfs_setsize_buftarg(btp, bdev_logical_block_size(btp->bt_bdev)))
|
2017-11-23 16:13:40 +00:00
|
|
|
goto error_free;
|
2024-02-22 20:43:21 +00:00
|
|
|
if (xfs_init_buftarg(btp, bdev_logical_block_size(btp->bt_bdev),
|
|
|
|
mp->m_super->s_id))
|
2017-11-23 16:13:40 +00:00
|
|
|
goto error_free;
|
2013-08-28 00:18:18 +00:00
|
|
|
|
2005-04-16 22:20:36 +00:00
|
|
|
return btp;
|
|
|
|
|
2017-11-23 16:13:40 +00:00
|
|
|
error_free:
|
2024-01-15 22:59:43 +00:00
|
|
|
kfree(btp);
|
2005-04-16 22:20:36 +00:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
xfs: force all buffers to be written during btree bulk load
While stress-testing online repair of btrees, I noticed periodic
assertion failures from the buffer cache about buffers with incorrect
DELWRI_Q state. Looking further, I observed this race between the AIL
trying to write out a btree block and repair zapping a btree block after
the fact:
AIL: Repair0:
pin buffer X
delwri_queue:
set DELWRI_Q
add to delwri list
stale buf X:
clear DELWRI_Q
does not clear b_list
free space X
commit
delwri_submit # oops
Worse yet, I discovered that running the same repair over and over in a
tight loop can result in a second race that cause data integrity
problems with the repair:
AIL: Repair0: Repair1:
pin buffer X
delwri_queue:
set DELWRI_Q
add to delwri list
stale buf X:
clear DELWRI_Q
does not clear b_list
free space X
commit
find free space X
get buffer
rewrite buffer
delwri_queue:
set DELWRI_Q
already on a list, do not add
commit
BAD: committed tree root before all blocks written
delwri_submit # too late now
I traced this to my own misunderstanding of how the delwri lists work,
particularly with regards to the AIL's buffer list. If a buffer is
logged and committed, the buffer can end up on that AIL buffer list. If
btree repairs are run twice in rapid succession, it's possible that the
first repair will invalidate the buffer and free it before the next time
the AIL wakes up. Marking the buffer stale clears DELWRI_Q from the
buffer state without removing the buffer from its delwri list. The
buffer doesn't know which list it's on, so it cannot know which lock to
take to protect the list for a removal.
If the second repair allocates the same block, it will then recycle the
buffer to start writing the new btree block. Meanwhile, if the AIL
wakes up and walks the buffer list, it will ignore the buffer because it
can't lock it, and go back to sleep.
When the second repair calls delwri_queue to put the buffer on the
list of buffers to write before committing the new btree, it will set
DELWRI_Q again, but since the buffer hasn't been removed from the AIL's
buffer list, it won't add it to the bulkload buffer's list.
This is incorrect, because the bulkload caller relies on delwri_submit
to ensure that all the buffers have been sent to disk /before/
committing the new btree root pointer. This ordering requirement is
required for data consistency.
Worse, the AIL won't clear DELWRI_Q from the buffer when it does finally
drop it, so the next thread to walk through the btree will trip over a
debug assertion on that flag.
To fix this, create a new function that waits for the buffer to be
removed from any other delwri lists before adding the buffer to the
caller's delwri list. By waiting for the buffer to clear both the
delwri list and any potential delwri wait list, we can be sure that
repair will initiate writes of all buffers and report all write errors
back to userspace instead of committing the new structure.
Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Christoph Hellwig <hch@lst.de>
2023-12-15 18:03:27 +00:00
|
|
|
static inline void
|
|
|
|
xfs_buf_list_del(
|
|
|
|
struct xfs_buf *bp)
|
|
|
|
{
|
|
|
|
list_del_init(&bp->b_list);
|
|
|
|
wake_up_var(&bp->b_list);
|
|
|
|
}
|
|
|
|
|
2017-04-21 19:40:44 +00:00
|
|
|
/*
|
|
|
|
* Cancel a delayed write list.
|
|
|
|
*
|
|
|
|
* Remove each buffer from the list, clear the delwri queue flag and drop the
|
|
|
|
* associated buffer reference.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
xfs_buf_delwri_cancel(
|
|
|
|
struct list_head *list)
|
|
|
|
{
|
|
|
|
struct xfs_buf *bp;
|
|
|
|
|
|
|
|
while (!list_empty(list)) {
|
|
|
|
bp = list_first_entry(list, struct xfs_buf, b_list);
|
|
|
|
|
|
|
|
xfs_buf_lock(bp);
|
|
|
|
bp->b_flags &= ~_XBF_DELWRI_Q;
|
xfs: force all buffers to be written during btree bulk load
While stress-testing online repair of btrees, I noticed periodic
assertion failures from the buffer cache about buffers with incorrect
DELWRI_Q state. Looking further, I observed this race between the AIL
trying to write out a btree block and repair zapping a btree block after
the fact:
AIL: Repair0:
pin buffer X
delwri_queue:
set DELWRI_Q
add to delwri list
stale buf X:
clear DELWRI_Q
does not clear b_list
free space X
commit
delwri_submit # oops
Worse yet, I discovered that running the same repair over and over in a
tight loop can result in a second race that cause data integrity
problems with the repair:
AIL: Repair0: Repair1:
pin buffer X
delwri_queue:
set DELWRI_Q
add to delwri list
stale buf X:
clear DELWRI_Q
does not clear b_list
free space X
commit
find free space X
get buffer
rewrite buffer
delwri_queue:
set DELWRI_Q
already on a list, do not add
commit
BAD: committed tree root before all blocks written
delwri_submit # too late now
I traced this to my own misunderstanding of how the delwri lists work,
particularly with regards to the AIL's buffer list. If a buffer is
logged and committed, the buffer can end up on that AIL buffer list. If
btree repairs are run twice in rapid succession, it's possible that the
first repair will invalidate the buffer and free it before the next time
the AIL wakes up. Marking the buffer stale clears DELWRI_Q from the
buffer state without removing the buffer from its delwri list. The
buffer doesn't know which list it's on, so it cannot know which lock to
take to protect the list for a removal.
If the second repair allocates the same block, it will then recycle the
buffer to start writing the new btree block. Meanwhile, if the AIL
wakes up and walks the buffer list, it will ignore the buffer because it
can't lock it, and go back to sleep.
When the second repair calls delwri_queue to put the buffer on the
list of buffers to write before committing the new btree, it will set
DELWRI_Q again, but since the buffer hasn't been removed from the AIL's
buffer list, it won't add it to the bulkload buffer's list.
This is incorrect, because the bulkload caller relies on delwri_submit
to ensure that all the buffers have been sent to disk /before/
committing the new btree root pointer. This ordering requirement is
required for data consistency.
Worse, the AIL won't clear DELWRI_Q from the buffer when it does finally
drop it, so the next thread to walk through the btree will trip over a
debug assertion on that flag.
To fix this, create a new function that waits for the buffer to be
removed from any other delwri lists before adding the buffer to the
caller's delwri list. By waiting for the buffer to clear both the
delwri list and any potential delwri wait list, we can be sure that
repair will initiate writes of all buffers and report all write errors
back to userspace instead of committing the new structure.
Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Christoph Hellwig <hch@lst.de>
2023-12-15 18:03:27 +00:00
|
|
|
xfs_buf_list_del(bp);
|
2017-04-21 19:40:44 +00:00
|
|
|
xfs_buf_relse(bp);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2005-04-16 22:20:36 +00:00
|
|
|
/*
|
xfs: on-stack delayed write buffer lists
Queue delwri buffers on a local on-stack list instead of a per-buftarg one,
and write back the buffers per-process instead of by waking up xfsbufd.
This is now easily doable given that we have very few places left that write
delwri buffers:
- log recovery:
Only done at mount time, and already forcing out the buffers
synchronously using xfs_flush_buftarg
- quotacheck:
Same story.
- dquot reclaim:
Writes out dirty dquots on the LRU under memory pressure. We might
want to look into doing more of this via xfsaild, but it's already
more optimal than the synchronous inode reclaim that writes each
buffer synchronously.
- xfsaild:
This is the main beneficiary of the change. By keeping a local list
of buffers to write we reduce latency of writing out buffers, and
more importably we can remove all the delwri list promotions which
were hitting the buffer cache hard under sustained metadata loads.
The implementation is very straight forward - xfs_buf_delwri_queue now gets
a new list_head pointer that it adds the delwri buffers to, and all callers
need to eventually submit the list using xfs_buf_delwi_submit or
xfs_buf_delwi_submit_nowait. Buffers that already are on a delwri list are
skipped in xfs_buf_delwri_queue, assuming they already are on another delwri
list. The biggest change to pass down the buffer list was done to the AIL
pushing. Now that we operate on buffers the trylock, push and pushbuf log
item methods are merged into a single push routine, which tries to lock the
item, and if possible add the buffer that needs writeback to the buffer list.
This leads to much simpler code than the previous split but requires the
individual IOP_PUSH instances to unlock and reacquire the AIL around calls
to blocking routines.
Given that xfsailds now also handle writing out buffers, the conditions for
log forcing and the sleep times needed some small changes. The most
important one is that we consider an AIL busy as long we still have buffers
to push, and the other one is that we do increment the pushed LSN for
buffers that are under flushing at this moment, but still count them towards
the stuck items for restart purposes. Without this we could hammer on stuck
items without ever forcing the log and not make progress under heavy random
delete workloads on fast flash storage devices.
[ Dave Chinner:
- rebase on previous patches.
- improved comments for XBF_DELWRI_Q handling
- fix XBF_ASYNC handling in queue submission (test 106 failure)
- rename delwri submit function buffer list parameters for clarity
- xfs_efd_item_push() should return XFS_ITEM_PINNED ]
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Mark Tinguely <tinguely@sgi.com>
Signed-off-by: Ben Myers <bpm@sgi.com>
2012-04-23 05:58:39 +00:00
|
|
|
* Add a buffer to the delayed write list.
|
|
|
|
*
|
|
|
|
* This queues a buffer for writeout if it hasn't already been. Note that
|
|
|
|
* neither this routine nor the buffer list submission functions perform
|
|
|
|
* any internal synchronization. It is expected that the lists are thread-local
|
|
|
|
* to the callers.
|
|
|
|
*
|
|
|
|
* Returns true if we queued up the buffer, or false if it already had
|
|
|
|
* been on the buffer list.
|
2005-04-16 22:20:36 +00:00
|
|
|
*/
|
xfs: on-stack delayed write buffer lists
Queue delwri buffers on a local on-stack list instead of a per-buftarg one,
and write back the buffers per-process instead of by waking up xfsbufd.
This is now easily doable given that we have very few places left that write
delwri buffers:
- log recovery:
Only done at mount time, and already forcing out the buffers
synchronously using xfs_flush_buftarg
- quotacheck:
Same story.
- dquot reclaim:
Writes out dirty dquots on the LRU under memory pressure. We might
want to look into doing more of this via xfsaild, but it's already
more optimal than the synchronous inode reclaim that writes each
buffer synchronously.
- xfsaild:
This is the main beneficiary of the change. By keeping a local list
of buffers to write we reduce latency of writing out buffers, and
more importably we can remove all the delwri list promotions which
were hitting the buffer cache hard under sustained metadata loads.
The implementation is very straight forward - xfs_buf_delwri_queue now gets
a new list_head pointer that it adds the delwri buffers to, and all callers
need to eventually submit the list using xfs_buf_delwi_submit or
xfs_buf_delwi_submit_nowait. Buffers that already are on a delwri list are
skipped in xfs_buf_delwri_queue, assuming they already are on another delwri
list. The biggest change to pass down the buffer list was done to the AIL
pushing. Now that we operate on buffers the trylock, push and pushbuf log
item methods are merged into a single push routine, which tries to lock the
item, and if possible add the buffer that needs writeback to the buffer list.
This leads to much simpler code than the previous split but requires the
individual IOP_PUSH instances to unlock and reacquire the AIL around calls
to blocking routines.
Given that xfsailds now also handle writing out buffers, the conditions for
log forcing and the sleep times needed some small changes. The most
important one is that we consider an AIL busy as long we still have buffers
to push, and the other one is that we do increment the pushed LSN for
buffers that are under flushing at this moment, but still count them towards
the stuck items for restart purposes. Without this we could hammer on stuck
items without ever forcing the log and not make progress under heavy random
delete workloads on fast flash storage devices.
[ Dave Chinner:
- rebase on previous patches.
- improved comments for XBF_DELWRI_Q handling
- fix XBF_ASYNC handling in queue submission (test 106 failure)
- rename delwri submit function buffer list parameters for clarity
- xfs_efd_item_push() should return XFS_ITEM_PINNED ]
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Mark Tinguely <tinguely@sgi.com>
Signed-off-by: Ben Myers <bpm@sgi.com>
2012-04-23 05:58:39 +00:00
|
|
|
bool
|
2006-01-11 04:39:08 +00:00
|
|
|
xfs_buf_delwri_queue(
|
xfs: on-stack delayed write buffer lists
Queue delwri buffers on a local on-stack list instead of a per-buftarg one,
and write back the buffers per-process instead of by waking up xfsbufd.
This is now easily doable given that we have very few places left that write
delwri buffers:
- log recovery:
Only done at mount time, and already forcing out the buffers
synchronously using xfs_flush_buftarg
- quotacheck:
Same story.
- dquot reclaim:
Writes out dirty dquots on the LRU under memory pressure. We might
want to look into doing more of this via xfsaild, but it's already
more optimal than the synchronous inode reclaim that writes each
buffer synchronously.
- xfsaild:
This is the main beneficiary of the change. By keeping a local list
of buffers to write we reduce latency of writing out buffers, and
more importably we can remove all the delwri list promotions which
were hitting the buffer cache hard under sustained metadata loads.
The implementation is very straight forward - xfs_buf_delwri_queue now gets
a new list_head pointer that it adds the delwri buffers to, and all callers
need to eventually submit the list using xfs_buf_delwi_submit or
xfs_buf_delwi_submit_nowait. Buffers that already are on a delwri list are
skipped in xfs_buf_delwri_queue, assuming they already are on another delwri
list. The biggest change to pass down the buffer list was done to the AIL
pushing. Now that we operate on buffers the trylock, push and pushbuf log
item methods are merged into a single push routine, which tries to lock the
item, and if possible add the buffer that needs writeback to the buffer list.
This leads to much simpler code than the previous split but requires the
individual IOP_PUSH instances to unlock and reacquire the AIL around calls
to blocking routines.
Given that xfsailds now also handle writing out buffers, the conditions for
log forcing and the sleep times needed some small changes. The most
important one is that we consider an AIL busy as long we still have buffers
to push, and the other one is that we do increment the pushed LSN for
buffers that are under flushing at this moment, but still count them towards
the stuck items for restart purposes. Without this we could hammer on stuck
items without ever forcing the log and not make progress under heavy random
delete workloads on fast flash storage devices.
[ Dave Chinner:
- rebase on previous patches.
- improved comments for XBF_DELWRI_Q handling
- fix XBF_ASYNC handling in queue submission (test 106 failure)
- rename delwri submit function buffer list parameters for clarity
- xfs_efd_item_push() should return XFS_ITEM_PINNED ]
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Mark Tinguely <tinguely@sgi.com>
Signed-off-by: Ben Myers <bpm@sgi.com>
2012-04-23 05:58:39 +00:00
|
|
|
struct xfs_buf *bp,
|
|
|
|
struct list_head *list)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
xfs: on-stack delayed write buffer lists
Queue delwri buffers on a local on-stack list instead of a per-buftarg one,
and write back the buffers per-process instead of by waking up xfsbufd.
This is now easily doable given that we have very few places left that write
delwri buffers:
- log recovery:
Only done at mount time, and already forcing out the buffers
synchronously using xfs_flush_buftarg
- quotacheck:
Same story.
- dquot reclaim:
Writes out dirty dquots on the LRU under memory pressure. We might
want to look into doing more of this via xfsaild, but it's already
more optimal than the synchronous inode reclaim that writes each
buffer synchronously.
- xfsaild:
This is the main beneficiary of the change. By keeping a local list
of buffers to write we reduce latency of writing out buffers, and
more importably we can remove all the delwri list promotions which
were hitting the buffer cache hard under sustained metadata loads.
The implementation is very straight forward - xfs_buf_delwri_queue now gets
a new list_head pointer that it adds the delwri buffers to, and all callers
need to eventually submit the list using xfs_buf_delwi_submit or
xfs_buf_delwi_submit_nowait. Buffers that already are on a delwri list are
skipped in xfs_buf_delwri_queue, assuming they already are on another delwri
list. The biggest change to pass down the buffer list was done to the AIL
pushing. Now that we operate on buffers the trylock, push and pushbuf log
item methods are merged into a single push routine, which tries to lock the
item, and if possible add the buffer that needs writeback to the buffer list.
This leads to much simpler code than the previous split but requires the
individual IOP_PUSH instances to unlock and reacquire the AIL around calls
to blocking routines.
Given that xfsailds now also handle writing out buffers, the conditions for
log forcing and the sleep times needed some small changes. The most
important one is that we consider an AIL busy as long we still have buffers
to push, and the other one is that we do increment the pushed LSN for
buffers that are under flushing at this moment, but still count them towards
the stuck items for restart purposes. Without this we could hammer on stuck
items without ever forcing the log and not make progress under heavy random
delete workloads on fast flash storage devices.
[ Dave Chinner:
- rebase on previous patches.
- improved comments for XBF_DELWRI_Q handling
- fix XBF_ASYNC handling in queue submission (test 106 failure)
- rename delwri submit function buffer list parameters for clarity
- xfs_efd_item_push() should return XFS_ITEM_PINNED ]
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Mark Tinguely <tinguely@sgi.com>
Signed-off-by: Ben Myers <bpm@sgi.com>
2012-04-23 05:58:39 +00:00
|
|
|
ASSERT(xfs_buf_islocked(bp));
|
2011-08-23 08:28:05 +00:00
|
|
|
ASSERT(!(bp->b_flags & XBF_READ));
|
2005-04-16 22:20:36 +00:00
|
|
|
|
xfs: on-stack delayed write buffer lists
Queue delwri buffers on a local on-stack list instead of a per-buftarg one,
and write back the buffers per-process instead of by waking up xfsbufd.
This is now easily doable given that we have very few places left that write
delwri buffers:
- log recovery:
Only done at mount time, and already forcing out the buffers
synchronously using xfs_flush_buftarg
- quotacheck:
Same story.
- dquot reclaim:
Writes out dirty dquots on the LRU under memory pressure. We might
want to look into doing more of this via xfsaild, but it's already
more optimal than the synchronous inode reclaim that writes each
buffer synchronously.
- xfsaild:
This is the main beneficiary of the change. By keeping a local list
of buffers to write we reduce latency of writing out buffers, and
more importably we can remove all the delwri list promotions which
were hitting the buffer cache hard under sustained metadata loads.
The implementation is very straight forward - xfs_buf_delwri_queue now gets
a new list_head pointer that it adds the delwri buffers to, and all callers
need to eventually submit the list using xfs_buf_delwi_submit or
xfs_buf_delwi_submit_nowait. Buffers that already are on a delwri list are
skipped in xfs_buf_delwri_queue, assuming they already are on another delwri
list. The biggest change to pass down the buffer list was done to the AIL
pushing. Now that we operate on buffers the trylock, push and pushbuf log
item methods are merged into a single push routine, which tries to lock the
item, and if possible add the buffer that needs writeback to the buffer list.
This leads to much simpler code than the previous split but requires the
individual IOP_PUSH instances to unlock and reacquire the AIL around calls
to blocking routines.
Given that xfsailds now also handle writing out buffers, the conditions for
log forcing and the sleep times needed some small changes. The most
important one is that we consider an AIL busy as long we still have buffers
to push, and the other one is that we do increment the pushed LSN for
buffers that are under flushing at this moment, but still count them towards
the stuck items for restart purposes. Without this we could hammer on stuck
items without ever forcing the log and not make progress under heavy random
delete workloads on fast flash storage devices.
[ Dave Chinner:
- rebase on previous patches.
- improved comments for XBF_DELWRI_Q handling
- fix XBF_ASYNC handling in queue submission (test 106 failure)
- rename delwri submit function buffer list parameters for clarity
- xfs_efd_item_push() should return XFS_ITEM_PINNED ]
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Mark Tinguely <tinguely@sgi.com>
Signed-off-by: Ben Myers <bpm@sgi.com>
2012-04-23 05:58:39 +00:00
|
|
|
/*
|
|
|
|
* If the buffer is already marked delwri it already is queued up
|
|
|
|
* by someone else for imediate writeout. Just ignore it in that
|
|
|
|
* case.
|
|
|
|
*/
|
|
|
|
if (bp->b_flags & _XBF_DELWRI_Q) {
|
|
|
|
trace_xfs_buf_delwri_queued(bp, _RET_IP_);
|
|
|
|
return false;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
xfs: on-stack delayed write buffer lists
Queue delwri buffers on a local on-stack list instead of a per-buftarg one,
and write back the buffers per-process instead of by waking up xfsbufd.
This is now easily doable given that we have very few places left that write
delwri buffers:
- log recovery:
Only done at mount time, and already forcing out the buffers
synchronously using xfs_flush_buftarg
- quotacheck:
Same story.
- dquot reclaim:
Writes out dirty dquots on the LRU under memory pressure. We might
want to look into doing more of this via xfsaild, but it's already
more optimal than the synchronous inode reclaim that writes each
buffer synchronously.
- xfsaild:
This is the main beneficiary of the change. By keeping a local list
of buffers to write we reduce latency of writing out buffers, and
more importably we can remove all the delwri list promotions which
were hitting the buffer cache hard under sustained metadata loads.
The implementation is very straight forward - xfs_buf_delwri_queue now gets
a new list_head pointer that it adds the delwri buffers to, and all callers
need to eventually submit the list using xfs_buf_delwi_submit or
xfs_buf_delwi_submit_nowait. Buffers that already are on a delwri list are
skipped in xfs_buf_delwri_queue, assuming they already are on another delwri
list. The biggest change to pass down the buffer list was done to the AIL
pushing. Now that we operate on buffers the trylock, push and pushbuf log
item methods are merged into a single push routine, which tries to lock the
item, and if possible add the buffer that needs writeback to the buffer list.
This leads to much simpler code than the previous split but requires the
individual IOP_PUSH instances to unlock and reacquire the AIL around calls
to blocking routines.
Given that xfsailds now also handle writing out buffers, the conditions for
log forcing and the sleep times needed some small changes. The most
important one is that we consider an AIL busy as long we still have buffers
to push, and the other one is that we do increment the pushed LSN for
buffers that are under flushing at this moment, but still count them towards
the stuck items for restart purposes. Without this we could hammer on stuck
items without ever forcing the log and not make progress under heavy random
delete workloads on fast flash storage devices.
[ Dave Chinner:
- rebase on previous patches.
- improved comments for XBF_DELWRI_Q handling
- fix XBF_ASYNC handling in queue submission (test 106 failure)
- rename delwri submit function buffer list parameters for clarity
- xfs_efd_item_push() should return XFS_ITEM_PINNED ]
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Mark Tinguely <tinguely@sgi.com>
Signed-off-by: Ben Myers <bpm@sgi.com>
2012-04-23 05:58:39 +00:00
|
|
|
trace_xfs_buf_delwri_queue(bp, _RET_IP_);
|
2010-02-01 23:13:42 +00:00
|
|
|
|
|
|
|
/*
|
xfs: on-stack delayed write buffer lists
Queue delwri buffers on a local on-stack list instead of a per-buftarg one,
and write back the buffers per-process instead of by waking up xfsbufd.
This is now easily doable given that we have very few places left that write
delwri buffers:
- log recovery:
Only done at mount time, and already forcing out the buffers
synchronously using xfs_flush_buftarg
- quotacheck:
Same story.
- dquot reclaim:
Writes out dirty dquots on the LRU under memory pressure. We might
want to look into doing more of this via xfsaild, but it's already
more optimal than the synchronous inode reclaim that writes each
buffer synchronously.
- xfsaild:
This is the main beneficiary of the change. By keeping a local list
of buffers to write we reduce latency of writing out buffers, and
more importably we can remove all the delwri list promotions which
were hitting the buffer cache hard under sustained metadata loads.
The implementation is very straight forward - xfs_buf_delwri_queue now gets
a new list_head pointer that it adds the delwri buffers to, and all callers
need to eventually submit the list using xfs_buf_delwi_submit or
xfs_buf_delwi_submit_nowait. Buffers that already are on a delwri list are
skipped in xfs_buf_delwri_queue, assuming they already are on another delwri
list. The biggest change to pass down the buffer list was done to the AIL
pushing. Now that we operate on buffers the trylock, push and pushbuf log
item methods are merged into a single push routine, which tries to lock the
item, and if possible add the buffer that needs writeback to the buffer list.
This leads to much simpler code than the previous split but requires the
individual IOP_PUSH instances to unlock and reacquire the AIL around calls
to blocking routines.
Given that xfsailds now also handle writing out buffers, the conditions for
log forcing and the sleep times needed some small changes. The most
important one is that we consider an AIL busy as long we still have buffers
to push, and the other one is that we do increment the pushed LSN for
buffers that are under flushing at this moment, but still count them towards
the stuck items for restart purposes. Without this we could hammer on stuck
items without ever forcing the log and not make progress under heavy random
delete workloads on fast flash storage devices.
[ Dave Chinner:
- rebase on previous patches.
- improved comments for XBF_DELWRI_Q handling
- fix XBF_ASYNC handling in queue submission (test 106 failure)
- rename delwri submit function buffer list parameters for clarity
- xfs_efd_item_push() should return XFS_ITEM_PINNED ]
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Mark Tinguely <tinguely@sgi.com>
Signed-off-by: Ben Myers <bpm@sgi.com>
2012-04-23 05:58:39 +00:00
|
|
|
* If a buffer gets written out synchronously or marked stale while it
|
|
|
|
* is on a delwri list we lazily remove it. To do this, the other party
|
|
|
|
* clears the _XBF_DELWRI_Q flag but otherwise leaves the buffer alone.
|
|
|
|
* It remains referenced and on the list. In a rare corner case it
|
|
|
|
* might get readded to a delwri list after the synchronous writeout, in
|
|
|
|
* which case we need just need to re-add the flag here.
|
2010-02-01 23:13:42 +00:00
|
|
|
*/
|
xfs: on-stack delayed write buffer lists
Queue delwri buffers on a local on-stack list instead of a per-buftarg one,
and write back the buffers per-process instead of by waking up xfsbufd.
This is now easily doable given that we have very few places left that write
delwri buffers:
- log recovery:
Only done at mount time, and already forcing out the buffers
synchronously using xfs_flush_buftarg
- quotacheck:
Same story.
- dquot reclaim:
Writes out dirty dquots on the LRU under memory pressure. We might
want to look into doing more of this via xfsaild, but it's already
more optimal than the synchronous inode reclaim that writes each
buffer synchronously.
- xfsaild:
This is the main beneficiary of the change. By keeping a local list
of buffers to write we reduce latency of writing out buffers, and
more importably we can remove all the delwri list promotions which
were hitting the buffer cache hard under sustained metadata loads.
The implementation is very straight forward - xfs_buf_delwri_queue now gets
a new list_head pointer that it adds the delwri buffers to, and all callers
need to eventually submit the list using xfs_buf_delwi_submit or
xfs_buf_delwi_submit_nowait. Buffers that already are on a delwri list are
skipped in xfs_buf_delwri_queue, assuming they already are on another delwri
list. The biggest change to pass down the buffer list was done to the AIL
pushing. Now that we operate on buffers the trylock, push and pushbuf log
item methods are merged into a single push routine, which tries to lock the
item, and if possible add the buffer that needs writeback to the buffer list.
This leads to much simpler code than the previous split but requires the
individual IOP_PUSH instances to unlock and reacquire the AIL around calls
to blocking routines.
Given that xfsailds now also handle writing out buffers, the conditions for
log forcing and the sleep times needed some small changes. The most
important one is that we consider an AIL busy as long we still have buffers
to push, and the other one is that we do increment the pushed LSN for
buffers that are under flushing at this moment, but still count them towards
the stuck items for restart purposes. Without this we could hammer on stuck
items without ever forcing the log and not make progress under heavy random
delete workloads on fast flash storage devices.
[ Dave Chinner:
- rebase on previous patches.
- improved comments for XBF_DELWRI_Q handling
- fix XBF_ASYNC handling in queue submission (test 106 failure)
- rename delwri submit function buffer list parameters for clarity
- xfs_efd_item_push() should return XFS_ITEM_PINNED ]
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Mark Tinguely <tinguely@sgi.com>
Signed-off-by: Ben Myers <bpm@sgi.com>
2012-04-23 05:58:39 +00:00
|
|
|
bp->b_flags |= _XBF_DELWRI_Q;
|
|
|
|
if (list_empty(&bp->b_list)) {
|
|
|
|
atomic_inc(&bp->b_hold);
|
|
|
|
list_add_tail(&bp->b_list, list);
|
2007-02-10 07:32:29 +00:00
|
|
|
}
|
|
|
|
|
xfs: on-stack delayed write buffer lists
Queue delwri buffers on a local on-stack list instead of a per-buftarg one,
and write back the buffers per-process instead of by waking up xfsbufd.
This is now easily doable given that we have very few places left that write
delwri buffers:
- log recovery:
Only done at mount time, and already forcing out the buffers
synchronously using xfs_flush_buftarg
- quotacheck:
Same story.
- dquot reclaim:
Writes out dirty dquots on the LRU under memory pressure. We might
want to look into doing more of this via xfsaild, but it's already
more optimal than the synchronous inode reclaim that writes each
buffer synchronously.
- xfsaild:
This is the main beneficiary of the change. By keeping a local list
of buffers to write we reduce latency of writing out buffers, and
more importably we can remove all the delwri list promotions which
were hitting the buffer cache hard under sustained metadata loads.
The implementation is very straight forward - xfs_buf_delwri_queue now gets
a new list_head pointer that it adds the delwri buffers to, and all callers
need to eventually submit the list using xfs_buf_delwi_submit or
xfs_buf_delwi_submit_nowait. Buffers that already are on a delwri list are
skipped in xfs_buf_delwri_queue, assuming they already are on another delwri
list. The biggest change to pass down the buffer list was done to the AIL
pushing. Now that we operate on buffers the trylock, push and pushbuf log
item methods are merged into a single push routine, which tries to lock the
item, and if possible add the buffer that needs writeback to the buffer list.
This leads to much simpler code than the previous split but requires the
individual IOP_PUSH instances to unlock and reacquire the AIL around calls
to blocking routines.
Given that xfsailds now also handle writing out buffers, the conditions for
log forcing and the sleep times needed some small changes. The most
important one is that we consider an AIL busy as long we still have buffers
to push, and the other one is that we do increment the pushed LSN for
buffers that are under flushing at this moment, but still count them towards
the stuck items for restart purposes. Without this we could hammer on stuck
items without ever forcing the log and not make progress under heavy random
delete workloads on fast flash storage devices.
[ Dave Chinner:
- rebase on previous patches.
- improved comments for XBF_DELWRI_Q handling
- fix XBF_ASYNC handling in queue submission (test 106 failure)
- rename delwri submit function buffer list parameters for clarity
- xfs_efd_item_push() should return XFS_ITEM_PINNED ]
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Mark Tinguely <tinguely@sgi.com>
Signed-off-by: Ben Myers <bpm@sgi.com>
2012-04-23 05:58:39 +00:00
|
|
|
return true;
|
2007-02-10 07:32:29 +00:00
|
|
|
}
|
|
|
|
|
xfs: force all buffers to be written during btree bulk load
While stress-testing online repair of btrees, I noticed periodic
assertion failures from the buffer cache about buffers with incorrect
DELWRI_Q state. Looking further, I observed this race between the AIL
trying to write out a btree block and repair zapping a btree block after
the fact:
AIL: Repair0:
pin buffer X
delwri_queue:
set DELWRI_Q
add to delwri list
stale buf X:
clear DELWRI_Q
does not clear b_list
free space X
commit
delwri_submit # oops
Worse yet, I discovered that running the same repair over and over in a
tight loop can result in a second race that cause data integrity
problems with the repair:
AIL: Repair0: Repair1:
pin buffer X
delwri_queue:
set DELWRI_Q
add to delwri list
stale buf X:
clear DELWRI_Q
does not clear b_list
free space X
commit
find free space X
get buffer
rewrite buffer
delwri_queue:
set DELWRI_Q
already on a list, do not add
commit
BAD: committed tree root before all blocks written
delwri_submit # too late now
I traced this to my own misunderstanding of how the delwri lists work,
particularly with regards to the AIL's buffer list. If a buffer is
logged and committed, the buffer can end up on that AIL buffer list. If
btree repairs are run twice in rapid succession, it's possible that the
first repair will invalidate the buffer and free it before the next time
the AIL wakes up. Marking the buffer stale clears DELWRI_Q from the
buffer state without removing the buffer from its delwri list. The
buffer doesn't know which list it's on, so it cannot know which lock to
take to protect the list for a removal.
If the second repair allocates the same block, it will then recycle the
buffer to start writing the new btree block. Meanwhile, if the AIL
wakes up and walks the buffer list, it will ignore the buffer because it
can't lock it, and go back to sleep.
When the second repair calls delwri_queue to put the buffer on the
list of buffers to write before committing the new btree, it will set
DELWRI_Q again, but since the buffer hasn't been removed from the AIL's
buffer list, it won't add it to the bulkload buffer's list.
This is incorrect, because the bulkload caller relies on delwri_submit
to ensure that all the buffers have been sent to disk /before/
committing the new btree root pointer. This ordering requirement is
required for data consistency.
Worse, the AIL won't clear DELWRI_Q from the buffer when it does finally
drop it, so the next thread to walk through the btree will trip over a
debug assertion on that flag.
To fix this, create a new function that waits for the buffer to be
removed from any other delwri lists before adding the buffer to the
caller's delwri list. By waiting for the buffer to clear both the
delwri list and any potential delwri wait list, we can be sure that
repair will initiate writes of all buffers and report all write errors
back to userspace instead of committing the new structure.
Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Christoph Hellwig <hch@lst.de>
2023-12-15 18:03:27 +00:00
|
|
|
/*
|
|
|
|
* Queue a buffer to this delwri list as part of a data integrity operation.
|
|
|
|
* If the buffer is on any other delwri list, we'll wait for that to clear
|
|
|
|
* so that the caller can submit the buffer for IO and wait for the result.
|
|
|
|
* Callers must ensure the buffer is not already on the list.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
xfs_buf_delwri_queue_here(
|
|
|
|
struct xfs_buf *bp,
|
|
|
|
struct list_head *buffer_list)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* We need this buffer to end up on the /caller's/ delwri list, not any
|
|
|
|
* old list. This can happen if the buffer is marked stale (which
|
|
|
|
* clears DELWRI_Q) after the AIL queues the buffer to its list but
|
|
|
|
* before the AIL has a chance to submit the list.
|
|
|
|
*/
|
|
|
|
while (!list_empty(&bp->b_list)) {
|
|
|
|
xfs_buf_unlock(bp);
|
|
|
|
wait_var_event(&bp->b_list, list_empty(&bp->b_list));
|
|
|
|
xfs_buf_lock(bp);
|
|
|
|
}
|
|
|
|
|
|
|
|
ASSERT(!(bp->b_flags & _XBF_DELWRI_Q));
|
|
|
|
|
|
|
|
xfs_buf_delwri_queue(bp, buffer_list);
|
|
|
|
}
|
|
|
|
|
2010-01-26 04:13:25 +00:00
|
|
|
/*
|
|
|
|
* Compare function is more complex than it needs to be because
|
|
|
|
* the return value is only 32 bits and we are doing comparisons
|
|
|
|
* on 64 bit values
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
xfs_buf_cmp(
|
2021-04-08 18:28:34 +00:00
|
|
|
void *priv,
|
|
|
|
const struct list_head *a,
|
|
|
|
const struct list_head *b)
|
2010-01-26 04:13:25 +00:00
|
|
|
{
|
|
|
|
struct xfs_buf *ap = container_of(a, struct xfs_buf, b_list);
|
|
|
|
struct xfs_buf *bp = container_of(b, struct xfs_buf, b_list);
|
|
|
|
xfs_daddr_t diff;
|
|
|
|
|
2012-12-04 23:18:02 +00:00
|
|
|
diff = ap->b_maps[0].bm_bn - bp->b_maps[0].bm_bn;
|
2010-01-26 04:13:25 +00:00
|
|
|
if (diff < 0)
|
|
|
|
return -1;
|
|
|
|
if (diff > 0)
|
|
|
|
return 1;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-06-01 07:38:15 +00:00
|
|
|
/*
|
2018-07-12 05:26:34 +00:00
|
|
|
* Submit buffers for write. If wait_list is specified, the buffers are
|
|
|
|
* submitted using sync I/O and placed on the wait list such that the caller can
|
|
|
|
* iowait each buffer. Otherwise async I/O is used and the buffers are released
|
|
|
|
* at I/O completion time. In either case, buffers remain locked until I/O
|
|
|
|
* completes and the buffer is released from the queue.
|
2016-06-01 07:38:15 +00:00
|
|
|
*/
|
xfs: on-stack delayed write buffer lists
Queue delwri buffers on a local on-stack list instead of a per-buftarg one,
and write back the buffers per-process instead of by waking up xfsbufd.
This is now easily doable given that we have very few places left that write
delwri buffers:
- log recovery:
Only done at mount time, and already forcing out the buffers
synchronously using xfs_flush_buftarg
- quotacheck:
Same story.
- dquot reclaim:
Writes out dirty dquots on the LRU under memory pressure. We might
want to look into doing more of this via xfsaild, but it's already
more optimal than the synchronous inode reclaim that writes each
buffer synchronously.
- xfsaild:
This is the main beneficiary of the change. By keeping a local list
of buffers to write we reduce latency of writing out buffers, and
more importably we can remove all the delwri list promotions which
were hitting the buffer cache hard under sustained metadata loads.
The implementation is very straight forward - xfs_buf_delwri_queue now gets
a new list_head pointer that it adds the delwri buffers to, and all callers
need to eventually submit the list using xfs_buf_delwi_submit or
xfs_buf_delwi_submit_nowait. Buffers that already are on a delwri list are
skipped in xfs_buf_delwri_queue, assuming they already are on another delwri
list. The biggest change to pass down the buffer list was done to the AIL
pushing. Now that we operate on buffers the trylock, push and pushbuf log
item methods are merged into a single push routine, which tries to lock the
item, and if possible add the buffer that needs writeback to the buffer list.
This leads to much simpler code than the previous split but requires the
individual IOP_PUSH instances to unlock and reacquire the AIL around calls
to blocking routines.
Given that xfsailds now also handle writing out buffers, the conditions for
log forcing and the sleep times needed some small changes. The most
important one is that we consider an AIL busy as long we still have buffers
to push, and the other one is that we do increment the pushed LSN for
buffers that are under flushing at this moment, but still count them towards
the stuck items for restart purposes. Without this we could hammer on stuck
items without ever forcing the log and not make progress under heavy random
delete workloads on fast flash storage devices.
[ Dave Chinner:
- rebase on previous patches.
- improved comments for XBF_DELWRI_Q handling
- fix XBF_ASYNC handling in queue submission (test 106 failure)
- rename delwri submit function buffer list parameters for clarity
- xfs_efd_item_push() should return XFS_ITEM_PINNED ]
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Mark Tinguely <tinguely@sgi.com>
Signed-off-by: Ben Myers <bpm@sgi.com>
2012-04-23 05:58:39 +00:00
|
|
|
static int
|
2016-06-01 07:38:15 +00:00
|
|
|
xfs_buf_delwri_submit_buffers(
|
xfs: on-stack delayed write buffer lists
Queue delwri buffers on a local on-stack list instead of a per-buftarg one,
and write back the buffers per-process instead of by waking up xfsbufd.
This is now easily doable given that we have very few places left that write
delwri buffers:
- log recovery:
Only done at mount time, and already forcing out the buffers
synchronously using xfs_flush_buftarg
- quotacheck:
Same story.
- dquot reclaim:
Writes out dirty dquots on the LRU under memory pressure. We might
want to look into doing more of this via xfsaild, but it's already
more optimal than the synchronous inode reclaim that writes each
buffer synchronously.
- xfsaild:
This is the main beneficiary of the change. By keeping a local list
of buffers to write we reduce latency of writing out buffers, and
more importably we can remove all the delwri list promotions which
were hitting the buffer cache hard under sustained metadata loads.
The implementation is very straight forward - xfs_buf_delwri_queue now gets
a new list_head pointer that it adds the delwri buffers to, and all callers
need to eventually submit the list using xfs_buf_delwi_submit or
xfs_buf_delwi_submit_nowait. Buffers that already are on a delwri list are
skipped in xfs_buf_delwri_queue, assuming they already are on another delwri
list. The biggest change to pass down the buffer list was done to the AIL
pushing. Now that we operate on buffers the trylock, push and pushbuf log
item methods are merged into a single push routine, which tries to lock the
item, and if possible add the buffer that needs writeback to the buffer list.
This leads to much simpler code than the previous split but requires the
individual IOP_PUSH instances to unlock and reacquire the AIL around calls
to blocking routines.
Given that xfsailds now also handle writing out buffers, the conditions for
log forcing and the sleep times needed some small changes. The most
important one is that we consider an AIL busy as long we still have buffers
to push, and the other one is that we do increment the pushed LSN for
buffers that are under flushing at this moment, but still count them towards
the stuck items for restart purposes. Without this we could hammer on stuck
items without ever forcing the log and not make progress under heavy random
delete workloads on fast flash storage devices.
[ Dave Chinner:
- rebase on previous patches.
- improved comments for XBF_DELWRI_Q handling
- fix XBF_ASYNC handling in queue submission (test 106 failure)
- rename delwri submit function buffer list parameters for clarity
- xfs_efd_item_push() should return XFS_ITEM_PINNED ]
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Mark Tinguely <tinguely@sgi.com>
Signed-off-by: Ben Myers <bpm@sgi.com>
2012-04-23 05:58:39 +00:00
|
|
|
struct list_head *buffer_list,
|
2016-06-01 07:38:15 +00:00
|
|
|
struct list_head *wait_list)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
xfs: on-stack delayed write buffer lists
Queue delwri buffers on a local on-stack list instead of a per-buftarg one,
and write back the buffers per-process instead of by waking up xfsbufd.
This is now easily doable given that we have very few places left that write
delwri buffers:
- log recovery:
Only done at mount time, and already forcing out the buffers
synchronously using xfs_flush_buftarg
- quotacheck:
Same story.
- dquot reclaim:
Writes out dirty dquots on the LRU under memory pressure. We might
want to look into doing more of this via xfsaild, but it's already
more optimal than the synchronous inode reclaim that writes each
buffer synchronously.
- xfsaild:
This is the main beneficiary of the change. By keeping a local list
of buffers to write we reduce latency of writing out buffers, and
more importably we can remove all the delwri list promotions which
were hitting the buffer cache hard under sustained metadata loads.
The implementation is very straight forward - xfs_buf_delwri_queue now gets
a new list_head pointer that it adds the delwri buffers to, and all callers
need to eventually submit the list using xfs_buf_delwi_submit or
xfs_buf_delwi_submit_nowait. Buffers that already are on a delwri list are
skipped in xfs_buf_delwri_queue, assuming they already are on another delwri
list. The biggest change to pass down the buffer list was done to the AIL
pushing. Now that we operate on buffers the trylock, push and pushbuf log
item methods are merged into a single push routine, which tries to lock the
item, and if possible add the buffer that needs writeback to the buffer list.
This leads to much simpler code than the previous split but requires the
individual IOP_PUSH instances to unlock and reacquire the AIL around calls
to blocking routines.
Given that xfsailds now also handle writing out buffers, the conditions for
log forcing and the sleep times needed some small changes. The most
important one is that we consider an AIL busy as long we still have buffers
to push, and the other one is that we do increment the pushed LSN for
buffers that are under flushing at this moment, but still count them towards
the stuck items for restart purposes. Without this we could hammer on stuck
items without ever forcing the log and not make progress under heavy random
delete workloads on fast flash storage devices.
[ Dave Chinner:
- rebase on previous patches.
- improved comments for XBF_DELWRI_Q handling
- fix XBF_ASYNC handling in queue submission (test 106 failure)
- rename delwri submit function buffer list parameters for clarity
- xfs_efd_item_push() should return XFS_ITEM_PINNED ]
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Mark Tinguely <tinguely@sgi.com>
Signed-off-by: Ben Myers <bpm@sgi.com>
2012-04-23 05:58:39 +00:00
|
|
|
struct xfs_buf *bp, *n;
|
|
|
|
int pinned = 0;
|
2016-06-01 07:38:15 +00:00
|
|
|
struct blk_plug plug;
|
xfs: on-stack delayed write buffer lists
Queue delwri buffers on a local on-stack list instead of a per-buftarg one,
and write back the buffers per-process instead of by waking up xfsbufd.
This is now easily doable given that we have very few places left that write
delwri buffers:
- log recovery:
Only done at mount time, and already forcing out the buffers
synchronously using xfs_flush_buftarg
- quotacheck:
Same story.
- dquot reclaim:
Writes out dirty dquots on the LRU under memory pressure. We might
want to look into doing more of this via xfsaild, but it's already
more optimal than the synchronous inode reclaim that writes each
buffer synchronously.
- xfsaild:
This is the main beneficiary of the change. By keeping a local list
of buffers to write we reduce latency of writing out buffers, and
more importably we can remove all the delwri list promotions which
were hitting the buffer cache hard under sustained metadata loads.
The implementation is very straight forward - xfs_buf_delwri_queue now gets
a new list_head pointer that it adds the delwri buffers to, and all callers
need to eventually submit the list using xfs_buf_delwi_submit or
xfs_buf_delwi_submit_nowait. Buffers that already are on a delwri list are
skipped in xfs_buf_delwri_queue, assuming they already are on another delwri
list. The biggest change to pass down the buffer list was done to the AIL
pushing. Now that we operate on buffers the trylock, push and pushbuf log
item methods are merged into a single push routine, which tries to lock the
item, and if possible add the buffer that needs writeback to the buffer list.
This leads to much simpler code than the previous split but requires the
individual IOP_PUSH instances to unlock and reacquire the AIL around calls
to blocking routines.
Given that xfsailds now also handle writing out buffers, the conditions for
log forcing and the sleep times needed some small changes. The most
important one is that we consider an AIL busy as long we still have buffers
to push, and the other one is that we do increment the pushed LSN for
buffers that are under flushing at this moment, but still count them towards
the stuck items for restart purposes. Without this we could hammer on stuck
items without ever forcing the log and not make progress under heavy random
delete workloads on fast flash storage devices.
[ Dave Chinner:
- rebase on previous patches.
- improved comments for XBF_DELWRI_Q handling
- fix XBF_ASYNC handling in queue submission (test 106 failure)
- rename delwri submit function buffer list parameters for clarity
- xfs_efd_item_push() should return XFS_ITEM_PINNED ]
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Mark Tinguely <tinguely@sgi.com>
Signed-off-by: Ben Myers <bpm@sgi.com>
2012-04-23 05:58:39 +00:00
|
|
|
|
2016-06-01 07:38:15 +00:00
|
|
|
list_sort(NULL, buffer_list, xfs_buf_cmp);
|
xfs: on-stack delayed write buffer lists
Queue delwri buffers on a local on-stack list instead of a per-buftarg one,
and write back the buffers per-process instead of by waking up xfsbufd.
This is now easily doable given that we have very few places left that write
delwri buffers:
- log recovery:
Only done at mount time, and already forcing out the buffers
synchronously using xfs_flush_buftarg
- quotacheck:
Same story.
- dquot reclaim:
Writes out dirty dquots on the LRU under memory pressure. We might
want to look into doing more of this via xfsaild, but it's already
more optimal than the synchronous inode reclaim that writes each
buffer synchronously.
- xfsaild:
This is the main beneficiary of the change. By keeping a local list
of buffers to write we reduce latency of writing out buffers, and
more importably we can remove all the delwri list promotions which
were hitting the buffer cache hard under sustained metadata loads.
The implementation is very straight forward - xfs_buf_delwri_queue now gets
a new list_head pointer that it adds the delwri buffers to, and all callers
need to eventually submit the list using xfs_buf_delwi_submit or
xfs_buf_delwi_submit_nowait. Buffers that already are on a delwri list are
skipped in xfs_buf_delwri_queue, assuming they already are on another delwri
list. The biggest change to pass down the buffer list was done to the AIL
pushing. Now that we operate on buffers the trylock, push and pushbuf log
item methods are merged into a single push routine, which tries to lock the
item, and if possible add the buffer that needs writeback to the buffer list.
This leads to much simpler code than the previous split but requires the
individual IOP_PUSH instances to unlock and reacquire the AIL around calls
to blocking routines.
Given that xfsailds now also handle writing out buffers, the conditions for
log forcing and the sleep times needed some small changes. The most
important one is that we consider an AIL busy as long we still have buffers
to push, and the other one is that we do increment the pushed LSN for
buffers that are under flushing at this moment, but still count them towards
the stuck items for restart purposes. Without this we could hammer on stuck
items without ever forcing the log and not make progress under heavy random
delete workloads on fast flash storage devices.
[ Dave Chinner:
- rebase on previous patches.
- improved comments for XBF_DELWRI_Q handling
- fix XBF_ASYNC handling in queue submission (test 106 failure)
- rename delwri submit function buffer list parameters for clarity
- xfs_efd_item_push() should return XFS_ITEM_PINNED ]
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Mark Tinguely <tinguely@sgi.com>
Signed-off-by: Ben Myers <bpm@sgi.com>
2012-04-23 05:58:39 +00:00
|
|
|
|
2016-06-01 07:38:15 +00:00
|
|
|
blk_start_plug(&plug);
|
xfs: on-stack delayed write buffer lists
Queue delwri buffers on a local on-stack list instead of a per-buftarg one,
and write back the buffers per-process instead of by waking up xfsbufd.
This is now easily doable given that we have very few places left that write
delwri buffers:
- log recovery:
Only done at mount time, and already forcing out the buffers
synchronously using xfs_flush_buftarg
- quotacheck:
Same story.
- dquot reclaim:
Writes out dirty dquots on the LRU under memory pressure. We might
want to look into doing more of this via xfsaild, but it's already
more optimal than the synchronous inode reclaim that writes each
buffer synchronously.
- xfsaild:
This is the main beneficiary of the change. By keeping a local list
of buffers to write we reduce latency of writing out buffers, and
more importably we can remove all the delwri list promotions which
were hitting the buffer cache hard under sustained metadata loads.
The implementation is very straight forward - xfs_buf_delwri_queue now gets
a new list_head pointer that it adds the delwri buffers to, and all callers
need to eventually submit the list using xfs_buf_delwi_submit or
xfs_buf_delwi_submit_nowait. Buffers that already are on a delwri list are
skipped in xfs_buf_delwri_queue, assuming they already are on another delwri
list. The biggest change to pass down the buffer list was done to the AIL
pushing. Now that we operate on buffers the trylock, push and pushbuf log
item methods are merged into a single push routine, which tries to lock the
item, and if possible add the buffer that needs writeback to the buffer list.
This leads to much simpler code than the previous split but requires the
individual IOP_PUSH instances to unlock and reacquire the AIL around calls
to blocking routines.
Given that xfsailds now also handle writing out buffers, the conditions for
log forcing and the sleep times needed some small changes. The most
important one is that we consider an AIL busy as long we still have buffers
to push, and the other one is that we do increment the pushed LSN for
buffers that are under flushing at this moment, but still count them towards
the stuck items for restart purposes. Without this we could hammer on stuck
items without ever forcing the log and not make progress under heavy random
delete workloads on fast flash storage devices.
[ Dave Chinner:
- rebase on previous patches.
- improved comments for XBF_DELWRI_Q handling
- fix XBF_ASYNC handling in queue submission (test 106 failure)
- rename delwri submit function buffer list parameters for clarity
- xfs_efd_item_push() should return XFS_ITEM_PINNED ]
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Mark Tinguely <tinguely@sgi.com>
Signed-off-by: Ben Myers <bpm@sgi.com>
2012-04-23 05:58:39 +00:00
|
|
|
list_for_each_entry_safe(bp, n, buffer_list, b_list) {
|
2016-06-01 07:38:15 +00:00
|
|
|
if (!wait_list) {
|
2022-03-17 16:09:10 +00:00
|
|
|
if (!xfs_buf_trylock(bp))
|
|
|
|
continue;
|
xfs: on-stack delayed write buffer lists
Queue delwri buffers on a local on-stack list instead of a per-buftarg one,
and write back the buffers per-process instead of by waking up xfsbufd.
This is now easily doable given that we have very few places left that write
delwri buffers:
- log recovery:
Only done at mount time, and already forcing out the buffers
synchronously using xfs_flush_buftarg
- quotacheck:
Same story.
- dquot reclaim:
Writes out dirty dquots on the LRU under memory pressure. We might
want to look into doing more of this via xfsaild, but it's already
more optimal than the synchronous inode reclaim that writes each
buffer synchronously.
- xfsaild:
This is the main beneficiary of the change. By keeping a local list
of buffers to write we reduce latency of writing out buffers, and
more importably we can remove all the delwri list promotions which
were hitting the buffer cache hard under sustained metadata loads.
The implementation is very straight forward - xfs_buf_delwri_queue now gets
a new list_head pointer that it adds the delwri buffers to, and all callers
need to eventually submit the list using xfs_buf_delwi_submit or
xfs_buf_delwi_submit_nowait. Buffers that already are on a delwri list are
skipped in xfs_buf_delwri_queue, assuming they already are on another delwri
list. The biggest change to pass down the buffer list was done to the AIL
pushing. Now that we operate on buffers the trylock, push and pushbuf log
item methods are merged into a single push routine, which tries to lock the
item, and if possible add the buffer that needs writeback to the buffer list.
This leads to much simpler code than the previous split but requires the
individual IOP_PUSH instances to unlock and reacquire the AIL around calls
to blocking routines.
Given that xfsailds now also handle writing out buffers, the conditions for
log forcing and the sleep times needed some small changes. The most
important one is that we consider an AIL busy as long we still have buffers
to push, and the other one is that we do increment the pushed LSN for
buffers that are under flushing at this moment, but still count them towards
the stuck items for restart purposes. Without this we could hammer on stuck
items without ever forcing the log and not make progress under heavy random
delete workloads on fast flash storage devices.
[ Dave Chinner:
- rebase on previous patches.
- improved comments for XBF_DELWRI_Q handling
- fix XBF_ASYNC handling in queue submission (test 106 failure)
- rename delwri submit function buffer list parameters for clarity
- xfs_efd_item_push() should return XFS_ITEM_PINNED ]
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Mark Tinguely <tinguely@sgi.com>
Signed-off-by: Ben Myers <bpm@sgi.com>
2012-04-23 05:58:39 +00:00
|
|
|
if (xfs_buf_ispinned(bp)) {
|
2022-03-17 16:09:10 +00:00
|
|
|
xfs_buf_unlock(bp);
|
xfs: on-stack delayed write buffer lists
Queue delwri buffers on a local on-stack list instead of a per-buftarg one,
and write back the buffers per-process instead of by waking up xfsbufd.
This is now easily doable given that we have very few places left that write
delwri buffers:
- log recovery:
Only done at mount time, and already forcing out the buffers
synchronously using xfs_flush_buftarg
- quotacheck:
Same story.
- dquot reclaim:
Writes out dirty dquots on the LRU under memory pressure. We might
want to look into doing more of this via xfsaild, but it's already
more optimal than the synchronous inode reclaim that writes each
buffer synchronously.
- xfsaild:
This is the main beneficiary of the change. By keeping a local list
of buffers to write we reduce latency of writing out buffers, and
more importably we can remove all the delwri list promotions which
were hitting the buffer cache hard under sustained metadata loads.
The implementation is very straight forward - xfs_buf_delwri_queue now gets
a new list_head pointer that it adds the delwri buffers to, and all callers
need to eventually submit the list using xfs_buf_delwi_submit or
xfs_buf_delwi_submit_nowait. Buffers that already are on a delwri list are
skipped in xfs_buf_delwri_queue, assuming they already are on another delwri
list. The biggest change to pass down the buffer list was done to the AIL
pushing. Now that we operate on buffers the trylock, push and pushbuf log
item methods are merged into a single push routine, which tries to lock the
item, and if possible add the buffer that needs writeback to the buffer list.
This leads to much simpler code than the previous split but requires the
individual IOP_PUSH instances to unlock and reacquire the AIL around calls
to blocking routines.
Given that xfsailds now also handle writing out buffers, the conditions for
log forcing and the sleep times needed some small changes. The most
important one is that we consider an AIL busy as long we still have buffers
to push, and the other one is that we do increment the pushed LSN for
buffers that are under flushing at this moment, but still count them towards
the stuck items for restart purposes. Without this we could hammer on stuck
items without ever forcing the log and not make progress under heavy random
delete workloads on fast flash storage devices.
[ Dave Chinner:
- rebase on previous patches.
- improved comments for XBF_DELWRI_Q handling
- fix XBF_ASYNC handling in queue submission (test 106 failure)
- rename delwri submit function buffer list parameters for clarity
- xfs_efd_item_push() should return XFS_ITEM_PINNED ]
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Mark Tinguely <tinguely@sgi.com>
Signed-off-by: Ben Myers <bpm@sgi.com>
2012-04-23 05:58:39 +00:00
|
|
|
pinned++;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
xfs_buf_lock(bp);
|
|
|
|
}
|
2007-12-07 03:09:02 +00:00
|
|
|
|
xfs: on-stack delayed write buffer lists
Queue delwri buffers on a local on-stack list instead of a per-buftarg one,
and write back the buffers per-process instead of by waking up xfsbufd.
This is now easily doable given that we have very few places left that write
delwri buffers:
- log recovery:
Only done at mount time, and already forcing out the buffers
synchronously using xfs_flush_buftarg
- quotacheck:
Same story.
- dquot reclaim:
Writes out dirty dquots on the LRU under memory pressure. We might
want to look into doing more of this via xfsaild, but it's already
more optimal than the synchronous inode reclaim that writes each
buffer synchronously.
- xfsaild:
This is the main beneficiary of the change. By keeping a local list
of buffers to write we reduce latency of writing out buffers, and
more importably we can remove all the delwri list promotions which
were hitting the buffer cache hard under sustained metadata loads.
The implementation is very straight forward - xfs_buf_delwri_queue now gets
a new list_head pointer that it adds the delwri buffers to, and all callers
need to eventually submit the list using xfs_buf_delwi_submit or
xfs_buf_delwi_submit_nowait. Buffers that already are on a delwri list are
skipped in xfs_buf_delwri_queue, assuming they already are on another delwri
list. The biggest change to pass down the buffer list was done to the AIL
pushing. Now that we operate on buffers the trylock, push and pushbuf log
item methods are merged into a single push routine, which tries to lock the
item, and if possible add the buffer that needs writeback to the buffer list.
This leads to much simpler code than the previous split but requires the
individual IOP_PUSH instances to unlock and reacquire the AIL around calls
to blocking routines.
Given that xfsailds now also handle writing out buffers, the conditions for
log forcing and the sleep times needed some small changes. The most
important one is that we consider an AIL busy as long we still have buffers
to push, and the other one is that we do increment the pushed LSN for
buffers that are under flushing at this moment, but still count them towards
the stuck items for restart purposes. Without this we could hammer on stuck
items without ever forcing the log and not make progress under heavy random
delete workloads on fast flash storage devices.
[ Dave Chinner:
- rebase on previous patches.
- improved comments for XBF_DELWRI_Q handling
- fix XBF_ASYNC handling in queue submission (test 106 failure)
- rename delwri submit function buffer list parameters for clarity
- xfs_efd_item_push() should return XFS_ITEM_PINNED ]
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Mark Tinguely <tinguely@sgi.com>
Signed-off-by: Ben Myers <bpm@sgi.com>
2012-04-23 05:58:39 +00:00
|
|
|
/*
|
|
|
|
* Someone else might have written the buffer synchronously or
|
|
|
|
* marked it stale in the meantime. In that case only the
|
|
|
|
* _XBF_DELWRI_Q flag got cleared, and we have to drop the
|
|
|
|
* reference and remove it from the list here.
|
|
|
|
*/
|
|
|
|
if (!(bp->b_flags & _XBF_DELWRI_Q)) {
|
xfs: force all buffers to be written during btree bulk load
While stress-testing online repair of btrees, I noticed periodic
assertion failures from the buffer cache about buffers with incorrect
DELWRI_Q state. Looking further, I observed this race between the AIL
trying to write out a btree block and repair zapping a btree block after
the fact:
AIL: Repair0:
pin buffer X
delwri_queue:
set DELWRI_Q
add to delwri list
stale buf X:
clear DELWRI_Q
does not clear b_list
free space X
commit
delwri_submit # oops
Worse yet, I discovered that running the same repair over and over in a
tight loop can result in a second race that cause data integrity
problems with the repair:
AIL: Repair0: Repair1:
pin buffer X
delwri_queue:
set DELWRI_Q
add to delwri list
stale buf X:
clear DELWRI_Q
does not clear b_list
free space X
commit
find free space X
get buffer
rewrite buffer
delwri_queue:
set DELWRI_Q
already on a list, do not add
commit
BAD: committed tree root before all blocks written
delwri_submit # too late now
I traced this to my own misunderstanding of how the delwri lists work,
particularly with regards to the AIL's buffer list. If a buffer is
logged and committed, the buffer can end up on that AIL buffer list. If
btree repairs are run twice in rapid succession, it's possible that the
first repair will invalidate the buffer and free it before the next time
the AIL wakes up. Marking the buffer stale clears DELWRI_Q from the
buffer state without removing the buffer from its delwri list. The
buffer doesn't know which list it's on, so it cannot know which lock to
take to protect the list for a removal.
If the second repair allocates the same block, it will then recycle the
buffer to start writing the new btree block. Meanwhile, if the AIL
wakes up and walks the buffer list, it will ignore the buffer because it
can't lock it, and go back to sleep.
When the second repair calls delwri_queue to put the buffer on the
list of buffers to write before committing the new btree, it will set
DELWRI_Q again, but since the buffer hasn't been removed from the AIL's
buffer list, it won't add it to the bulkload buffer's list.
This is incorrect, because the bulkload caller relies on delwri_submit
to ensure that all the buffers have been sent to disk /before/
committing the new btree root pointer. This ordering requirement is
required for data consistency.
Worse, the AIL won't clear DELWRI_Q from the buffer when it does finally
drop it, so the next thread to walk through the btree will trip over a
debug assertion on that flag.
To fix this, create a new function that waits for the buffer to be
removed from any other delwri lists before adding the buffer to the
caller's delwri list. By waiting for the buffer to clear both the
delwri list and any potential delwri wait list, we can be sure that
repair will initiate writes of all buffers and report all write errors
back to userspace instead of committing the new structure.
Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Christoph Hellwig <hch@lst.de>
2023-12-15 18:03:27 +00:00
|
|
|
xfs_buf_list_del(bp);
|
xfs: on-stack delayed write buffer lists
Queue delwri buffers on a local on-stack list instead of a per-buftarg one,
and write back the buffers per-process instead of by waking up xfsbufd.
This is now easily doable given that we have very few places left that write
delwri buffers:
- log recovery:
Only done at mount time, and already forcing out the buffers
synchronously using xfs_flush_buftarg
- quotacheck:
Same story.
- dquot reclaim:
Writes out dirty dquots on the LRU under memory pressure. We might
want to look into doing more of this via xfsaild, but it's already
more optimal than the synchronous inode reclaim that writes each
buffer synchronously.
- xfsaild:
This is the main beneficiary of the change. By keeping a local list
of buffers to write we reduce latency of writing out buffers, and
more importably we can remove all the delwri list promotions which
were hitting the buffer cache hard under sustained metadata loads.
The implementation is very straight forward - xfs_buf_delwri_queue now gets
a new list_head pointer that it adds the delwri buffers to, and all callers
need to eventually submit the list using xfs_buf_delwi_submit or
xfs_buf_delwi_submit_nowait. Buffers that already are on a delwri list are
skipped in xfs_buf_delwri_queue, assuming they already are on another delwri
list. The biggest change to pass down the buffer list was done to the AIL
pushing. Now that we operate on buffers the trylock, push and pushbuf log
item methods are merged into a single push routine, which tries to lock the
item, and if possible add the buffer that needs writeback to the buffer list.
This leads to much simpler code than the previous split but requires the
individual IOP_PUSH instances to unlock and reacquire the AIL around calls
to blocking routines.
Given that xfsailds now also handle writing out buffers, the conditions for
log forcing and the sleep times needed some small changes. The most
important one is that we consider an AIL busy as long we still have buffers
to push, and the other one is that we do increment the pushed LSN for
buffers that are under flushing at this moment, but still count them towards
the stuck items for restart purposes. Without this we could hammer on stuck
items without ever forcing the log and not make progress under heavy random
delete workloads on fast flash storage devices.
[ Dave Chinner:
- rebase on previous patches.
- improved comments for XBF_DELWRI_Q handling
- fix XBF_ASYNC handling in queue submission (test 106 failure)
- rename delwri submit function buffer list parameters for clarity
- xfs_efd_item_push() should return XFS_ITEM_PINNED ]
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Mark Tinguely <tinguely@sgi.com>
Signed-off-by: Ben Myers <bpm@sgi.com>
2012-04-23 05:58:39 +00:00
|
|
|
xfs_buf_relse(bp);
|
|
|
|
continue;
|
|
|
|
}
|
2010-01-11 11:49:59 +00:00
|
|
|
|
xfs: on-stack delayed write buffer lists
Queue delwri buffers on a local on-stack list instead of a per-buftarg one,
and write back the buffers per-process instead of by waking up xfsbufd.
This is now easily doable given that we have very few places left that write
delwri buffers:
- log recovery:
Only done at mount time, and already forcing out the buffers
synchronously using xfs_flush_buftarg
- quotacheck:
Same story.
- dquot reclaim:
Writes out dirty dquots on the LRU under memory pressure. We might
want to look into doing more of this via xfsaild, but it's already
more optimal than the synchronous inode reclaim that writes each
buffer synchronously.
- xfsaild:
This is the main beneficiary of the change. By keeping a local list
of buffers to write we reduce latency of writing out buffers, and
more importably we can remove all the delwri list promotions which
were hitting the buffer cache hard under sustained metadata loads.
The implementation is very straight forward - xfs_buf_delwri_queue now gets
a new list_head pointer that it adds the delwri buffers to, and all callers
need to eventually submit the list using xfs_buf_delwi_submit or
xfs_buf_delwi_submit_nowait. Buffers that already are on a delwri list are
skipped in xfs_buf_delwri_queue, assuming they already are on another delwri
list. The biggest change to pass down the buffer list was done to the AIL
pushing. Now that we operate on buffers the trylock, push and pushbuf log
item methods are merged into a single push routine, which tries to lock the
item, and if possible add the buffer that needs writeback to the buffer list.
This leads to much simpler code than the previous split but requires the
individual IOP_PUSH instances to unlock and reacquire the AIL around calls
to blocking routines.
Given that xfsailds now also handle writing out buffers, the conditions for
log forcing and the sleep times needed some small changes. The most
important one is that we consider an AIL busy as long we still have buffers
to push, and the other one is that we do increment the pushed LSN for
buffers that are under flushing at this moment, but still count them towards
the stuck items for restart purposes. Without this we could hammer on stuck
items without ever forcing the log and not make progress under heavy random
delete workloads on fast flash storage devices.
[ Dave Chinner:
- rebase on previous patches.
- improved comments for XBF_DELWRI_Q handling
- fix XBF_ASYNC handling in queue submission (test 106 failure)
- rename delwri submit function buffer list parameters for clarity
- xfs_efd_item_push() should return XFS_ITEM_PINNED ]
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Mark Tinguely <tinguely@sgi.com>
Signed-off-by: Ben Myers <bpm@sgi.com>
2012-04-23 05:58:39 +00:00
|
|
|
trace_xfs_buf_delwri_split(bp, _RET_IP_);
|
2011-03-30 11:05:09 +00:00
|
|
|
|
2014-10-01 23:04:01 +00:00
|
|
|
/*
|
2018-07-12 05:26:34 +00:00
|
|
|
* If we have a wait list, each buffer (and associated delwri
|
|
|
|
* queue reference) transfers to it and is submitted
|
|
|
|
* synchronously. Otherwise, drop the buffer from the delwri
|
|
|
|
* queue and submit async.
|
2014-10-01 23:04:01 +00:00
|
|
|
*/
|
2020-05-06 20:25:20 +00:00
|
|
|
bp->b_flags &= ~_XBF_DELWRI_Q;
|
2018-07-12 05:26:34 +00:00
|
|
|
bp->b_flags |= XBF_WRITE;
|
2016-06-01 07:38:15 +00:00
|
|
|
if (wait_list) {
|
2018-07-12 05:26:34 +00:00
|
|
|
bp->b_flags &= ~XBF_ASYNC;
|
2016-06-01 07:38:15 +00:00
|
|
|
list_move_tail(&bp->b_list, wait_list);
|
2018-07-12 05:26:34 +00:00
|
|
|
} else {
|
|
|
|
bp->b_flags |= XBF_ASYNC;
|
xfs: force all buffers to be written during btree bulk load
While stress-testing online repair of btrees, I noticed periodic
assertion failures from the buffer cache about buffers with incorrect
DELWRI_Q state. Looking further, I observed this race between the AIL
trying to write out a btree block and repair zapping a btree block after
the fact:
AIL: Repair0:
pin buffer X
delwri_queue:
set DELWRI_Q
add to delwri list
stale buf X:
clear DELWRI_Q
does not clear b_list
free space X
commit
delwri_submit # oops
Worse yet, I discovered that running the same repair over and over in a
tight loop can result in a second race that cause data integrity
problems with the repair:
AIL: Repair0: Repair1:
pin buffer X
delwri_queue:
set DELWRI_Q
add to delwri list
stale buf X:
clear DELWRI_Q
does not clear b_list
free space X
commit
find free space X
get buffer
rewrite buffer
delwri_queue:
set DELWRI_Q
already on a list, do not add
commit
BAD: committed tree root before all blocks written
delwri_submit # too late now
I traced this to my own misunderstanding of how the delwri lists work,
particularly with regards to the AIL's buffer list. If a buffer is
logged and committed, the buffer can end up on that AIL buffer list. If
btree repairs are run twice in rapid succession, it's possible that the
first repair will invalidate the buffer and free it before the next time
the AIL wakes up. Marking the buffer stale clears DELWRI_Q from the
buffer state without removing the buffer from its delwri list. The
buffer doesn't know which list it's on, so it cannot know which lock to
take to protect the list for a removal.
If the second repair allocates the same block, it will then recycle the
buffer to start writing the new btree block. Meanwhile, if the AIL
wakes up and walks the buffer list, it will ignore the buffer because it
can't lock it, and go back to sleep.
When the second repair calls delwri_queue to put the buffer on the
list of buffers to write before committing the new btree, it will set
DELWRI_Q again, but since the buffer hasn't been removed from the AIL's
buffer list, it won't add it to the bulkload buffer's list.
This is incorrect, because the bulkload caller relies on delwri_submit
to ensure that all the buffers have been sent to disk /before/
committing the new btree root pointer. This ordering requirement is
required for data consistency.
Worse, the AIL won't clear DELWRI_Q from the buffer when it does finally
drop it, so the next thread to walk through the btree will trip over a
debug assertion on that flag.
To fix this, create a new function that waits for the buffer to be
removed from any other delwri lists before adding the buffer to the
caller's delwri list. By waiting for the buffer to clear both the
delwri list and any potential delwri wait list, we can be sure that
repair will initiate writes of all buffers and report all write errors
back to userspace instead of committing the new structure.
Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Christoph Hellwig <hch@lst.de>
2023-12-15 18:03:27 +00:00
|
|
|
xfs_buf_list_del(bp);
|
2018-07-12 05:26:34 +00:00
|
|
|
}
|
2018-07-12 05:26:35 +00:00
|
|
|
__xfs_buf_submit(bp, false);
|
xfs: on-stack delayed write buffer lists
Queue delwri buffers on a local on-stack list instead of a per-buftarg one,
and write back the buffers per-process instead of by waking up xfsbufd.
This is now easily doable given that we have very few places left that write
delwri buffers:
- log recovery:
Only done at mount time, and already forcing out the buffers
synchronously using xfs_flush_buftarg
- quotacheck:
Same story.
- dquot reclaim:
Writes out dirty dquots on the LRU under memory pressure. We might
want to look into doing more of this via xfsaild, but it's already
more optimal than the synchronous inode reclaim that writes each
buffer synchronously.
- xfsaild:
This is the main beneficiary of the change. By keeping a local list
of buffers to write we reduce latency of writing out buffers, and
more importably we can remove all the delwri list promotions which
were hitting the buffer cache hard under sustained metadata loads.
The implementation is very straight forward - xfs_buf_delwri_queue now gets
a new list_head pointer that it adds the delwri buffers to, and all callers
need to eventually submit the list using xfs_buf_delwi_submit or
xfs_buf_delwi_submit_nowait. Buffers that already are on a delwri list are
skipped in xfs_buf_delwri_queue, assuming they already are on another delwri
list. The biggest change to pass down the buffer list was done to the AIL
pushing. Now that we operate on buffers the trylock, push and pushbuf log
item methods are merged into a single push routine, which tries to lock the
item, and if possible add the buffer that needs writeback to the buffer list.
This leads to much simpler code than the previous split but requires the
individual IOP_PUSH instances to unlock and reacquire the AIL around calls
to blocking routines.
Given that xfsailds now also handle writing out buffers, the conditions for
log forcing and the sleep times needed some small changes. The most
important one is that we consider an AIL busy as long we still have buffers
to push, and the other one is that we do increment the pushed LSN for
buffers that are under flushing at this moment, but still count them towards
the stuck items for restart purposes. Without this we could hammer on stuck
items without ever forcing the log and not make progress under heavy random
delete workloads on fast flash storage devices.
[ Dave Chinner:
- rebase on previous patches.
- improved comments for XBF_DELWRI_Q handling
- fix XBF_ASYNC handling in queue submission (test 106 failure)
- rename delwri submit function buffer list parameters for clarity
- xfs_efd_item_push() should return XFS_ITEM_PINNED ]
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Mark Tinguely <tinguely@sgi.com>
Signed-off-by: Ben Myers <bpm@sgi.com>
2012-04-23 05:58:39 +00:00
|
|
|
}
|
|
|
|
blk_finish_plug(&plug);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
xfs: on-stack delayed write buffer lists
Queue delwri buffers on a local on-stack list instead of a per-buftarg one,
and write back the buffers per-process instead of by waking up xfsbufd.
This is now easily doable given that we have very few places left that write
delwri buffers:
- log recovery:
Only done at mount time, and already forcing out the buffers
synchronously using xfs_flush_buftarg
- quotacheck:
Same story.
- dquot reclaim:
Writes out dirty dquots on the LRU under memory pressure. We might
want to look into doing more of this via xfsaild, but it's already
more optimal than the synchronous inode reclaim that writes each
buffer synchronously.
- xfsaild:
This is the main beneficiary of the change. By keeping a local list
of buffers to write we reduce latency of writing out buffers, and
more importably we can remove all the delwri list promotions which
were hitting the buffer cache hard under sustained metadata loads.
The implementation is very straight forward - xfs_buf_delwri_queue now gets
a new list_head pointer that it adds the delwri buffers to, and all callers
need to eventually submit the list using xfs_buf_delwi_submit or
xfs_buf_delwi_submit_nowait. Buffers that already are on a delwri list are
skipped in xfs_buf_delwri_queue, assuming they already are on another delwri
list. The biggest change to pass down the buffer list was done to the AIL
pushing. Now that we operate on buffers the trylock, push and pushbuf log
item methods are merged into a single push routine, which tries to lock the
item, and if possible add the buffer that needs writeback to the buffer list.
This leads to much simpler code than the previous split but requires the
individual IOP_PUSH instances to unlock and reacquire the AIL around calls
to blocking routines.
Given that xfsailds now also handle writing out buffers, the conditions for
log forcing and the sleep times needed some small changes. The most
important one is that we consider an AIL busy as long we still have buffers
to push, and the other one is that we do increment the pushed LSN for
buffers that are under flushing at this moment, but still count them towards
the stuck items for restart purposes. Without this we could hammer on stuck
items without ever forcing the log and not make progress under heavy random
delete workloads on fast flash storage devices.
[ Dave Chinner:
- rebase on previous patches.
- improved comments for XBF_DELWRI_Q handling
- fix XBF_ASYNC handling in queue submission (test 106 failure)
- rename delwri submit function buffer list parameters for clarity
- xfs_efd_item_push() should return XFS_ITEM_PINNED ]
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Mark Tinguely <tinguely@sgi.com>
Signed-off-by: Ben Myers <bpm@sgi.com>
2012-04-23 05:58:39 +00:00
|
|
|
return pinned;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
xfs: on-stack delayed write buffer lists
Queue delwri buffers on a local on-stack list instead of a per-buftarg one,
and write back the buffers per-process instead of by waking up xfsbufd.
This is now easily doable given that we have very few places left that write
delwri buffers:
- log recovery:
Only done at mount time, and already forcing out the buffers
synchronously using xfs_flush_buftarg
- quotacheck:
Same story.
- dquot reclaim:
Writes out dirty dquots on the LRU under memory pressure. We might
want to look into doing more of this via xfsaild, but it's already
more optimal than the synchronous inode reclaim that writes each
buffer synchronously.
- xfsaild:
This is the main beneficiary of the change. By keeping a local list
of buffers to write we reduce latency of writing out buffers, and
more importably we can remove all the delwri list promotions which
were hitting the buffer cache hard under sustained metadata loads.
The implementation is very straight forward - xfs_buf_delwri_queue now gets
a new list_head pointer that it adds the delwri buffers to, and all callers
need to eventually submit the list using xfs_buf_delwi_submit or
xfs_buf_delwi_submit_nowait. Buffers that already are on a delwri list are
skipped in xfs_buf_delwri_queue, assuming they already are on another delwri
list. The biggest change to pass down the buffer list was done to the AIL
pushing. Now that we operate on buffers the trylock, push and pushbuf log
item methods are merged into a single push routine, which tries to lock the
item, and if possible add the buffer that needs writeback to the buffer list.
This leads to much simpler code than the previous split but requires the
individual IOP_PUSH instances to unlock and reacquire the AIL around calls
to blocking routines.
Given that xfsailds now also handle writing out buffers, the conditions for
log forcing and the sleep times needed some small changes. The most
important one is that we consider an AIL busy as long we still have buffers
to push, and the other one is that we do increment the pushed LSN for
buffers that are under flushing at this moment, but still count them towards
the stuck items for restart purposes. Without this we could hammer on stuck
items without ever forcing the log and not make progress under heavy random
delete workloads on fast flash storage devices.
[ Dave Chinner:
- rebase on previous patches.
- improved comments for XBF_DELWRI_Q handling
- fix XBF_ASYNC handling in queue submission (test 106 failure)
- rename delwri submit function buffer list parameters for clarity
- xfs_efd_item_push() should return XFS_ITEM_PINNED ]
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Mark Tinguely <tinguely@sgi.com>
Signed-off-by: Ben Myers <bpm@sgi.com>
2012-04-23 05:58:39 +00:00
|
|
|
* Write out a buffer list asynchronously.
|
|
|
|
*
|
|
|
|
* This will take the @buffer_list, write all non-locked and non-pinned buffers
|
|
|
|
* out and not wait for I/O completion on any of the buffers. This interface
|
|
|
|
* is only safely useable for callers that can track I/O completion by higher
|
|
|
|
* level means, e.g. AIL pushing as the @buffer_list is consumed in this
|
|
|
|
* function.
|
xfs: clear ail delwri queued bufs on unmount of shutdown fs
In the typical unmount case, the AIL is forced out by the unmount
sequence before the xfsaild task is stopped. Since AIL items are
removed on writeback completion, this means that the AIL
->ail_buf_list delwri queue has been drained. This is not always
true in the shutdown case, however.
It's possible for buffers to sit on a delwri queue for a period of
time across submission attempts if said items are locked or have
been relogged and pinned since first added to the queue. If the
attempt to log such an item results in a log I/O error, the error
processing can shutdown the fs, remove the item from the AIL, stale
the buffer (dropping the LRU reference) and clear its delwri queue
state. The latter bit means the buffer will be released from a
delwri queue on the next submission attempt, but this might never
occur if the filesystem has shutdown and the AIL is empty.
This means that such buffers are held indefinitely by the AIL delwri
queue across destruction of the AIL. Aside from being a memory leak,
these buffers can also hold references to in-core perag structures.
The latter problem manifests as a generic/475 failure, reproducing
the following asserts at unmount time:
XFS: Assertion failed: atomic_read(&pag->pag_ref) == 0,
file: fs/xfs/xfs_mount.c, line: 151
XFS: Assertion failed: atomic_read(&pag->pag_ref) == 0,
file: fs/xfs/xfs_mount.c, line: 132
To prevent this problem, clear the AIL delwri queue as a final step
before xfsaild() exit. The !empty state should never occur in the
normal case, so add an assert to catch unexpected problems going
forward.
[dgc: add comment explaining need for xfs_buf_delwri_cancel() after
calling xfs_buf_delwri_submit_nowait().]
Signed-off-by: Brian Foster <bfoster@redhat.com>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Signed-off-by: Dave Chinner <david@fromorbit.com>
2018-10-18 06:21:49 +00:00
|
|
|
*
|
|
|
|
* Note: this function will skip buffers it would block on, and in doing so
|
|
|
|
* leaves them on @buffer_list so they can be retried on a later pass. As such,
|
|
|
|
* it is up to the caller to ensure that the buffer list is fully submitted or
|
|
|
|
* cancelled appropriately when they are finished with the list. Failure to
|
|
|
|
* cancel or resubmit the list until it is empty will result in leaked buffers
|
|
|
|
* at unmount time.
|
2005-04-16 22:20:36 +00:00
|
|
|
*/
|
|
|
|
int
|
xfs: on-stack delayed write buffer lists
Queue delwri buffers on a local on-stack list instead of a per-buftarg one,
and write back the buffers per-process instead of by waking up xfsbufd.
This is now easily doable given that we have very few places left that write
delwri buffers:
- log recovery:
Only done at mount time, and already forcing out the buffers
synchronously using xfs_flush_buftarg
- quotacheck:
Same story.
- dquot reclaim:
Writes out dirty dquots on the LRU under memory pressure. We might
want to look into doing more of this via xfsaild, but it's already
more optimal than the synchronous inode reclaim that writes each
buffer synchronously.
- xfsaild:
This is the main beneficiary of the change. By keeping a local list
of buffers to write we reduce latency of writing out buffers, and
more importably we can remove all the delwri list promotions which
were hitting the buffer cache hard under sustained metadata loads.
The implementation is very straight forward - xfs_buf_delwri_queue now gets
a new list_head pointer that it adds the delwri buffers to, and all callers
need to eventually submit the list using xfs_buf_delwi_submit or
xfs_buf_delwi_submit_nowait. Buffers that already are on a delwri list are
skipped in xfs_buf_delwri_queue, assuming they already are on another delwri
list. The biggest change to pass down the buffer list was done to the AIL
pushing. Now that we operate on buffers the trylock, push and pushbuf log
item methods are merged into a single push routine, which tries to lock the
item, and if possible add the buffer that needs writeback to the buffer list.
This leads to much simpler code than the previous split but requires the
individual IOP_PUSH instances to unlock and reacquire the AIL around calls
to blocking routines.
Given that xfsailds now also handle writing out buffers, the conditions for
log forcing and the sleep times needed some small changes. The most
important one is that we consider an AIL busy as long we still have buffers
to push, and the other one is that we do increment the pushed LSN for
buffers that are under flushing at this moment, but still count them towards
the stuck items for restart purposes. Without this we could hammer on stuck
items without ever forcing the log and not make progress under heavy random
delete workloads on fast flash storage devices.
[ Dave Chinner:
- rebase on previous patches.
- improved comments for XBF_DELWRI_Q handling
- fix XBF_ASYNC handling in queue submission (test 106 failure)
- rename delwri submit function buffer list parameters for clarity
- xfs_efd_item_push() should return XFS_ITEM_PINNED ]
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Mark Tinguely <tinguely@sgi.com>
Signed-off-by: Ben Myers <bpm@sgi.com>
2012-04-23 05:58:39 +00:00
|
|
|
xfs_buf_delwri_submit_nowait(
|
|
|
|
struct list_head *buffer_list)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2016-06-01 07:38:15 +00:00
|
|
|
return xfs_buf_delwri_submit_buffers(buffer_list, NULL);
|
xfs: on-stack delayed write buffer lists
Queue delwri buffers on a local on-stack list instead of a per-buftarg one,
and write back the buffers per-process instead of by waking up xfsbufd.
This is now easily doable given that we have very few places left that write
delwri buffers:
- log recovery:
Only done at mount time, and already forcing out the buffers
synchronously using xfs_flush_buftarg
- quotacheck:
Same story.
- dquot reclaim:
Writes out dirty dquots on the LRU under memory pressure. We might
want to look into doing more of this via xfsaild, but it's already
more optimal than the synchronous inode reclaim that writes each
buffer synchronously.
- xfsaild:
This is the main beneficiary of the change. By keeping a local list
of buffers to write we reduce latency of writing out buffers, and
more importably we can remove all the delwri list promotions which
were hitting the buffer cache hard under sustained metadata loads.
The implementation is very straight forward - xfs_buf_delwri_queue now gets
a new list_head pointer that it adds the delwri buffers to, and all callers
need to eventually submit the list using xfs_buf_delwi_submit or
xfs_buf_delwi_submit_nowait. Buffers that already are on a delwri list are
skipped in xfs_buf_delwri_queue, assuming they already are on another delwri
list. The biggest change to pass down the buffer list was done to the AIL
pushing. Now that we operate on buffers the trylock, push and pushbuf log
item methods are merged into a single push routine, which tries to lock the
item, and if possible add the buffer that needs writeback to the buffer list.
This leads to much simpler code than the previous split but requires the
individual IOP_PUSH instances to unlock and reacquire the AIL around calls
to blocking routines.
Given that xfsailds now also handle writing out buffers, the conditions for
log forcing and the sleep times needed some small changes. The most
important one is that we consider an AIL busy as long we still have buffers
to push, and the other one is that we do increment the pushed LSN for
buffers that are under flushing at this moment, but still count them towards
the stuck items for restart purposes. Without this we could hammer on stuck
items without ever forcing the log and not make progress under heavy random
delete workloads on fast flash storage devices.
[ Dave Chinner:
- rebase on previous patches.
- improved comments for XBF_DELWRI_Q handling
- fix XBF_ASYNC handling in queue submission (test 106 failure)
- rename delwri submit function buffer list parameters for clarity
- xfs_efd_item_push() should return XFS_ITEM_PINNED ]
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Mark Tinguely <tinguely@sgi.com>
Signed-off-by: Ben Myers <bpm@sgi.com>
2012-04-23 05:58:39 +00:00
|
|
|
}
|
2005-04-16 22:20:36 +00:00
|
|
|
|
xfs: on-stack delayed write buffer lists
Queue delwri buffers on a local on-stack list instead of a per-buftarg one,
and write back the buffers per-process instead of by waking up xfsbufd.
This is now easily doable given that we have very few places left that write
delwri buffers:
- log recovery:
Only done at mount time, and already forcing out the buffers
synchronously using xfs_flush_buftarg
- quotacheck:
Same story.
- dquot reclaim:
Writes out dirty dquots on the LRU under memory pressure. We might
want to look into doing more of this via xfsaild, but it's already
more optimal than the synchronous inode reclaim that writes each
buffer synchronously.
- xfsaild:
This is the main beneficiary of the change. By keeping a local list
of buffers to write we reduce latency of writing out buffers, and
more importably we can remove all the delwri list promotions which
were hitting the buffer cache hard under sustained metadata loads.
The implementation is very straight forward - xfs_buf_delwri_queue now gets
a new list_head pointer that it adds the delwri buffers to, and all callers
need to eventually submit the list using xfs_buf_delwi_submit or
xfs_buf_delwi_submit_nowait. Buffers that already are on a delwri list are
skipped in xfs_buf_delwri_queue, assuming they already are on another delwri
list. The biggest change to pass down the buffer list was done to the AIL
pushing. Now that we operate on buffers the trylock, push and pushbuf log
item methods are merged into a single push routine, which tries to lock the
item, and if possible add the buffer that needs writeback to the buffer list.
This leads to much simpler code than the previous split but requires the
individual IOP_PUSH instances to unlock and reacquire the AIL around calls
to blocking routines.
Given that xfsailds now also handle writing out buffers, the conditions for
log forcing and the sleep times needed some small changes. The most
important one is that we consider an AIL busy as long we still have buffers
to push, and the other one is that we do increment the pushed LSN for
buffers that are under flushing at this moment, but still count them towards
the stuck items for restart purposes. Without this we could hammer on stuck
items without ever forcing the log and not make progress under heavy random
delete workloads on fast flash storage devices.
[ Dave Chinner:
- rebase on previous patches.
- improved comments for XBF_DELWRI_Q handling
- fix XBF_ASYNC handling in queue submission (test 106 failure)
- rename delwri submit function buffer list parameters for clarity
- xfs_efd_item_push() should return XFS_ITEM_PINNED ]
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Mark Tinguely <tinguely@sgi.com>
Signed-off-by: Ben Myers <bpm@sgi.com>
2012-04-23 05:58:39 +00:00
|
|
|
/*
|
|
|
|
* Write out a buffer list synchronously.
|
|
|
|
*
|
|
|
|
* This will take the @buffer_list, write all buffers out and wait for I/O
|
|
|
|
* completion on all of the buffers. @buffer_list is consumed by the function,
|
|
|
|
* so callers must have some other way of tracking buffers if they require such
|
|
|
|
* functionality.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
xfs_buf_delwri_submit(
|
|
|
|
struct list_head *buffer_list)
|
|
|
|
{
|
2016-06-01 07:38:15 +00:00
|
|
|
LIST_HEAD (wait_list);
|
xfs: on-stack delayed write buffer lists
Queue delwri buffers on a local on-stack list instead of a per-buftarg one,
and write back the buffers per-process instead of by waking up xfsbufd.
This is now easily doable given that we have very few places left that write
delwri buffers:
- log recovery:
Only done at mount time, and already forcing out the buffers
synchronously using xfs_flush_buftarg
- quotacheck:
Same story.
- dquot reclaim:
Writes out dirty dquots on the LRU under memory pressure. We might
want to look into doing more of this via xfsaild, but it's already
more optimal than the synchronous inode reclaim that writes each
buffer synchronously.
- xfsaild:
This is the main beneficiary of the change. By keeping a local list
of buffers to write we reduce latency of writing out buffers, and
more importably we can remove all the delwri list promotions which
were hitting the buffer cache hard under sustained metadata loads.
The implementation is very straight forward - xfs_buf_delwri_queue now gets
a new list_head pointer that it adds the delwri buffers to, and all callers
need to eventually submit the list using xfs_buf_delwi_submit or
xfs_buf_delwi_submit_nowait. Buffers that already are on a delwri list are
skipped in xfs_buf_delwri_queue, assuming they already are on another delwri
list. The biggest change to pass down the buffer list was done to the AIL
pushing. Now that we operate on buffers the trylock, push and pushbuf log
item methods are merged into a single push routine, which tries to lock the
item, and if possible add the buffer that needs writeback to the buffer list.
This leads to much simpler code than the previous split but requires the
individual IOP_PUSH instances to unlock and reacquire the AIL around calls
to blocking routines.
Given that xfsailds now also handle writing out buffers, the conditions for
log forcing and the sleep times needed some small changes. The most
important one is that we consider an AIL busy as long we still have buffers
to push, and the other one is that we do increment the pushed LSN for
buffers that are under flushing at this moment, but still count them towards
the stuck items for restart purposes. Without this we could hammer on stuck
items without ever forcing the log and not make progress under heavy random
delete workloads on fast flash storage devices.
[ Dave Chinner:
- rebase on previous patches.
- improved comments for XBF_DELWRI_Q handling
- fix XBF_ASYNC handling in queue submission (test 106 failure)
- rename delwri submit function buffer list parameters for clarity
- xfs_efd_item_push() should return XFS_ITEM_PINNED ]
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Mark Tinguely <tinguely@sgi.com>
Signed-off-by: Ben Myers <bpm@sgi.com>
2012-04-23 05:58:39 +00:00
|
|
|
int error = 0, error2;
|
|
|
|
struct xfs_buf *bp;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2016-06-01 07:38:15 +00:00
|
|
|
xfs_buf_delwri_submit_buffers(buffer_list, &wait_list);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
xfs: on-stack delayed write buffer lists
Queue delwri buffers on a local on-stack list instead of a per-buftarg one,
and write back the buffers per-process instead of by waking up xfsbufd.
This is now easily doable given that we have very few places left that write
delwri buffers:
- log recovery:
Only done at mount time, and already forcing out the buffers
synchronously using xfs_flush_buftarg
- quotacheck:
Same story.
- dquot reclaim:
Writes out dirty dquots on the LRU under memory pressure. We might
want to look into doing more of this via xfsaild, but it's already
more optimal than the synchronous inode reclaim that writes each
buffer synchronously.
- xfsaild:
This is the main beneficiary of the change. By keeping a local list
of buffers to write we reduce latency of writing out buffers, and
more importably we can remove all the delwri list promotions which
were hitting the buffer cache hard under sustained metadata loads.
The implementation is very straight forward - xfs_buf_delwri_queue now gets
a new list_head pointer that it adds the delwri buffers to, and all callers
need to eventually submit the list using xfs_buf_delwi_submit or
xfs_buf_delwi_submit_nowait. Buffers that already are on a delwri list are
skipped in xfs_buf_delwri_queue, assuming they already are on another delwri
list. The biggest change to pass down the buffer list was done to the AIL
pushing. Now that we operate on buffers the trylock, push and pushbuf log
item methods are merged into a single push routine, which tries to lock the
item, and if possible add the buffer that needs writeback to the buffer list.
This leads to much simpler code than the previous split but requires the
individual IOP_PUSH instances to unlock and reacquire the AIL around calls
to blocking routines.
Given that xfsailds now also handle writing out buffers, the conditions for
log forcing and the sleep times needed some small changes. The most
important one is that we consider an AIL busy as long we still have buffers
to push, and the other one is that we do increment the pushed LSN for
buffers that are under flushing at this moment, but still count them towards
the stuck items for restart purposes. Without this we could hammer on stuck
items without ever forcing the log and not make progress under heavy random
delete workloads on fast flash storage devices.
[ Dave Chinner:
- rebase on previous patches.
- improved comments for XBF_DELWRI_Q handling
- fix XBF_ASYNC handling in queue submission (test 106 failure)
- rename delwri submit function buffer list parameters for clarity
- xfs_efd_item_push() should return XFS_ITEM_PINNED ]
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Mark Tinguely <tinguely@sgi.com>
Signed-off-by: Ben Myers <bpm@sgi.com>
2012-04-23 05:58:39 +00:00
|
|
|
/* Wait for IO to complete. */
|
2016-06-01 07:38:15 +00:00
|
|
|
while (!list_empty(&wait_list)) {
|
|
|
|
bp = list_first_entry(&wait_list, struct xfs_buf, b_list);
|
2011-03-30 11:05:09 +00:00
|
|
|
|
xfs: force all buffers to be written during btree bulk load
While stress-testing online repair of btrees, I noticed periodic
assertion failures from the buffer cache about buffers with incorrect
DELWRI_Q state. Looking further, I observed this race between the AIL
trying to write out a btree block and repair zapping a btree block after
the fact:
AIL: Repair0:
pin buffer X
delwri_queue:
set DELWRI_Q
add to delwri list
stale buf X:
clear DELWRI_Q
does not clear b_list
free space X
commit
delwri_submit # oops
Worse yet, I discovered that running the same repair over and over in a
tight loop can result in a second race that cause data integrity
problems with the repair:
AIL: Repair0: Repair1:
pin buffer X
delwri_queue:
set DELWRI_Q
add to delwri list
stale buf X:
clear DELWRI_Q
does not clear b_list
free space X
commit
find free space X
get buffer
rewrite buffer
delwri_queue:
set DELWRI_Q
already on a list, do not add
commit
BAD: committed tree root before all blocks written
delwri_submit # too late now
I traced this to my own misunderstanding of how the delwri lists work,
particularly with regards to the AIL's buffer list. If a buffer is
logged and committed, the buffer can end up on that AIL buffer list. If
btree repairs are run twice in rapid succession, it's possible that the
first repair will invalidate the buffer and free it before the next time
the AIL wakes up. Marking the buffer stale clears DELWRI_Q from the
buffer state without removing the buffer from its delwri list. The
buffer doesn't know which list it's on, so it cannot know which lock to
take to protect the list for a removal.
If the second repair allocates the same block, it will then recycle the
buffer to start writing the new btree block. Meanwhile, if the AIL
wakes up and walks the buffer list, it will ignore the buffer because it
can't lock it, and go back to sleep.
When the second repair calls delwri_queue to put the buffer on the
list of buffers to write before committing the new btree, it will set
DELWRI_Q again, but since the buffer hasn't been removed from the AIL's
buffer list, it won't add it to the bulkload buffer's list.
This is incorrect, because the bulkload caller relies on delwri_submit
to ensure that all the buffers have been sent to disk /before/
committing the new btree root pointer. This ordering requirement is
required for data consistency.
Worse, the AIL won't clear DELWRI_Q from the buffer when it does finally
drop it, so the next thread to walk through the btree will trip over a
debug assertion on that flag.
To fix this, create a new function that waits for the buffer to be
removed from any other delwri lists before adding the buffer to the
caller's delwri list. By waiting for the buffer to clear both the
delwri list and any potential delwri wait list, we can be sure that
repair will initiate writes of all buffers and report all write errors
back to userspace instead of committing the new structure.
Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Christoph Hellwig <hch@lst.de>
2023-12-15 18:03:27 +00:00
|
|
|
xfs_buf_list_del(bp);
|
2014-10-01 23:04:01 +00:00
|
|
|
|
2018-07-12 05:26:34 +00:00
|
|
|
/*
|
|
|
|
* Wait on the locked buffer, check for errors and unlock and
|
|
|
|
* release the delwri queue reference.
|
|
|
|
*/
|
|
|
|
error2 = xfs_buf_iowait(bp);
|
xfs: on-stack delayed write buffer lists
Queue delwri buffers on a local on-stack list instead of a per-buftarg one,
and write back the buffers per-process instead of by waking up xfsbufd.
This is now easily doable given that we have very few places left that write
delwri buffers:
- log recovery:
Only done at mount time, and already forcing out the buffers
synchronously using xfs_flush_buftarg
- quotacheck:
Same story.
- dquot reclaim:
Writes out dirty dquots on the LRU under memory pressure. We might
want to look into doing more of this via xfsaild, but it's already
more optimal than the synchronous inode reclaim that writes each
buffer synchronously.
- xfsaild:
This is the main beneficiary of the change. By keeping a local list
of buffers to write we reduce latency of writing out buffers, and
more importably we can remove all the delwri list promotions which
were hitting the buffer cache hard under sustained metadata loads.
The implementation is very straight forward - xfs_buf_delwri_queue now gets
a new list_head pointer that it adds the delwri buffers to, and all callers
need to eventually submit the list using xfs_buf_delwi_submit or
xfs_buf_delwi_submit_nowait. Buffers that already are on a delwri list are
skipped in xfs_buf_delwri_queue, assuming they already are on another delwri
list. The biggest change to pass down the buffer list was done to the AIL
pushing. Now that we operate on buffers the trylock, push and pushbuf log
item methods are merged into a single push routine, which tries to lock the
item, and if possible add the buffer that needs writeback to the buffer list.
This leads to much simpler code than the previous split but requires the
individual IOP_PUSH instances to unlock and reacquire the AIL around calls
to blocking routines.
Given that xfsailds now also handle writing out buffers, the conditions for
log forcing and the sleep times needed some small changes. The most
important one is that we consider an AIL busy as long we still have buffers
to push, and the other one is that we do increment the pushed LSN for
buffers that are under flushing at this moment, but still count them towards
the stuck items for restart purposes. Without this we could hammer on stuck
items without ever forcing the log and not make progress under heavy random
delete workloads on fast flash storage devices.
[ Dave Chinner:
- rebase on previous patches.
- improved comments for XBF_DELWRI_Q handling
- fix XBF_ASYNC handling in queue submission (test 106 failure)
- rename delwri submit function buffer list parameters for clarity
- xfs_efd_item_push() should return XFS_ITEM_PINNED ]
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Mark Tinguely <tinguely@sgi.com>
Signed-off-by: Ben Myers <bpm@sgi.com>
2012-04-23 05:58:39 +00:00
|
|
|
xfs_buf_relse(bp);
|
|
|
|
if (!error)
|
|
|
|
error = error2;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
xfs: on-stack delayed write buffer lists
Queue delwri buffers on a local on-stack list instead of a per-buftarg one,
and write back the buffers per-process instead of by waking up xfsbufd.
This is now easily doable given that we have very few places left that write
delwri buffers:
- log recovery:
Only done at mount time, and already forcing out the buffers
synchronously using xfs_flush_buftarg
- quotacheck:
Same story.
- dquot reclaim:
Writes out dirty dquots on the LRU under memory pressure. We might
want to look into doing more of this via xfsaild, but it's already
more optimal than the synchronous inode reclaim that writes each
buffer synchronously.
- xfsaild:
This is the main beneficiary of the change. By keeping a local list
of buffers to write we reduce latency of writing out buffers, and
more importably we can remove all the delwri list promotions which
were hitting the buffer cache hard under sustained metadata loads.
The implementation is very straight forward - xfs_buf_delwri_queue now gets
a new list_head pointer that it adds the delwri buffers to, and all callers
need to eventually submit the list using xfs_buf_delwi_submit or
xfs_buf_delwi_submit_nowait. Buffers that already are on a delwri list are
skipped in xfs_buf_delwri_queue, assuming they already are on another delwri
list. The biggest change to pass down the buffer list was done to the AIL
pushing. Now that we operate on buffers the trylock, push and pushbuf log
item methods are merged into a single push routine, which tries to lock the
item, and if possible add the buffer that needs writeback to the buffer list.
This leads to much simpler code than the previous split but requires the
individual IOP_PUSH instances to unlock and reacquire the AIL around calls
to blocking routines.
Given that xfsailds now also handle writing out buffers, the conditions for
log forcing and the sleep times needed some small changes. The most
important one is that we consider an AIL busy as long we still have buffers
to push, and the other one is that we do increment the pushed LSN for
buffers that are under flushing at this moment, but still count them towards
the stuck items for restart purposes. Without this we could hammer on stuck
items without ever forcing the log and not make progress under heavy random
delete workloads on fast flash storage devices.
[ Dave Chinner:
- rebase on previous patches.
- improved comments for XBF_DELWRI_Q handling
- fix XBF_ASYNC handling in queue submission (test 106 failure)
- rename delwri submit function buffer list parameters for clarity
- xfs_efd_item_push() should return XFS_ITEM_PINNED ]
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Mark Tinguely <tinguely@sgi.com>
Signed-off-by: Ben Myers <bpm@sgi.com>
2012-04-23 05:58:39 +00:00
|
|
|
return error;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
2017-06-15 04:21:45 +00:00
|
|
|
/*
|
|
|
|
* Push a single buffer on a delwri queue.
|
|
|
|
*
|
|
|
|
* The purpose of this function is to submit a single buffer of a delwri queue
|
|
|
|
* and return with the buffer still on the original queue. The waiting delwri
|
|
|
|
* buffer submission infrastructure guarantees transfer of the delwri queue
|
|
|
|
* buffer reference to a temporary wait list. We reuse this infrastructure to
|
|
|
|
* transfer the buffer back to the original queue.
|
|
|
|
*
|
|
|
|
* Note the buffer transitions from the queued state, to the submitted and wait
|
|
|
|
* listed state and back to the queued state during this call. The buffer
|
|
|
|
* locking and queue management logic between _delwri_pushbuf() and
|
|
|
|
* _delwri_queue() guarantee that the buffer cannot be queued to another list
|
|
|
|
* before returning.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
xfs_buf_delwri_pushbuf(
|
|
|
|
struct xfs_buf *bp,
|
|
|
|
struct list_head *buffer_list)
|
|
|
|
{
|
|
|
|
LIST_HEAD (submit_list);
|
|
|
|
int error;
|
|
|
|
|
|
|
|
ASSERT(bp->b_flags & _XBF_DELWRI_Q);
|
|
|
|
|
|
|
|
trace_xfs_buf_delwri_pushbuf(bp, _RET_IP_);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Isolate the buffer to a new local list so we can submit it for I/O
|
|
|
|
* independently from the rest of the original list.
|
|
|
|
*/
|
|
|
|
xfs_buf_lock(bp);
|
|
|
|
list_move(&bp->b_list, &submit_list);
|
|
|
|
xfs_buf_unlock(bp);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Delwri submission clears the DELWRI_Q buffer flag and returns with
|
2018-07-12 05:26:34 +00:00
|
|
|
* the buffer on the wait list with the original reference. Rather than
|
2017-06-15 04:21:45 +00:00
|
|
|
* bounce the buffer from a local wait list back to the original list
|
|
|
|
* after I/O completion, reuse the original list as the wait list.
|
|
|
|
*/
|
|
|
|
xfs_buf_delwri_submit_buffers(&submit_list, buffer_list);
|
|
|
|
|
|
|
|
/*
|
2018-07-12 05:26:34 +00:00
|
|
|
* The buffer is now locked, under I/O and wait listed on the original
|
|
|
|
* delwri queue. Wait for I/O completion, restore the DELWRI_Q flag and
|
|
|
|
* return with the buffer unlocked and on the original queue.
|
2017-06-15 04:21:45 +00:00
|
|
|
*/
|
2018-07-12 05:26:34 +00:00
|
|
|
error = xfs_buf_iowait(bp);
|
2017-06-15 04:21:45 +00:00
|
|
|
bp->b_flags |= _XBF_DELWRI_Q;
|
|
|
|
xfs_buf_unlock(bp);
|
|
|
|
|
|
|
|
return error;
|
|
|
|
}
|
|
|
|
|
2017-10-17 21:16:29 +00:00
|
|
|
void xfs_buf_set_ref(struct xfs_buf *bp, int lru_ref)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Set the lru reference count to 0 based on the error injection tag.
|
|
|
|
* This allows userspace to disrupt buffer caching for debug/testing
|
|
|
|
* purposes.
|
|
|
|
*/
|
2019-06-29 02:27:29 +00:00
|
|
|
if (XFS_TEST_ERROR(false, bp->b_mount, XFS_ERRTAG_BUF_LRU_REF))
|
2017-10-17 21:16:29 +00:00
|
|
|
lru_ref = 0;
|
|
|
|
|
|
|
|
atomic_set(&bp->b_lru_ref, lru_ref);
|
|
|
|
}
|
2019-02-07 18:45:46 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Verify an on-disk magic value against the magic value specified in the
|
|
|
|
* verifier structure. The verifier magic is in disk byte order so the caller is
|
|
|
|
* expected to pass the value directly from disk.
|
|
|
|
*/
|
|
|
|
bool
|
|
|
|
xfs_verify_magic(
|
|
|
|
struct xfs_buf *bp,
|
2019-02-16 19:47:28 +00:00
|
|
|
__be32 dmagic)
|
2019-02-07 18:45:46 +00:00
|
|
|
{
|
2019-06-29 02:27:29 +00:00
|
|
|
struct xfs_mount *mp = bp->b_mount;
|
2019-02-07 18:45:46 +00:00
|
|
|
int idx;
|
|
|
|
|
2021-08-19 01:46:37 +00:00
|
|
|
idx = xfs_has_crc(mp);
|
2019-09-25 23:49:37 +00:00
|
|
|
if (WARN_ON(!bp->b_ops || !bp->b_ops->magic[idx]))
|
2019-02-07 18:45:46 +00:00
|
|
|
return false;
|
|
|
|
return dmagic == bp->b_ops->magic[idx];
|
|
|
|
}
|
2019-02-16 19:47:28 +00:00
|
|
|
/*
|
|
|
|
* Verify an on-disk magic value against the magic value specified in the
|
|
|
|
* verifier structure. The verifier magic is in disk byte order so the caller is
|
|
|
|
* expected to pass the value directly from disk.
|
|
|
|
*/
|
|
|
|
bool
|
|
|
|
xfs_verify_magic16(
|
|
|
|
struct xfs_buf *bp,
|
|
|
|
__be16 dmagic)
|
|
|
|
{
|
2019-06-29 02:27:29 +00:00
|
|
|
struct xfs_mount *mp = bp->b_mount;
|
2019-02-16 19:47:28 +00:00
|
|
|
int idx;
|
|
|
|
|
2021-08-19 01:46:37 +00:00
|
|
|
idx = xfs_has_crc(mp);
|
2019-09-25 23:49:37 +00:00
|
|
|
if (WARN_ON(!bp->b_ops || !bp->b_ops->magic16[idx]))
|
2019-02-16 19:47:28 +00:00
|
|
|
return false;
|
|
|
|
return dmagic == bp->b_ops->magic16[idx];
|
|
|
|
}
|