From: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
To: stable@vger.kernel.org
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
patches@lists.linux.dev, David Howells <dhowells@redhat.com>,
Paulo Alcantara <pc@manguebit.org>,
netfs@lists.linux.dev, linux-fsdevel@vger.kernel.org,
"Christian Brauner (Amutable)" <brauner@kernel.org>,
Sasha Levin <sashal@kernel.org>
Subject: [PATCH 7.2 087/733] netfs: Fix read progress reporting
Date: Thu, 17 Sep 2026 16:06:35 +0100 [thread overview]
Message-ID: <20260917151353.074841239@linuxfoundation.org> (raw)
In-Reply-To: <20260917151350.597953846@linuxfoundation.org>
7.2-stable review patch. If anyone has any objections, please let me know.
------------------
From: David Howells <dhowells@redhat.com>
[ Upstream commit e00827a4d0cfebf8d78dfd0a9a024237f57c9273 ]
For really big read RPC ops that span multiple folios, netfslib allows the
filesystem to give progress notifications to wake up the collector thread
to do a collection of folios that have now been fetched, even if the RPC is
still ongoing, thereby allowing the application to make progress.
This works by taking the current rreq->cleaned_to value (which indicates
which folios have been unlocked) and adding the stashed size of the next
folio to it. cleaned_to, however, is subject to 64-bit tearing on a 32-bit
arch.
Fix this by stashing the next progress notification point as a size_t
(which won't tear) to be added to rreq->start (which won't change), with
the collector thread calculating that from cleaned_to plus the next folio
size.
Further, however, if the folios are small, the collector thread gets
constantly woken up - which has a negative performance impact on the
system.
Fix that too by setting a minimum trigger of 256KiB or the size of the
folio at the front of the queue, whichever is larger. Note that this has
an issue that different subreqs have different need-to-be-cached
properties; this is solved by a preceding patch that marks the property on
the folios whilst issuing subreqs rather than when collecting them.
Also, make sure rreq->cleaned_to is initialised up front, along with
rreq->collected_to and stream->collected_to.
Fixes: e2d46f2ec332 ("netfs: Change the read result collector to only use one work item")
Link: https://sashiko.dev/#/patchset/20260804100224.2748935-1-dhowells%40redhat.com
Signed-off-by: David Howells <dhowells@redhat.com>
Link: https://patch.msgid.link/20260827134304.2075713-10-dhowells@redhat.com
Acked-by: Paulo Alcantara <pc@manguebit.org>
cc: Paulo Alcantara <pc@manguebit.org>
cc: netfs@lists.linux.dev
cc: linux-fsdevel@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
fs/netfs/buffered_read.c | 2 ++
fs/netfs/internal.h | 1 +
fs/netfs/objects.c | 32 +++++++++++++--------
fs/netfs/read_collect.c | 54 ++++++++++++++++++++++++++++--------
fs/netfs/read_single.c | 2 ++
include/linux/netfs.h | 2 +-
include/trace/events/netfs.h | 21 ++++++++++++++
7 files changed, 89 insertions(+), 25 deletions(-)
diff --git a/fs/netfs/buffered_read.c b/fs/netfs/buffered_read.c
index 16d4db776f6ae..424df70a5c30f 100644
--- a/fs/netfs/buffered_read.c
+++ b/fs/netfs/buffered_read.c
@@ -442,6 +442,7 @@ void netfs_readahead(struct readahead_control *ractl)
rreq->submitted = rreq->start + added;
rreq->cleaned_to = rreq->start;
+ netfs_read_set_unlock_at(rreq);
netfs_read_to_pagecache(rreq);
netfs_maybe_bulk_drop_ra_refs(rreq);
@@ -467,6 +468,7 @@ static int netfs_create_singular_buffer(struct netfs_io_request *rreq, struct fo
if (added < 0)
return added;
rreq->submitted = rreq->start + added;
+ rreq->progress_at = added;
return 0;
}
diff --git a/fs/netfs/internal.h b/fs/netfs/internal.h
index dfe7939f35f37..c79c8e69d60ca 100644
--- a/fs/netfs/internal.h
+++ b/fs/netfs/internal.h
@@ -111,6 +111,7 @@ static inline void netfs_see_subrequest(struct netfs_io_subrequest *subreq,
* read_collect.c
*/
void netfs_cancel_copy_to_cache(struct netfs_io_request *rreq, struct folio *folio);
+void netfs_read_set_unlock_at(struct netfs_io_request *rreq);
bool netfs_read_collection(struct netfs_io_request *rreq);
void netfs_read_collection_worker(struct work_struct *work);
void netfs_cancel_read(struct netfs_io_subrequest *subreq, int error);
diff --git a/fs/netfs/objects.c b/fs/netfs/objects.c
index 01461a74642d6..7f6a3e912602e 100644
--- a/fs/netfs/objects.c
+++ b/fs/netfs/objects.c
@@ -41,24 +41,32 @@ struct netfs_io_request *netfs_alloc_request(struct address_space *mapping,
memset(rreq, 0, kmem_cache_size(cache));
INIT_WORK(&rreq->cleanup_work, netfs_free_request);
- rreq->gfp = gfp;
- rreq->start = start;
- rreq->len = len;
- rreq->origin = origin;
- rreq->netfs_ops = ctx->ops;
- rreq->mapping = mapping;
- rreq->inode = inode;
- rreq->i_size = i_size_read(inode);
- rreq->debug_id = atomic_inc_return(&debug_ids);
- rreq->wsize = INT_MAX;
+ rreq->gfp = gfp;
+ rreq->start = start;
+ rreq->collected_to = start;
+ rreq->cleaned_to = start;
+ rreq->len = len;
+ rreq->progress_at = 0;
+ rreq->origin = origin;
+ rreq->netfs_ops = ctx->ops;
+ rreq->mapping = mapping;
+ rreq->inode = inode;
+ rreq->i_size = i_size_read(inode);
+ rreq->debug_id = atomic_inc_return(&debug_ids);
+ rreq->wsize = INT_MAX;
rreq->io_streams[0].sreq_max_len = ULONG_MAX;
rreq->io_streams[0].sreq_max_segs = 0;
spin_lock_init(&rreq->lock);
- INIT_LIST_HEAD(&rreq->io_streams[0].subrequests);
- INIT_LIST_HEAD(&rreq->io_streams[1].subrequests);
init_waitqueue_head(&rreq->waitq);
refcount_set(&rreq->ref, 2);
+ for (int s = 0; s < NR_IO_STREAMS; s++) {
+ struct netfs_io_stream *stream = &rreq->io_streams[s];
+
+ INIT_LIST_HEAD(&stream->subrequests);
+ stream->collected_to = rreq->start;
+ }
+
if (origin == NETFS_READAHEAD ||
origin == NETFS_READPAGE ||
origin == NETFS_READ_GAPS ||
diff --git a/fs/netfs/read_collect.c b/fs/netfs/read_collect.c
index 12a786be1ea26..5cf22087d2439 100644
--- a/fs/netfs/read_collect.c
+++ b/fs/netfs/read_collect.c
@@ -117,6 +117,35 @@ static void netfs_unlock_read_folio(struct netfs_io_request *rreq,
folioq_clear(folioq, slot);
}
+/*
+ * Determine how much to gather before unlocking more folios.
+ */
+void netfs_read_set_unlock_at(struct netfs_io_request *rreq)
+{
+ struct folio_queue *folioq = rreq->buffer.tail;
+ unsigned int slot = rreq->buffer.first_tail_slot;
+ size_t cleaned_to = rreq->cleaned_to - rreq->start;
+ size_t progress_at = cleaned_to;
+ size_t minimum = 256 * 1024;
+
+ while (progress_at < rreq->len) {
+ if (slot >= folioq_count(folioq)) {
+ folioq = folioq->next;
+ if (!folioq)
+ break;
+ slot = 0;
+ }
+
+ progress_at += folioq_folio_size(folioq, slot);
+ if (progress_at - cleaned_to >= minimum)
+ break;
+ slot++;
+ }
+
+ WRITE_ONCE(rreq->progress_at, progress_at);
+ trace_netfs_read_progress_at(rreq);
+}
+
/*
* Unlock any folios we've finished with.
*/
@@ -135,7 +164,7 @@ static void netfs_read_unlock_folios(struct netfs_io_request *rreq,
if (slot >= folioq_nr_slots(folioq)) {
folioq = rolling_buffer_delete_spent(&rreq->buffer);
if (!folioq) {
- rreq->front_folio_order = 0;
+ WRITE_ONCE(rreq->progress_at, rreq->len);
return;
}
slot = 0;
@@ -151,7 +180,6 @@ static void netfs_read_unlock_folios(struct netfs_io_request *rreq,
for (;;) {
struct folio *folio;
unsigned long long fpos, fend;
- unsigned int order;
size_t fsize;
folio = folioq_folio(folioq, slot);
@@ -160,9 +188,7 @@ static void netfs_read_unlock_folios(struct netfs_io_request *rreq,
rreq->debug_id, folio->index))
trace_netfs_folio(folio, netfs_folio_trace_not_locked);
- order = folioq_folio_order(folioq, slot);
- rreq->front_folio_order = order;
- fsize = PAGE_SIZE << order;
+ fsize = folioq_folio_size(folioq, slot);
fpos = folio_pos(folio);
fend = fpos + fsize;
@@ -197,6 +223,8 @@ static void netfs_read_unlock_folios(struct netfs_io_request *rreq,
rreq->buffer.tail = folioq;
done:
rreq->buffer.first_tail_slot = slot;
+
+ netfs_read_set_unlock_at(rreq);
}
/*
@@ -257,7 +285,7 @@ static void netfs_collect_read_results(struct netfs_io_request *rreq)
* subreqs.
*/
if (notes & BUFFERED) {
- size_t fsize = PAGE_SIZE << rreq->front_folio_order;
+ uoff_t unlock_at = rreq->start + rreq->progress_at;
/* Clear the tail of a short read. */
if (!(notes & HIT_PENDING) &&
@@ -279,7 +307,7 @@ static void netfs_collect_read_results(struct netfs_io_request *rreq)
transferred = front->len;
trace_netfs_rreq(rreq, netfs_rreq_trace_set_abandon);
}
- if (front->start + transferred >= rreq->cleaned_to + fsize ||
+ if (front->start + transferred >= unlock_at ||
test_bit(NETFS_SREQ_HIT_EOF, &front->flags))
netfs_read_unlock_folios(rreq, ¬es);
} else {
@@ -499,20 +527,22 @@ void netfs_read_collection_worker(struct work_struct *work)
void netfs_read_subreq_progress(struct netfs_io_subrequest *subreq)
{
struct netfs_io_request *rreq = subreq->rreq;
- struct netfs_io_stream *stream = &rreq->io_streams[0];
- size_t fsize = PAGE_SIZE << rreq->front_folio_order;
-
- trace_netfs_sreq(subreq, netfs_sreq_trace_progress);
+ struct netfs_io_stream *stream = &rreq->io_streams[subreq->stream_nr];
+ size_t progress_at = READ_ONCE(rreq->progress_at);
+ uoff_t update_at = rreq->start + progress_at;
+ uoff_t transferred_to = subreq->start + subreq->transferred;
/* If we are at the head of the queue, wake up the collector,
* getting a ref to it if we were the ones to do so.
*/
- if (subreq->start + subreq->transferred > rreq->cleaned_to + fsize &&
+ if (progress_at < rreq->len &&
+ transferred_to >= update_at &&
(rreq->origin == NETFS_READAHEAD ||
rreq->origin == NETFS_READPAGE ||
rreq->origin == NETFS_READ_FOR_WRITE) &&
list_is_first(&subreq->rreq_link, &stream->subrequests)
) {
+ trace_netfs_sreq(subreq, netfs_sreq_trace_progress);
__set_bit(NETFS_SREQ_MADE_PROGRESS, &subreq->flags);
netfs_wake_collector(rreq);
}
diff --git a/fs/netfs/read_single.c b/fs/netfs/read_single.c
index 8833550d2eb60..de67ac41548d1 100644
--- a/fs/netfs/read_single.c
+++ b/fs/netfs/read_single.c
@@ -170,6 +170,8 @@ ssize_t netfs_read_single(struct inode *inode, struct file *file, struct iov_ite
if (IS_ERR(rreq))
return PTR_ERR(rreq);
+ rreq->progress_at = rreq->len;
+
ret = netfs_single_begin_cache_read(rreq, ictx);
if (ret == -ENOMEM || ret == -EINTR || ret == -ERESTARTSYS)
goto cleanup_free;
diff --git a/include/linux/netfs.h b/include/linux/netfs.h
index 029456e034849..67c2d9ef7c623 100644
--- a/include/linux/netfs.h
+++ b/include/linux/netfs.h
@@ -247,6 +247,7 @@ struct netfs_io_request {
unsigned long long submitted; /* Amount submitted for I/O so far */
unsigned long long len; /* Length of the request */
size_t transferred; /* Amount to be indicated as transferred */
+ size_t progress_at; /* Report read progress when hit this much read */
long error; /* 0 or error that occurred */
unsigned long long i_size; /* Size of the file */
unsigned long long start; /* Start position */
@@ -263,7 +264,6 @@ struct netfs_io_request {
atomic_t subreq_counter; /* Next subreq->debug_index */
unsigned int nr_group_rel; /* Number of refs to release on ->group */
spinlock_t lock; /* Lock for queuing subreqs */
- unsigned char front_folio_order; /* Order (size) of front folio */
enum netfs_io_origin origin; /* Origin of the request */
bool direct_bv_unpin; /* T if direct_bv[] must be unpinned */
refcount_t ref;
diff --git a/include/trace/events/netfs.h b/include/trace/events/netfs.h
index a22084813cb59..3fec3e8f91c85 100644
--- a/include/trace/events/netfs.h
+++ b/include/trace/events/netfs.h
@@ -791,6 +791,27 @@ TRACE_EVENT(netfs_folioq,
__print_symbolic(__entry->trace, netfs_folioq_traces))
);
+TRACE_EVENT(netfs_read_progress_at,
+ TP_PROTO(const struct netfs_io_request *rreq),
+
+ TP_ARGS(rreq),
+
+ TP_STRUCT__entry(
+ __field(unsigned int, rreq)
+ __field(size_t, progress_at)
+ __field(size_t, cleaned_to)
+ ),
+
+ TP_fast_assign(
+ __entry->rreq = rreq->debug_id;
+ __entry->cleaned_to = rreq->cleaned_to - rreq->start;
+ __entry->progress_at = rreq->progress_at;
+ ),
+
+ TP_printk("R=%08x cln=%zx prg=%zx",
+ __entry->rreq, __entry->cleaned_to, __entry->progress_at)
+ );
+
#undef EM
#undef E_
#endif /* _TRACE_NETFS_H */
--
2.53.0
next prev parent reply other threads:[~2026-09-17 15:28 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
[not found] <20260917151350.597953846@linuxfoundation.org>
2026-09-17 15:06 ` [PATCH 7.2 080/733] netfs: Fix unbuffered/DIO write partial transfer error return Greg Kroah-Hartman
2026-09-17 15:06 ` [PATCH 7.2 081/733] netfs: Fix error vs transferred passed to ->ki_complete() Greg Kroah-Hartman
2026-09-17 15:06 ` [PATCH 7.2 082/733] netfs: Fix i_size update for partial transfer Greg Kroah-Hartman
2026-09-17 15:06 ` [PATCH 7.2 083/733] netfs: Fix subreq ref leak Greg Kroah-Hartman
2026-09-17 15:06 ` [PATCH 7.2 085/733] netfs: Fix readahead synchronisation issues by loading all folios upfront Greg Kroah-Hartman
2026-09-17 15:06 ` [PATCH 7.2 086/733] netfs: Mark folios with COPY_TO_CACHE whilst issuing subreqs Greg Kroah-Hartman
2026-09-17 15:06 ` Greg Kroah-Hartman [this message]
2026-09-17 15:06 ` [PATCH 7.2 088/733] cachefiles: Fix potential UAF/KASAN warning Greg Kroah-Hartman
2026-09-17 15:12 ` [PATCH 7.2 438/733] block: Fix start and length check added to iov_iter_extract_bvecs() Greg Kroah-Hartman
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=20260917151353.074841239@linuxfoundation.org \
--to=gregkh@linuxfoundation.org \
--cc=brauner@kernel.org \
--cc=dhowells@redhat.com \
--cc=linux-fsdevel@vger.kernel.org \
--cc=netfs@lists.linux.dev \
--cc=patches@lists.linux.dev \
--cc=pc@manguebit.org \
--cc=sashal@kernel.org \
--cc=stable@vger.kernel.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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox