// SPDX-License-Identifier: GPL-2.0-only /* Network filesystem write subrequest result collection, assessment * and retrying. * * Copyright (C) 2024 Red Hat, Inc. All Rights Reserved. * Written by David Howells (dhowells@redhat.com)
*/
/* Notes made in the collector */ #define HIT_PENDING 0x01 /* A front op was still pending */ #define NEED_REASSESS 0x02 /* Need to loop round and reassess */ #define MADE_PROGRESS 0x04 /* Made progress cleaning up a stream or the folio set */ #define NEED_UNLOCK 0x08 /* The pagecache needs unlocking */ #define NEED_RETRY 0x10 /* A front op requests retrying */ #define SAW_FAILURE 0x20 /* One stream or hit a permanent failure */
/* * Successful completion of write of a folio to the server and/or cache. Note * that we are not allowed to lock the folio here on pain of deadlocking with * truncate.
*/ int netfs_folio_written_back(struct folio *folio)
{ enum netfs_folio_trace why = netfs_folio_trace_clear; struct netfs_inode *ictx = netfs_inode(folio->mapping->host); struct netfs_folio *finfo; struct netfs_group *group = NULL; int gcount = 0;
if ((finfo = netfs_folio_info(folio))) { /* Streaming writes cannot be redirtied whilst under writeback, * so discard the streaming record.
*/ unsignedlonglong fend;
if ((group = netfs_folio_group(folio))) { if (group == NETFS_FOLIO_COPY_TO_CACHE) {
why = netfs_folio_trace_clear_cc;
folio_detach_private(folio); goto end_wb;
}
/* Need to detach the group pointer if the page didn't get * redirtied. If it has been redirtied, then it must be within * the same group.
*/
why = netfs_folio_trace_redirtied; if (!folio_test_dirty(folio)) {
folio_detach_private(folio);
gcount++;
why = netfs_folio_trace_clear_g;
}
}
folio = folioq_folio(folioq, slot); if (WARN_ONCE(!folio_test_writeback(folio), "R=%08x: folio %lx is not under writeback\n",
wreq->debug_id, folio->index))
trace_netfs_folio(folio, netfs_folio_trace_not_under_wback);
/* Clean up the head folioq. If we clear an entire folioq, then * we can get rid of it provided it's not also the tail folioq * being filled by the issuer.
*/
folioq_clear(folioq, slot);
slot++; if (slot >= folioq_nr_slots(folioq)) {
folioq = rolling_buffer_delete_spent(&wreq->buffer); if (!folioq) goto done;
slot = 0;
}
/* * Collect and assess the results of various write subrequests. We may need to * retry some of the results - or even do an RMW cycle for content crypto. * * Note that we have a number of parallel, overlapping lists of subrequests, * one to the server and one to the local cache for example, which may not be * the same size or starting position and may not even correspond in boundary * alignment.
*/ staticvoid netfs_collect_write_results(struct netfs_io_request *wreq)
{ struct netfs_io_subrequest *front, *remove; struct netfs_io_stream *stream; unsignedlonglong collected_to, issued_to; unsignedint notes; int s;
/* Remove completed subrequests from the front of the streams and * advance the completion point on each stream. 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 (s = 0; s < NR_IO_STREAMS; s++) {
stream = &wreq->io_streams[s]; /* Read active flag before list pointers */ if (!smp_load_acquire(&stream->active)) continue;
front = stream->front; while (front) {
trace_netfs_collect_sreq(wreq, front); //_debug("sreq [%x] %llx %zx/%zx", // front->debug_index, front->start, front->transferred, front->len);
/* Stall if the front is still undergoing I/O. */ if (netfs_check_subreq_in_progress(front)) {
notes |= HIT_PENDING; break;
}
smp_rmb(); /* Read counters after I-P flag. */
/* If we have an empty stream, we need to jump it forward * otherwise the collection point will never advance.
*/ if (!front && issued_to > stream->collected_to) {
trace_netfs_collect_gap(wreq, stream, issued_to, 'E');
stream->collected_to = issued_to;
}
if (stream->collected_to < collected_to)
collected_to = stream->collected_to;
}
/* Unlock any folios that we have now finished with. */ if (notes & NEED_UNLOCK) { if (wreq->cleaned_to < wreq->collected_to)
netfs_writeback_unlock_folios(wreq, ¬es);
} else {
wreq->cleaned_to = wreq->collected_to;
}
need_retry: /* Okay... We're going to have to retry one or both streams. Note * that any partially completed op will have had any wholly transferred * folios removed from it.
*/
_debug("retry");
netfs_retry_writes(wreq); goto out;
}
/* * Perform the collection of subrequests, folios and encryption buffers.
*/ bool netfs_write_collection(struct netfs_io_request *wreq)
{ struct netfs_inode *ictx = netfs_inode(wreq->inode);
size_t transferred; bool transferred_valid = false; int s;
_enter("R=%x", wreq->debug_id);
netfs_collect_write_results(wreq);
/* We're done when the app thread has finished posting subreqs and all * the queues in all the streams are empty.
*/ if (!test_bit(NETFS_RREQ_ALL_QUEUED, &wreq->flags)) returnfalse;
smp_rmb(); /* Read ALL_QUEUED before lists. */
transferred = LONG_MAX; for (s = 0; s < NR_IO_STREAMS; s++) { struct netfs_io_stream *stream = &wreq->io_streams[s]; if (!stream->active) continue; if (!list_empty(&stream->subrequests)) returnfalse; if (stream->transferred_valid &&
stream->transferred < transferred) {
transferred = stream->transferred;
transferred_valid = true;
}
}
/* Okay, declare that all I/O is complete. */ if (transferred_valid)
wreq->transferred = transferred;
trace_netfs_rreq(wreq, netfs_rreq_trace_write_done);
if (wreq->origin == NETFS_DIO_WRITE &&
wreq->mapping->nrpages) { /* mmap may have got underfoot and we may now have folios * locally covering the region we just wrote. Attempt to * discard the folios, but leave in place any modified locally. * ->write_iter() is prevented from interfering by the DIO * counter.
*/
pgoff_t first = wreq->start >> PAGE_SHIFT;
pgoff_t last = (wreq->start + wreq->transferred - 1) >> PAGE_SHIFT;
invalidate_inode_pages2_range(wreq->mapping, first, last);
}
if (wreq->origin == NETFS_DIO_WRITE)
inode_dio_end(wreq->inode);
_debug("finished");
netfs_wake_rreq_flag(wreq, NETFS_RREQ_IN_PROGRESS, netfs_rreq_trace_wake_ip); /* As we cleared NETFS_RREQ_IN_PROGRESS, we acquired its ref. */
if (wreq->iocb) {
size_t written = min(wreq->transferred, wreq->len);
wreq->iocb->ki_pos += written; if (wreq->iocb->ki_complete) {
trace_netfs_rreq(wreq, netfs_rreq_trace_ki_complete);
wreq->iocb->ki_complete(
wreq->iocb, wreq->error ? wreq->error : written);
}
wreq->iocb = VFS_PTR_POISON;
}
netfs_see_request(rreq, netfs_rreq_trace_see_work); if (netfs_check_rreq_in_progress(rreq)) { if (netfs_write_collection(rreq)) /* Drop the ref from the IN_PROGRESS flag. */
netfs_put_request(rreq, netfs_rreq_trace_put_work_ip); else
netfs_see_request(rreq, netfs_rreq_trace_see_work_complete);
}
}
/** * netfs_write_subrequest_terminated - Note the termination of a write operation. * @_op: The I/O request that has terminated. * @transferred_or_error: The amount of data transferred or an error code. * * This tells the library that a contributory write I/O operation has * terminated, one way or another, and that it should collect the results. * * The caller indicates in @transferred_or_error the outcome of the operation, * supplying a positive value to indicate the number of bytes transferred or a * negative error code. The library will look after reissuing I/O operations * as appropriate and writing downloaded data to the cache. * * When this is called, ownership of the subrequest is transferred back to the * library, along with a ref. * * Note that %_op is a void* so that the function can be passed to * kiocb::term_func without the need for a casting wrapper.
*/ void netfs_write_subrequest_terminated(void *_op, ssize_t transferred_or_error)
{ struct netfs_io_subrequest *subreq = _op; struct netfs_io_request *wreq = subreq->rreq;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.