Skip to content

Commit 1d00139

Browse files
dhowellsbrauner
authored andcommitted
netfs: Fix a number of read-retry hangs
Fix a number of hangs in the netfslib read-retry code, including: (1) netfs_reissue_read() doubles up the getting of references on subrequests, thereby leaking the subrequest and causing inode eviction to wait indefinitely. This can lead to the kernel reporting a hang in the filesystem's evict_inode(). Fix this by removing the get from netfs_reissue_read() and adding one to netfs_retry_read_subrequests() to deal with the one place that didn't double up. (2) The loop in netfs_retry_read_subrequests() that retries a sequence of failed subrequests doesn't record whether or not it retried the one that the "subreq" pointer points to when it leaves the loop. It may not if renegotiation/repreparation of the subrequests means that fewer subrequests are needed to span the cumulative range of the sequence. Because it doesn't record this, the piece of code that discards now-superfluous subrequests doesn't know whether it should discard the one "subreq" points to - and so it doesn't. Fix this by noting whether the last subreq it examines is superfluous and if it is, then getting rid of it and all subsequent subrequests. If that one one wasn't superfluous, then we would have tried to go round the previous loop again and so there can be no further unretried subrequests in the sequence. (3) netfs_retry_read_subrequests() gets yet an extra ref on any additional subrequests it has to get because it ran out of ones it could reuse to to renegotiation/repreparation shrinking the subrequests. Fix this by removing that extra ref. (4) In netfs_retry_reads(), it was using wait_on_bit() to wait for NETFS_SREQ_IN_PROGRESS to be cleared on all subrequests in the sequence - but netfs_read_subreq_terminated() is now using a wait queue on the request instead and so this wait will never finish. Fix this by waiting on the wait queue instead. To make this work, a new flag, NETFS_RREQ_RETRYING, is now set around the wait loop to tell the wake-up code to wake up the wait queue rather than requeuing the request's work item. Note that this flag replaces the NETFS_RREQ_NEED_RETRY flag which is no longer used. (5) Whilst not strictly anything to do with the hang, netfs_retry_read_subrequests() was also doubly incrementing the subreq_counter and re-setting the debug index, leaving a gap in the trace. This is also fixed. One of these hangs was observed with 9p and with cifs. Others were forced by manual code injection into fs/afs/file.c. Firstly, afs_prepare_read() was created to provide an changing pattern of maximum subrequest sizes: static int afs_prepare_read(struct netfs_io_subrequest *subreq) { struct netfs_io_request *rreq = subreq->rreq; if (!S_ISREG(subreq->rreq->inode->i_mode)) return 0; if (subreq->retry_count < 20) rreq->io_streams[0].sreq_max_len = umax(200, 2222 - subreq->retry_count * 40); else rreq->io_streams[0].sreq_max_len = 3333; return 0; } and pointed to by afs_req_ops. Then the following: struct netfs_io_subrequest *subreq = op->fetch.subreq; if (subreq->error == 0 && S_ISREG(subreq->rreq->inode->i_mode) && subreq->retry_count < 20) { subreq->transferred = subreq->already_done; __clear_bit(NETFS_SREQ_HIT_EOF, &subreq->flags); __set_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags); afs_fetch_data_notify(op); return; } was inserted into afs_fetch_data_success() at the beginning and struct netfs_io_subrequest given an extra field, "already_done" that was set to the value in "subreq->transferred" by netfs_reissue_read(). When reading a 4K file, the subrequests would get gradually smaller, a new subrequest would be allocated around the 3rd retry and then eventually be rendered superfluous when the 20th retry was hit and the limit on the first subrequest was eased. Fixes: e2d46f2 ("netfs: Change the read result collector to only use one work item") Signed-off-by: David Howells <dhowells@redhat.com> Link: https://lore.kernel.org/r/20250212222402.3618494-2-dhowells@redhat.com Tested-by: Marc Dionne <marc.dionne@auristor.com> Tested-by: Steve French <stfrench@microsoft.com> cc: Ihor Solodrai <ihor.solodrai@pm.me> cc: Eric Van Hensbergen <ericvh@kernel.org> cc: Latchesar Ionkov <lucho@ionkov.net> cc: Dominique Martinet <asmadeus@codewreck.org> cc: Christian Schoenebeck <linux_oss@crudebyte.com> cc: Paulo Alcantara <pc@manguebit.com> cc: Jeff Layton <jlayton@kernel.org> cc: v9fs@lists.linux.dev cc: linux-cifs@vger.kernel.org cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner <brauner@kernel.org>
1 parent 2401892 commit 1d00139

File tree

4 files changed

+38
-14
lines changed

4 files changed

+38
-14
lines changed

fs/netfs/read_collect.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -470,7 +470,8 @@ void netfs_read_collection_worker(struct work_struct *work)
470470
*/
471471
void netfs_wake_read_collector(struct netfs_io_request *rreq)
472472
{
473-
if (test_bit(NETFS_RREQ_OFFLOAD_COLLECTION, &rreq->flags)) {
473+
if (test_bit(NETFS_RREQ_OFFLOAD_COLLECTION, &rreq->flags) &&
474+
!test_bit(NETFS_RREQ_RETRYING, &rreq->flags)) {
474475
if (!work_pending(&rreq->work)) {
475476
netfs_get_request(rreq, netfs_rreq_trace_get_work);
476477
if (!queue_work(system_unbound_wq, &rreq->work))
@@ -586,7 +587,8 @@ void netfs_read_subreq_terminated(struct netfs_io_subrequest *subreq)
586587
smp_mb__after_atomic(); /* Clear IN_PROGRESS before task state */
587588

588589
/* If we are at the head of the queue, wake up the collector. */
589-
if (list_is_first(&subreq->rreq_link, &stream->subrequests))
590+
if (list_is_first(&subreq->rreq_link, &stream->subrequests) ||
591+
test_bit(NETFS_RREQ_RETRYING, &rreq->flags))
590592
netfs_wake_read_collector(rreq);
591593

592594
netfs_put_subrequest(subreq, true, netfs_sreq_trace_put_terminated);

fs/netfs/read_retry.c

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ static void netfs_reissue_read(struct netfs_io_request *rreq,
1414
{
1515
__clear_bit(NETFS_SREQ_MADE_PROGRESS, &subreq->flags);
1616
__set_bit(NETFS_SREQ_IN_PROGRESS, &subreq->flags);
17-
netfs_get_subrequest(subreq, netfs_sreq_trace_get_resubmit);
1817
subreq->rreq->netfs_ops->issue_read(subreq);
1918
}
2019

@@ -48,6 +47,7 @@ static void netfs_retry_read_subrequests(struct netfs_io_request *rreq)
4847
__clear_bit(NETFS_SREQ_MADE_PROGRESS, &subreq->flags);
4948
subreq->retry_count++;
5049
netfs_reset_iter(subreq);
50+
netfs_get_subrequest(subreq, netfs_sreq_trace_get_resubmit);
5151
netfs_reissue_read(rreq, subreq);
5252
}
5353
}
@@ -75,7 +75,7 @@ static void netfs_retry_read_subrequests(struct netfs_io_request *rreq)
7575
struct iov_iter source;
7676
unsigned long long start, len;
7777
size_t part;
78-
bool boundary = false;
78+
bool boundary = false, subreq_superfluous = false;
7979

8080
/* Go through the subreqs and find the next span of contiguous
8181
* buffer that we then rejig (cifs, for example, needs the
@@ -116,8 +116,10 @@ static void netfs_retry_read_subrequests(struct netfs_io_request *rreq)
116116
/* Work through the sublist. */
117117
subreq = from;
118118
list_for_each_entry_from(subreq, &stream->subrequests, rreq_link) {
119-
if (!len)
119+
if (!len) {
120+
subreq_superfluous = true;
120121
break;
122+
}
121123
subreq->source = NETFS_DOWNLOAD_FROM_SERVER;
122124
subreq->start = start - subreq->transferred;
123125
subreq->len = len + subreq->transferred;
@@ -154,19 +156,21 @@ static void netfs_retry_read_subrequests(struct netfs_io_request *rreq)
154156

155157
netfs_get_subrequest(subreq, netfs_sreq_trace_get_resubmit);
156158
netfs_reissue_read(rreq, subreq);
157-
if (subreq == to)
159+
if (subreq == to) {
160+
subreq_superfluous = false;
158161
break;
162+
}
159163
}
160164

161165
/* If we managed to use fewer subreqs, we can discard the
162166
* excess; if we used the same number, then we're done.
163167
*/
164168
if (!len) {
165-
if (subreq == to)
169+
if (!subreq_superfluous)
166170
continue;
167171
list_for_each_entry_safe_from(subreq, tmp,
168172
&stream->subrequests, rreq_link) {
169-
trace_netfs_sreq(subreq, netfs_sreq_trace_discard);
173+
trace_netfs_sreq(subreq, netfs_sreq_trace_superfluous);
170174
list_del(&subreq->rreq_link);
171175
netfs_put_subrequest(subreq, false, netfs_sreq_trace_put_done);
172176
if (subreq == to)
@@ -187,14 +191,12 @@ static void netfs_retry_read_subrequests(struct netfs_io_request *rreq)
187191
subreq->source = NETFS_DOWNLOAD_FROM_SERVER;
188192
subreq->start = start;
189193
subreq->len = len;
190-
subreq->debug_index = atomic_inc_return(&rreq->subreq_counter);
191194
subreq->stream_nr = stream->stream_nr;
192195
subreq->retry_count = 1;
193196

194197
trace_netfs_sreq_ref(rreq->debug_id, subreq->debug_index,
195198
refcount_read(&subreq->ref),
196199
netfs_sreq_trace_new);
197-
netfs_get_subrequest(subreq, netfs_sreq_trace_get_resubmit);
198200

199201
list_add(&subreq->rreq_link, &to->rreq_link);
200202
to = list_next_entry(to, rreq_link);
@@ -256,14 +258,32 @@ void netfs_retry_reads(struct netfs_io_request *rreq)
256258
{
257259
struct netfs_io_subrequest *subreq;
258260
struct netfs_io_stream *stream = &rreq->io_streams[0];
261+
DEFINE_WAIT(myself);
262+
263+
set_bit(NETFS_RREQ_RETRYING, &rreq->flags);
259264

260265
/* Wait for all outstanding I/O to quiesce before performing retries as
261266
* we may need to renegotiate the I/O sizes.
262267
*/
263268
list_for_each_entry(subreq, &stream->subrequests, rreq_link) {
264-
wait_on_bit(&subreq->flags, NETFS_SREQ_IN_PROGRESS,
265-
TASK_UNINTERRUPTIBLE);
269+
if (!test_bit(NETFS_SREQ_IN_PROGRESS, &subreq->flags))
270+
continue;
271+
272+
trace_netfs_rreq(rreq, netfs_rreq_trace_wait_queue);
273+
for (;;) {
274+
prepare_to_wait(&rreq->waitq, &myself, TASK_UNINTERRUPTIBLE);
275+
276+
if (!test_bit(NETFS_SREQ_IN_PROGRESS, &subreq->flags))
277+
break;
278+
279+
trace_netfs_sreq(subreq, netfs_sreq_trace_wait_for);
280+
schedule();
281+
trace_netfs_rreq(rreq, netfs_rreq_trace_woke_queue);
282+
}
283+
284+
finish_wait(&rreq->waitq, &myself);
266285
}
286+
clear_bit(NETFS_RREQ_RETRYING, &rreq->flags);
267287

268288
trace_netfs_rreq(rreq, netfs_rreq_trace_resubmit);
269289
netfs_retry_read_subrequests(rreq);

include/linux/netfs.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ struct netfs_io_request {
278278
#define NETFS_RREQ_PAUSE 11 /* Pause subrequest generation */
279279
#define NETFS_RREQ_USE_IO_ITER 12 /* Use ->io_iter rather than ->i_pages */
280280
#define NETFS_RREQ_ALL_QUEUED 13 /* All subreqs are now queued */
281-
#define NETFS_RREQ_NEED_RETRY 14 /* Need to try retrying */
281+
#define NETFS_RREQ_RETRYING 14 /* Set if we're in the retry path */
282282
#define NETFS_RREQ_USE_PGPRIV2 31 /* [DEPRECATED] Use PG_private_2 to mark
283283
* write to cache on read */
284284
const struct netfs_request_ops *netfs_ops;

include/trace/events/netfs.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@
9999
EM(netfs_sreq_trace_limited, "LIMIT") \
100100
EM(netfs_sreq_trace_need_clear, "N-CLR") \
101101
EM(netfs_sreq_trace_partial_read, "PARTR") \
102-
EM(netfs_sreq_trace_need_retry, "NRTRY") \
102+
EM(netfs_sreq_trace_need_retry, "ND-RT") \
103103
EM(netfs_sreq_trace_prepare, "PREP ") \
104104
EM(netfs_sreq_trace_prep_failed, "PRPFL") \
105105
EM(netfs_sreq_trace_progress, "PRGRS") \
@@ -108,7 +108,9 @@
108108
EM(netfs_sreq_trace_short, "SHORT") \
109109
EM(netfs_sreq_trace_split, "SPLIT") \
110110
EM(netfs_sreq_trace_submit, "SUBMT") \
111+
EM(netfs_sreq_trace_superfluous, "SPRFL") \
111112
EM(netfs_sreq_trace_terminated, "TERM ") \
113+
EM(netfs_sreq_trace_wait_for, "_WAIT") \
112114
EM(netfs_sreq_trace_write, "WRITE") \
113115
EM(netfs_sreq_trace_write_skip, "SKIP ") \
114116
E_(netfs_sreq_trace_write_term, "WTERM")

0 commit comments

Comments
 (0)