From: David Howells <dhowells@redhat.com>
To: Christian Brauner <christian@brauner.io>,
Matthew Wilcox <willy@infradead.org>,
Christoph Hellwig <hch@infradead.org>
Cc: David Howells <dhowells@redhat.com>,
Paulo Alcantara <pc@manguebit.org>, Jens Axboe <axboe@kernel.dk>,
Leon Romanovsky <leon@kernel.org>,
Steve French <sfrench@samba.org>,
ChenXiaoSong <chenxiaosong@chenxiaosong.com>,
Marc Dionne <marc.dionne@auristor.com>,
Stefan Metzmacher <metze@samba.org>,
Eric Van Hensbergen <ericvh@kernel.org>,
Dominique Martinet <asmadeus@codewreck.org>,
Ilya Dryomov <idryomov@gmail.com>,
netfs@lists.linux.dev, linux-afs@lists.infradead.org,
linux-cifs@vger.kernel.org, linux-nfs@vger.kernel.org,
ceph-devel@vger.kernel.org, v9fs@lists.linux.dev,
linux-erofs@lists.ozlabs.org, linux-fsdevel@vger.kernel.org,
linux-kernel@vger.kernel.org
Subject: [PATCH v9 23/26] netfs: Simplify read abandonment
Date: Mon, 10 Aug 2026 15:47:40 +0100 [thread overview]
Message-ID: <20260810144746.574036-24-dhowells@redhat.com> (raw)
In-Reply-To: <20260810144746.574036-1-dhowells@redhat.com>
Currently, if one or more of the subrequests in a read request fails, the
read collection algorithm will attempt to salvage folios that are fully
downloaded but that span multiple subreqs, provided all of the contributory
subreqs succeeded, but this makes the algorithm quite complicated as a
subreq can contribute to multiple folios and a folio can be contributed to
by multiple subreqs.
Simplify this by just abandoning the rest of a read request once a
permanently failed subreq is hit. This is what read_folio, DIO/unbuffered
read, gap filling, RMW and write preload all need to do; readahead is the
odd one out as it doesn't have any return other than unlocking folios.
With this change, even for readahead, the rest of the read is simply
abandoned; read() and suchlike will call ->read_folio() on each
non-uptodate folio to retry and retrieve the error.
Note that retryable failures still get retried by netfslib as part of the
request as before.
Signed-off-by: David Howells <dhowells@redhat.com>
cc: Paulo Alcantara <pc@manguebit.org>
cc: netfs@lists.linux.dev
cc: linux-fsdevel@vger.kernel.org
---
fs/netfs/read_collect.c | 190 ++++++++++++++++++++++++++++------------
include/linux/netfs.h | 1 +
2 files changed, 137 insertions(+), 54 deletions(-)
diff --git a/fs/netfs/read_collect.c b/fs/netfs/read_collect.c
index 5d0a8a75ce26..cf7c36037edc 100644
--- a/fs/netfs/read_collect.c
+++ b/fs/netfs/read_collect.c
@@ -19,8 +19,8 @@
#define MADE_PROGRESS 0x04 /* Made progress cleaning up a stream or the folio set */
#define BUFFERED 0x08 /* The pagecache needs cleaning up */
#define NEED_RETRY 0x10 /* A front op requests retrying */
-#define COPY_TO_CACHE 0x40 /* Need to copy subrequest to cache */
-#define ABANDON_SREQ 0x80 /* Need to abandon untransferred part of subrequest */
+#define COPY_TO_CACHE 0x20 /* Need to copy subrequest to cache */
+#define ABANDON_RREQ 0x40 /* Need to abandon the rest of a request */
/*
* Clear the unread part of an I/O request.
@@ -174,6 +174,78 @@ static void netfs_read_unlock_folios(struct netfs_io_request *rreq,
rreq->collect_cursor.slot = slot;
}
+/*
+ * Abandon all remaining read results. Once we've hit a permanent failure, we
+ * assume that the file is probably unusable. In the event of readahead, in
+ * theory we might manage to read some data later in the file, that we could
+ * still unlock, but ->read_folio() will be called again.
+ */
+static void netfs_abandon_read_results(struct netfs_io_request *rreq)
+{
+ struct netfs_io_stream *stream = &rreq->io_streams[0];
+ unsigned int notes = ABANDON_RREQ;
+
+ _enter("%llx-%llx", rreq->start, rreq->start + rreq->len);
+ trace_netfs_rreq(rreq, netfs_rreq_trace_collect);
+ trace_netfs_collect(rreq);
+
+ if (rreq->origin == NETFS_READAHEAD ||
+ rreq->origin == NETFS_READPAGE ||
+ rreq->origin == NETFS_READ_FOR_WRITE)
+ notes |= BUFFERED;
+
+ /* Remove completed subrequests from the front of the stream and
+ * advance the completion point. We stop when we hit something that's
+ * in progress. The issuer thread may be adding stuff to the tail
+ * whilst we're doing this.
+ */
+ for (;;) {
+ struct netfs_io_subrequest *front;
+ unsigned long front_flags;
+
+ front = list_first_entry_or_null_acquire(&stream->subrequests,
+ struct netfs_io_subrequest, rreq_link);
+ /* Read first subreq pointer before IN_PROGRESS flag. */
+ if (!front)
+ break;
+
+ front_flags = smp_load_acquire(&front->flags);
+ /* Order read of flags before read of anything else, such as error. */
+
+ /* Wait for each subreq to complete. */
+ if (test_bit(NETFS_SREQ_IN_PROGRESS, &front_flags)) {
+ notes |= HIT_PENDING;
+ break;
+ }
+
+ /* The subreq now belongs to us. */
+ if (!stream->failed) {
+ stream->failed = true;
+ stream->error = front->error;
+ rreq->error = front->error;
+ trace_netfs_rreq(rreq, netfs_rreq_trace_set_abandon);
+ }
+
+ stream->collected_to = front->start + front->len;
+ trace_netfs_sreq(front, netfs_sreq_trace_abandoned);
+
+ spin_lock(&rreq->lock);
+ list_del_init(&front->rreq_link);
+ spin_unlock(&rreq->lock);
+ netfs_put_subrequest(front, netfs_sreq_trace_put_abandon);
+ }
+
+ rreq->collected_to = stream->collected_to;
+ rreq->abandon_to = rreq->collected_to;
+ if (notes & BUFFERED)
+ netfs_read_unlock_folios(rreq, ¬es);
+ else
+ rreq->cleaned_to = rreq->collected_to;
+
+ trace_netfs_collect_stream(rreq, stream);
+ trace_netfs_collect_state(rreq, rreq->collected_to, notes);
+}
+
/*
* Collect and assess the results of various read subrequests. We may need to
* retry some of the results.
@@ -193,6 +265,9 @@ static void netfs_collect_read_results(struct netfs_io_request *rreq)
trace_netfs_collect(rreq);
reassess:
+ if (test_bit(NETFS_RREQ_ABANDON_REQ, &rreq->flags))
+ goto abandon_request;
+
if (rreq->origin == NETFS_READAHEAD ||
rreq->origin == NETFS_READPAGE ||
rreq->origin == NETFS_READ_FOR_WRITE)
@@ -210,7 +285,9 @@ static void netfs_collect_read_results(struct netfs_io_request *rreq)
/* Read first subreq pointer before IN_PROGRESS flag. */
while (front) {
+ unsigned long front_flags;
size_t transferred;
+ uoff_t unlock_at = rreq->start + rreq->progress_at;
trace_netfs_collect_sreq(rreq, front);
_debug("sreq [%x] %llx %zx/%zx",
@@ -221,25 +298,63 @@ static void netfs_collect_read_results(struct netfs_io_request *rreq)
stream->collected_to = front->start;
}
- if (netfs_check_subreq_in_progress(front))
+ front_flags = smp_load_acquire(&front->flags);
+ /* Order read of flags before read of anything else, such as error. */
+
+ if (test_bit(NETFS_SREQ_FAILED, &front_flags))
+ goto abandon_request;
+ if (test_bit(NETFS_SREQ_IN_PROGRESS, &front_flags))
notes |= HIT_PENDING;
- smp_rmb(); /* Read counters after IN_PROGRESS flag. */
+
transferred = READ_ONCE(front->transferred);
+ /* If we can collect the next folio from a pending op, do so,
+ * but we should only do it if we don't otherwise need to wait
+ * for completion.
+ */
+ if ((notes & HIT_PENDING) &&
+ (notes & BUFFERED) &&
+ !test_bit(NETFS_SREQ_HIT_EOF, &front_flags) &&
+ front->error == 0 &&
+ transferred < front->len
+ ) {
+ stream->collected_to = front->start + transferred;
+ rreq->collected_to = stream->collected_to;
+
+ if (test_bit(NETFS_SREQ_COPY_TO_CACHE, &front_flags))
+ notes |= COPY_TO_CACHE;
+ if (front->start + transferred >= unlock_at)
+ netfs_read_unlock_folios(rreq, ¬es);
+ }
+
+ /* Stall if the front is still undergoing I/O. */
+ if (notes & HIT_PENDING)
+ break;
+
+ if (test_bit(NETFS_SREQ_NEED_RETRY, &front_flags)) {
+ stream->need_retry = true;
+ notes |= NEED_RETRY | MADE_PROGRESS;
+ break;
+ } else if (test_bit(NETFS_RREQ_SHORT_TRANSFER, &rreq->flags)) {
+ notes |= MADE_PROGRESS;
+ } else {
+ stream->transferred += transferred;
+ stream->transferred_valid = true;
+ if (front->transferred < front->len)
+ set_bit(NETFS_RREQ_SHORT_TRANSFER, &rreq->flags);
+ notes |= MADE_PROGRESS;
+ }
+
/* If we can now collect the next folio, do so. We don't want
* to defer this as we have to decide whether we need to copy
* to the cache or not, and that may differ between adjacent
* subreqs.
*/
if (notes & BUFFERED) {
- uoff_t unlock_at = rreq->start + rreq->progress_at;
-
/* Clear the tail of a short read. */
- if (!(notes & HIT_PENDING) &&
- front->error == 0 &&
- transferred < front->len &&
- (test_bit(NETFS_SREQ_HIT_EOF, &front->flags) ||
- test_bit(NETFS_SREQ_CLEAR_TAIL, &front->flags))) {
+ if (transferred < front->len &&
+ (test_bit(NETFS_SREQ_HIT_EOF, &front_flags) ||
+ test_bit(NETFS_SREQ_CLEAR_TAIL, &front_flags))) {
netfs_clear_unread(front);
transferred = front->transferred = front->len;
trace_netfs_sreq(front, netfs_sreq_trace_clear);
@@ -248,67 +363,28 @@ static void netfs_collect_read_results(struct netfs_io_request *rreq)
stream->collected_to = front->start + transferred;
rreq->collected_to = stream->collected_to;
- if (test_bit(NETFS_SREQ_COPY_TO_CACHE, &front->flags))
+ if (test_bit(NETFS_SREQ_COPY_TO_CACHE, &front_flags))
notes |= COPY_TO_CACHE;
- if (test_bit(NETFS_SREQ_FAILED, &front->flags)) {
- rreq->abandon_to = front->start + front->len;
- front->transferred = front->len;
- transferred = front->len;
- trace_netfs_rreq(rreq, netfs_rreq_trace_set_abandon);
- }
if (front->start + transferred >= unlock_at ||
- test_bit(NETFS_SREQ_HIT_EOF, &front->flags))
+ test_bit(NETFS_SREQ_HIT_EOF, &front_flags))
netfs_read_unlock_folios(rreq, ¬es);
} else {
stream->collected_to = front->start + transferred;
rreq->collected_to = stream->collected_to;
}
- /* Stall if the front is still undergoing I/O. */
- if (notes & HIT_PENDING)
- break;
-
- if (test_bit(NETFS_SREQ_FAILED, &front->flags)) {
- if (!stream->failed) {
- stream->error = front->error;
- rreq->error = front->error;
- set_bit(NETFS_RREQ_FAILED, &rreq->flags);
- stream->failed = true;
- }
- notes |= MADE_PROGRESS | ABANDON_SREQ;
- } else if (test_bit(NETFS_SREQ_NEED_RETRY, &front->flags)) {
- stream->need_retry = true;
- notes |= NEED_RETRY | MADE_PROGRESS;
- break;
- } else if (test_bit(NETFS_RREQ_SHORT_TRANSFER, &rreq->flags)) {
- notes |= MADE_PROGRESS;
- } else {
- if (!stream->failed) {
- stream->transferred += transferred;
- stream->transferred_valid = true;
- }
- if (front->transferred < front->len)
- set_bit(NETFS_RREQ_SHORT_TRANSFER, &rreq->flags);
- notes |= MADE_PROGRESS;
- }
-
/* Remove if completely consumed. */
stream->source = front->source;
spin_lock(&rreq->lock);
remove = front;
- trace_netfs_sreq(front,
- notes & ABANDON_SREQ ?
- netfs_sreq_trace_abandoned : netfs_sreq_trace_consumed);
+ trace_netfs_sreq(front, netfs_sreq_trace_consumed);
list_del_init(&front->rreq_link);
front = list_first_entry_or_null(&stream->subrequests,
struct netfs_io_subrequest, rreq_link);
spin_unlock(&rreq->lock);
- netfs_put_subrequest(remove,
- notes & ABANDON_SREQ ?
- netfs_sreq_trace_put_abandon :
- netfs_sreq_trace_put_done);
+ netfs_put_subrequest(remove, netfs_sreq_trace_put_done);
}
trace_netfs_collect_stream(rreq, stream);
@@ -337,6 +413,12 @@ static void netfs_collect_read_results(struct netfs_io_request *rreq)
_debug("retry");
netfs_retry_reads(rreq);
goto out;
+
+abandon_request:
+ set_bit(NETFS_RREQ_FAILED, &rreq->flags);
+ set_bit(NETFS_RREQ_ABANDON_REQ, &rreq->flags);
+ netfs_wake_rreq_flag(rreq, NETFS_RREQ_PAUSE, netfs_rreq_trace_unpause);
+ return netfs_abandon_read_results(rreq);
}
/*
diff --git a/include/linux/netfs.h b/include/linux/netfs.h
index 43696fc67a2a..f277c187c408 100644
--- a/include/linux/netfs.h
+++ b/include/linux/netfs.h
@@ -281,6 +281,7 @@ struct netfs_io_request {
#define NETFS_RREQ_FAILED 3 /* The request failed */
#define NETFS_RREQ_RETRYING 4 /* Set if we're in the retry path */
#define NETFS_RREQ_SHORT_TRANSFER 5 /* Set if we have a short transfer */
+#define NETFS_RREQ_ABANDON_REQ 6 /* Set if the request is to be abandoned */
#define NETFS_RREQ_CACHE_STOP 8 /* Set to stop caching (ENOBUFS or error) */
#define NETFS_RREQ_CACHE_ERROR 9 /* Set if we got an error from the cache */
#define NETFS_RREQ_OFFLOAD_COLLECTION 12 /* Offload collection to workqueue */
next prev parent reply other threads:[~2026-08-10 14:51 UTC|newest]
Thread overview: 32+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-10 14:47 [PATCH v9 00/26] netfs: Keep track of folios in a segmented bio_vec[] chain David Howells
2026-08-10 14:47 ` [PATCH v9 01/26] netfs: Fix read progress reporting David Howells
2026-08-10 14:47 ` [PATCH v9 02/26] mm: Make readahead store folio count in readahead_control David Howells
2026-08-10 14:47 ` [PATCH v9 03/26] netfs: Bulk load the readahead-provided folios up front David Howells
2026-08-10 14:47 ` [PATCH v9 04/26] Add a function to kmap one page of a multipage bio_vec David Howells
2026-08-10 19:36 ` Matthew Wilcox
2026-08-10 20:31 ` David Howells
2026-08-10 14:47 ` [PATCH v9 05/26] iov_iter: Make iov_iter_get_pages*() wrap iov_iter_extract_pages() David Howells
2026-08-10 14:47 ` [PATCH v9 06/26] iov_iter: Add a segmented queue of bio_vec[] David Howells
2026-08-10 14:47 ` [PATCH v9 07/26] netfs: Add some tools for managing bvecq chains David Howells
2026-08-10 14:47 ` [PATCH v9 08/26] netfs: Make mempool available for bvecq David Howells
2026-08-10 14:47 ` [PATCH v9 09/26] netfs: Add a function to extract from an iter into a bvecq David Howells
2026-08-10 14:47 ` [PATCH v9 10/26] afs: Use a bvecq to hold dir content rather than folioq David Howells
2026-08-10 14:47 ` [PATCH v9 11/26] cifs: Use a bvecq for buffering instead of a folioq David Howells
2026-08-10 14:47 ` [PATCH v9 12/26] smbdirect: Support ITER_BVECQ in smbdirect_map_sges_from_iter() David Howells
2026-08-10 14:47 ` [PATCH v9 13/26] netfs: Remove the writethrough code David Howells
2026-08-10 14:47 ` [PATCH v9 14/26] cachefiles,netfs: sunset ondemand mode David Howells
2026-08-10 15:09 ` sashiko-bot
2026-08-10 14:47 ` [PATCH v9 15/26] cachefiles: Don't rely on backing fs storage map for most use cases David Howells
2026-08-10 14:47 ` [PATCH v9 16/26] netfs: Add the cache object ID to netfs_read/write tracepoints David Howells
2026-08-10 14:47 ` [PATCH v9 17/26] netfs: Switch to using bvecq rather than folio_queue and rolling_buffer David Howells
2026-08-10 14:47 ` [PATCH v9 18/26] smbdirect: Remove support for ITER_FOLIOQ from smbdirect_map_sges_from_iter() David Howells
2026-08-10 14:47 ` [PATCH v9 19/26] netfs: Remove netfs_alloc/free_folioq_buffer() David Howells
2026-08-10 14:47 ` [PATCH v9 20/26] netfs: Remove netfs_extract_user_iter() David Howells
2026-08-10 14:47 ` [PATCH v9 21/26] iov_iter: Remove ITER_FOLIOQ David Howells
2026-08-10 14:47 ` [PATCH v9 22/26] netfs: Remove folio_queue and rolling_buffer David Howells
2026-08-10 14:47 ` David Howells [this message]
2026-08-10 14:47 ` [PATCH v9 24/26] netfs: Check for too much data being read David Howells
2026-08-10 14:47 ` [PATCH v9 25/26] netfs: Combine prepare and issue ops and grab the buffers on request David Howells
2026-08-10 14:47 ` [PATCH v9 26/26] cachefiles: Preset the state xattr when creating a new file David Howells
2026-08-10 14:51 ` [PATCH v9 00/26] netfs: Keep track of folios in a segmented bio_vec[] chain Christoph Hellwig
2026-08-11 7:26 ` Christian Brauner
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260810144746.574036-24-dhowells@redhat.com \
--to=dhowells@redhat.com \
--cc=asmadeus@codewreck.org \
--cc=axboe@kernel.dk \
--cc=ceph-devel@vger.kernel.org \
--cc=chenxiaosong@chenxiaosong.com \
--cc=christian@brauner.io \
--cc=ericvh@kernel.org \
--cc=hch@infradead.org \
--cc=idryomov@gmail.com \
--cc=leon@kernel.org \
--cc=linux-afs@lists.infradead.org \
--cc=linux-cifs@vger.kernel.org \
--cc=linux-erofs@lists.ozlabs.org \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-nfs@vger.kernel.org \
--cc=marc.dionne@auristor.com \
--cc=metze@samba.org \
--cc=netfs@lists.linux.dev \
--cc=pc@manguebit.org \
--cc=sfrench@samba.org \
--cc=v9fs@lists.linux.dev \
--cc=willy@infradead.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.