mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git
synced 2024-12-28 16:52:18 +00:00
b4f239c91f
Change the way netfslib collects read results to do all the collection for a particular read request using a single work item that walks along the subrequest queue as subrequests make progress or complete, unlocking folios progressively rather than doing the unlock in parallel as parallel requests come in. The code is remodelled to be more like the write-side code, though only using a single stream. This makes it more directly comparable and thus easier to duplicate fixes between the two sides. This has a number of advantages: (1) It's simpler. There doesn't need to be a complex donation mechanism to handle mismatches between the size and alignment of subrequests and folios. The collector unlocks folios as the subrequests covering each complete. (2) It should cause less scheduler overhead as there's a single work item in play unlocking pages in parallel when a read gets split up into a lot of subrequests instead of one per subrequest. Whilst the parallellism is nice in theory, in practice, the vast majority of loads are sequential reads of the whole file, so committing a bunch of threads to unlocking folios out of order doesn't help in those cases. (3) It should make it easier to implement content decryption. A folio cannot be decrypted until all the requests that contribute to it have completed - and, again, most loads are sequential and so, most of the time, we want to begin decryption sequentially (though it's great if the decryption can happen in parallel). There is a disadvantage in that we're losing the ability to decrypt and unlock things on an as-things-arrive basis which may affect some applications. Signed-off-by: David Howells <dhowells@redhat.com> Link: https://lore.kernel.org/r/20241108173236.1382366-29-dhowells@redhat.com cc: Jeff Layton <jlayton@kernel.org> cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner <brauner@kernel.org>
234 lines
6.5 KiB
C
234 lines
6.5 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/* Network filesystem write retrying.
|
|
*
|
|
* Copyright (C) 2024 Red Hat, Inc. All Rights Reserved.
|
|
* Written by David Howells (dhowells@redhat.com)
|
|
*/
|
|
|
|
#include <linux/fs.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/pagemap.h>
|
|
#include <linux/slab.h>
|
|
#include "internal.h"
|
|
|
|
/*
|
|
* Perform retries on the streams that need it.
|
|
*/
|
|
static void netfs_retry_write_stream(struct netfs_io_request *wreq,
|
|
struct netfs_io_stream *stream)
|
|
{
|
|
struct list_head *next;
|
|
|
|
_enter("R=%x[%x:]", wreq->debug_id, stream->stream_nr);
|
|
|
|
if (list_empty(&stream->subrequests))
|
|
return;
|
|
|
|
if (stream->source == NETFS_UPLOAD_TO_SERVER &&
|
|
wreq->netfs_ops->retry_request)
|
|
wreq->netfs_ops->retry_request(wreq, stream);
|
|
|
|
if (unlikely(stream->failed))
|
|
return;
|
|
|
|
/* If there's no renegotiation to do, just resend each failed subreq. */
|
|
if (!stream->prepare_write) {
|
|
struct netfs_io_subrequest *subreq;
|
|
|
|
list_for_each_entry(subreq, &stream->subrequests, rreq_link) {
|
|
if (test_bit(NETFS_SREQ_FAILED, &subreq->flags))
|
|
break;
|
|
if (__test_and_clear_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags)) {
|
|
struct iov_iter source = subreq->io_iter;
|
|
|
|
iov_iter_revert(&source, subreq->len - source.count);
|
|
__set_bit(NETFS_SREQ_RETRYING, &subreq->flags);
|
|
netfs_get_subrequest(subreq, netfs_sreq_trace_get_resubmit);
|
|
netfs_reissue_write(stream, subreq, &source);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
next = stream->subrequests.next;
|
|
|
|
do {
|
|
struct netfs_io_subrequest *subreq = NULL, *from, *to, *tmp;
|
|
struct iov_iter source;
|
|
unsigned long long start, len;
|
|
size_t part;
|
|
bool boundary = false;
|
|
|
|
/* Go through the stream and find the next span of contiguous
|
|
* data that we then rejig (cifs, for example, needs the wsize
|
|
* renegotiating) and reissue.
|
|
*/
|
|
from = list_entry(next, struct netfs_io_subrequest, rreq_link);
|
|
to = from;
|
|
start = from->start + from->transferred;
|
|
len = from->len - from->transferred;
|
|
|
|
if (test_bit(NETFS_SREQ_FAILED, &from->flags) ||
|
|
!test_bit(NETFS_SREQ_NEED_RETRY, &from->flags))
|
|
return;
|
|
|
|
list_for_each_continue(next, &stream->subrequests) {
|
|
subreq = list_entry(next, struct netfs_io_subrequest, rreq_link);
|
|
if (subreq->start + subreq->transferred != start + len ||
|
|
test_bit(NETFS_SREQ_BOUNDARY, &subreq->flags) ||
|
|
!test_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags))
|
|
break;
|
|
to = subreq;
|
|
len += to->len;
|
|
}
|
|
|
|
/* Determine the set of buffers we're going to use. Each
|
|
* subreq gets a subset of a single overall contiguous buffer.
|
|
*/
|
|
netfs_reset_iter(from);
|
|
source = from->io_iter;
|
|
source.count = len;
|
|
|
|
/* Work through the sublist. */
|
|
subreq = from;
|
|
list_for_each_entry_from(subreq, &stream->subrequests, rreq_link) {
|
|
if (!len)
|
|
break;
|
|
|
|
subreq->start = start;
|
|
subreq->len = len;
|
|
__clear_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags);
|
|
__set_bit(NETFS_SREQ_RETRYING, &subreq->flags);
|
|
trace_netfs_sreq(subreq, netfs_sreq_trace_retry);
|
|
|
|
/* Renegotiate max_len (wsize) */
|
|
stream->sreq_max_len = len;
|
|
stream->prepare_write(subreq);
|
|
|
|
part = umin(len, stream->sreq_max_len);
|
|
if (unlikely(stream->sreq_max_segs))
|
|
part = netfs_limit_iter(&source, 0, part, stream->sreq_max_segs);
|
|
subreq->len = part;
|
|
subreq->transferred = 0;
|
|
len -= part;
|
|
start += part;
|
|
if (len && subreq == to &&
|
|
__test_and_clear_bit(NETFS_SREQ_BOUNDARY, &to->flags))
|
|
boundary = true;
|
|
|
|
netfs_get_subrequest(subreq, netfs_sreq_trace_get_resubmit);
|
|
netfs_reissue_write(stream, subreq, &source);
|
|
if (subreq == to)
|
|
break;
|
|
}
|
|
|
|
/* If we managed to use fewer subreqs, we can discard the
|
|
* excess; if we used the same number, then we're done.
|
|
*/
|
|
if (!len) {
|
|
if (subreq == to)
|
|
continue;
|
|
list_for_each_entry_safe_from(subreq, tmp,
|
|
&stream->subrequests, rreq_link) {
|
|
trace_netfs_sreq(subreq, netfs_sreq_trace_discard);
|
|
list_del(&subreq->rreq_link);
|
|
netfs_put_subrequest(subreq, false, netfs_sreq_trace_put_done);
|
|
if (subreq == to)
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
/* We ran out of subrequests, so we need to allocate some more
|
|
* and insert them after.
|
|
*/
|
|
do {
|
|
subreq = netfs_alloc_subrequest(wreq);
|
|
subreq->source = to->source;
|
|
subreq->start = start;
|
|
subreq->debug_index = atomic_inc_return(&wreq->subreq_counter);
|
|
subreq->stream_nr = to->stream_nr;
|
|
__set_bit(NETFS_SREQ_RETRYING, &subreq->flags);
|
|
|
|
trace_netfs_sreq_ref(wreq->debug_id, subreq->debug_index,
|
|
refcount_read(&subreq->ref),
|
|
netfs_sreq_trace_new);
|
|
netfs_get_subrequest(subreq, netfs_sreq_trace_get_resubmit);
|
|
|
|
list_add(&subreq->rreq_link, &to->rreq_link);
|
|
to = list_next_entry(to, rreq_link);
|
|
trace_netfs_sreq(subreq, netfs_sreq_trace_retry);
|
|
|
|
stream->sreq_max_len = len;
|
|
stream->sreq_max_segs = INT_MAX;
|
|
switch (stream->source) {
|
|
case NETFS_UPLOAD_TO_SERVER:
|
|
netfs_stat(&netfs_n_wh_upload);
|
|
stream->sreq_max_len = umin(len, wreq->wsize);
|
|
break;
|
|
case NETFS_WRITE_TO_CACHE:
|
|
netfs_stat(&netfs_n_wh_write);
|
|
break;
|
|
default:
|
|
WARN_ON_ONCE(1);
|
|
}
|
|
|
|
stream->prepare_write(subreq);
|
|
|
|
part = umin(len, stream->sreq_max_len);
|
|
subreq->len = subreq->transferred + part;
|
|
len -= part;
|
|
start += part;
|
|
if (!len && boundary) {
|
|
__set_bit(NETFS_SREQ_BOUNDARY, &to->flags);
|
|
boundary = false;
|
|
}
|
|
|
|
netfs_reissue_write(stream, subreq, &source);
|
|
if (!len)
|
|
break;
|
|
|
|
} while (len);
|
|
|
|
} while (!list_is_head(next, &stream->subrequests));
|
|
}
|
|
|
|
/*
|
|
* Perform retries on the streams that need it. If we're doing content
|
|
* encryption and the server copy changed due to a third-party write, we may
|
|
* need to do an RMW cycle and also rewrite the data to the cache.
|
|
*/
|
|
void netfs_retry_writes(struct netfs_io_request *wreq)
|
|
{
|
|
struct netfs_io_subrequest *subreq;
|
|
struct netfs_io_stream *stream;
|
|
int s;
|
|
|
|
/* Wait for all outstanding I/O to quiesce before performing retries as
|
|
* we may need to renegotiate the I/O sizes.
|
|
*/
|
|
for (s = 0; s < NR_IO_STREAMS; s++) {
|
|
stream = &wreq->io_streams[s];
|
|
if (!stream->active)
|
|
continue;
|
|
|
|
list_for_each_entry(subreq, &stream->subrequests, rreq_link) {
|
|
wait_on_bit(&subreq->flags, NETFS_SREQ_IN_PROGRESS,
|
|
TASK_UNINTERRUPTIBLE);
|
|
}
|
|
}
|
|
|
|
// TODO: Enc: Fetch changed partial pages
|
|
// TODO: Enc: Reencrypt content if needed.
|
|
// TODO: Enc: Wind back transferred point.
|
|
// TODO: Enc: Mark cache pages for retry.
|
|
|
|
for (s = 0; s < NR_IO_STREAMS; s++) {
|
|
stream = &wreq->io_streams[s];
|
|
if (stream->need_retry) {
|
|
stream->need_retry = false;
|
|
netfs_retry_write_stream(wreq, stream);
|
|
}
|
|
}
|
|
}
|