Linux NFS development
 help / color / mirror / Atom feed
From: Mike Snitzer <snitzer@kernel.org>
To: linux-nfs@vger.kernel.org, linux-block@vger.kernel.org
Cc: dm-devel@lists.linux.dev, axboe@kernel.dk, cel@kernel.org,
	jlayton@kernel.org, david.flynn@hammerspace.com
Subject: [PATCH 4/4] nfsd: fall back to buffered I/O when a direct write gets -EINVAL
Date: Tue,  8 Sep 2026 12:34:39 -0400	[thread overview]
Message-ID: <20260908163448.30841-5-snitzer@kernel.org> (raw)
In-Reply-To: <20260908163448.30841-1-snitzer@kernel.org>

nfsd_dio_iter_is_aligned() approves a write iterator against the
file's STATX_DIOALIGN attributes (a whole-iterator iov_iter_alignment()
test against dio_mem_align), but the block stack applies stricter
geometry tests at bio split time: bio_split_io_at() checks each bvec's
offset and length against the queue's dma_alignment and may find no
valid block-size-aligned split at all. An ITER_BVEC WRITE payload can
pass the former and fail the latter: bio_iov_bvec_set() hands nfsd's
bvec array to the queue as-is, and the payload's first fragment starts
mid-page (the RPC header precedes it in the receive buffer), so the
iterator's interior page boundaries need not be logical-block aligned
and a bio the queue must split may have no valid split point. When
that happens, nfsd_direct_write() returned the -EINVAL to the client
as a failed WRITE (NFS4ERR_INVAL) -- for a perfectly valid request.

Observed against a brd-backed nvme-loop XFS export (dio_mem_align=4)
with 1 MiB WRITEs, e.g. arriving as 65 bvecs with bv0=(408,15976):
the gate admits the iterator, the block layer rejects it, and every
large write on the affected connection errors out (dd: Invalid
argument).

Treat -EINVAL from the direct attempt as "not direct-able": restore the
segment's iterator and retry it as (uncached when FOP_DONTCACHE)
buffered I/O, the same fallback nfsd_write_dio_iters_init() picks for
geometries it rejects itself.

The failed attempt must be assumed to have left the iterator advanced:
 ->write_iter() advances it while building and submitting bios before
the split-time rejection can fire, and vfs_iocb_iter_write() does not
revert on error. That is why the restore is a struct copy taken before
the attempt -- it snapshots the complete cursor by value (iov_offset,
count, bvec, nr_segs; the underlying bio_vec array is never mutated by
iteration), where iov_iter_revert() would need a byte count that an
error return does not provide. ki_pos is only advanced on success
(iomap_dio_complete() bumps it under ret > 0), so the retry lands at
the original offset, and any sectors a partially-split attempt already
reached are rewritten with the same data. The retry emits
nfsd_write_vector after the original nfsd_write_direct, so a fallback
is visible in tracing as the pair.

With this fix the same rig survives 30 fresh connections x 16 MiB of
page-aligned O_DIRECT client writes with zero client-visible errors
(fallback observed on 20 of 30 connections).

Fixes: 06c5c97293e3 ("NFSD: Implement NFSD_IO_DIRECT for NFS WRITE")
Assisted-by: Claude:claude-fable-5
Signed-off-by: Mike Snitzer <snitzer@kernel.org>
---
 fs/nfsd/vfs.c | 29 +++++++++++++++++++++++++++++
 1 file changed, 29 insertions(+)

diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c
index 1af4f77f82fc..f43bbd0ae731 100644
--- a/fs/nfsd/vfs.c
+++ b/fs/nfsd/vfs.c
@@ -1456,6 +1456,8 @@ nfsd_direct_write(struct svc_rqst *rqstp, struct svc_fh *fhp,
 
 	*cnt = 0;
 	for (i = 0; i < nsegs; i++) {
+		struct iov_iter saved_iter = segments[i].iter;
+
 		kiocb->ki_flags = segments[i].flags;
 		if (kiocb->ki_flags & IOCB_DIRECT)
 			trace_nfsd_write_direct(rqstp, fhp, kiocb->ki_pos,
@@ -1467,6 +1469,33 @@ nfsd_direct_write(struct svc_rqst *rqstp, struct svc_fh *fhp,
 		expected = iov_iter_count(&segments[i].iter);
 
 		host_err = vfs_iocb_iter_write(file, kiocb, &segments[i].iter);
+		if (unlikely(host_err == -EINVAL &&
+			     (kiocb->ki_flags & IOCB_DIRECT))) {
+			/*
+			 * nfsd_dio_iter_is_aligned() approves the iterator
+			 * against the file's STATX_DIOALIGN attributes, but
+			 * the block stack applies stricter geometry tests at
+			 * split time (bio_split_io_at() checks each bvec's
+			 * offset and length against the queue's dma_alignment
+			 * and may find no valid block-size-aligned split).
+			 * A receive-buffer iterator can pass the former and
+			 * still fail the latter at split time, so treat
+			 * -EINVAL from the direct attempt as "not direct-able"
+			 * and retry the segment as (uncached) buffered I/O
+			 * rather than failing the WRITE.  ki_pos is not
+			 * advanced on error, and any sectors the failed
+			 * attempt already reached are rewritten with the
+			 * same data.
+			 */
+			segments[i].iter = saved_iter;
+			kiocb->ki_flags &= ~IOCB_DIRECT;
+			if (file->f_op->fop_flags & FOP_DONTCACHE)
+				kiocb->ki_flags |= IOCB_DONTCACHE;
+			trace_nfsd_write_vector(rqstp, fhp, kiocb->ki_pos,
+						segments[i].iter.count);
+			host_err = vfs_iocb_iter_write(file, kiocb,
+						       &segments[i].iter);
+		}
 		if (host_err < 0)
 			return host_err;
 		*cnt += host_err;
-- 
2.52.0


  parent reply	other threads:[~2026-09-08 16:34 UTC|newest]

Thread overview: 24+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-08 16:32 [PATCH 0/4] block, nfsd: fixes for sub-sector bvec direct I/O Mike Snitzer
2026-09-08 16:32 ` [PATCH 1/4] brd: iterate the bio by byte position, not bi_sector Mike Snitzer
2026-09-08 16:34 ` [PATCH 0/4] block, nfsd: fixes for sub-sector bvec direct I/O Mike Snitzer
2026-09-08 16:34   ` [PATCH 1/4] brd: iterate the bio by byte position, not bi_sector Mike Snitzer
2026-09-08 16:34   ` [PATCH 2/4] zram: handle sub-page bvec segments without corrupting data Mike Snitzer
2026-09-08 16:34   ` [PATCH 3/4] nfsd: fetch direct I/O alignment for files handed to the filecache Mike Snitzer
2026-09-09 14:11     ` Chuck Lever
2026-09-08 16:34   ` Mike Snitzer [this message]
2026-09-08 18:25     ` [PATCH 4/4] nfsd: fall back to buffered I/O when a direct write gets -EINVAL Chuck Lever
     [not found]       ` <B3A1EA3A-00AA-4A56-A644-9AC77FF50CAF@hammerspace.com>
2026-09-09 13:44         ` Chuck Lever
2026-09-09 16:40       ` Mike Snitzer
2026-09-10  9:53       ` Christoph Hellwig
2026-09-08 16:34   ` [PATCH 0/4] block, nfsd: fixes for sub-sector bvec direct I/O Mike Snitzer
2026-09-08 16:34   ` [PATCH 1/4] brd: iterate the bio by byte position, not bi_sector Mike Snitzer
2026-09-08 16:34   ` [PATCH 2/4] zram: handle sub-page bvec segments without corrupting data Mike Snitzer
2026-09-08 16:34   ` [PATCH 3/4] nfsd: fetch direct I/O alignment for files handed to the filecache Mike Snitzer
2026-09-08 16:34   ` [PATCH 4/4] nfsd: fall back to buffered I/O when a direct write gets -EINVAL Mike Snitzer
2026-09-08 16:34   ` [PATCH 2/4] zram: handle sub-page bvec segments without corrupting data Mike Snitzer
2026-09-08 16:34   ` [PATCH 3/4] nfsd: fetch direct I/O alignment for files handed to the filecache Mike Snitzer
2026-09-08 16:34   ` [PATCH 4/4] nfsd: fall back to buffered I/O when a direct write gets -EINVAL Mike Snitzer
2026-09-08 16:36   ` [PATCH 0/4] block, nfsd: fixes for sub-sector bvec direct I/O Mike Snitzer
2026-09-08 17:48     ` Chuck Lever
2026-09-08 18:06       ` Mike Snitzer
2026-09-10  7:14     ` Christoph Hellwig

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=20260908163448.30841-5-snitzer@kernel.org \
    --to=snitzer@kernel.org \
    --cc=axboe@kernel.dk \
    --cc=cel@kernel.org \
    --cc=david.flynn@hammerspace.com \
    --cc=dm-devel@lists.linux.dev \
    --cc=jlayton@kernel.org \
    --cc=linux-block@vger.kernel.org \
    --cc=linux-nfs@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