From nobody Sat Jun 20 05:01:31 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id EFE4C38945D; Mon, 20 Apr 2026 08:39:43 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776674384; cv=none; b=GnF0mWgGmFoxBls8OsxUM9QM4j1opRKCHInoN9M9pdLdKTDfIXrjBQFVzLufS0rkbWeP4PjRTZ6PFbsi0rf4y52ONZjSs32i3drSje81KYmwRB+XlMLjyrM5vwDbiffvRIfYIjB3oixr/Jw1bMUapwPW4bU7IFORoV428ZIJ46w= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776674384; c=relaxed/simple; bh=zpZdZY5IblUR64ldg4Lr/vEXA8B2WknFa4YzO4pxZdE=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:To:Cc; b=Yur03zVUGKCm4H+TBHTexJhCjN/wx/VbJUX6kCADEpbHuhCicuoeQI8VQVoVW2vvcQG4w8su1sm94Q23ZJXGjWVZMEwGEylQfKD9e20dT89Q99GXqszzEbrOI3H+W1/rNJ/mWlillCm0abLQLOlAuYdfumCzKjoglMqvtNz+a1Y= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=XM/Tj6gP; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="XM/Tj6gP" Received: by smtp.kernel.org (Postfix) with ESMTPS id 969BCC19425; Mon, 20 Apr 2026 08:39:43 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1776674383; bh=zpZdZY5IblUR64ldg4Lr/vEXA8B2WknFa4YzO4pxZdE=; h=From:Date:Subject:To:Cc:Reply-To:From; b=XM/Tj6gP9WzwAS8LhBntsF3/BvEZO+ob6few2CRKNBAe4NTQ2dksvUV4RdjcgVWvc InXR5egs3jaMKRNCu8Py3Xgas8rE2PzR5HyDcV4gVj+JJdxDE44b4d08Ohk5pEDjBb DDzQVT0jzu6RDXzJJ4twaUHF0UxcY4kljG7Tg0zj3u1Pxho0AFh6X9rtEfsciZthul yPv0tDGEcRicvXtTf1NUsSwHss1VupC3Vg6w4FwqWcfxv1rcCRT8DjATpT7GOoGHvu lYlR2ix/sO5EovBATnQOXBlMfNuZij8j/uGHVqSzk3GTzTS1TaKj+ZN3o2bfRPp/17 8M7q20lkTzzCw== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 8CB71F557E6; Mon, 20 Apr 2026 08:39:43 +0000 (UTC) From: Cheng Ding via B4 Relay Date: Mon, 20 Apr 2026 16:39:34 +0800 Subject: [PATCH] fuse: invalidate page cache after DIO and async DIO writes Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260420-b4-new-451-v1-1-1808fa758695@ddn.com> X-B4-Tracking: v=1; b=H4sIAEbm5WkC/x3MQQqAIBBG4avErBtQsYSuEi3Kfms2FgoVhHdPW n6L917KSIJMQ/NSwiVZjlih24b8PscNLGs1GWV6ZY3ixXLEzbbTDA/vfHBWG1ANzoQgzz8bp1I +5L0R7lwAAAA= X-Change-ID: 20260420-b4-new-451-ecec7cf7412e To: Miklos Szeredi Cc: Jingbo Xu , Bernd Schubert , linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org, Cheng Ding X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1776674382; l=7501; i=cding@ddn.com; s=20260306; h=from:subject:message-id; bh=To22O5pFeN05dCS/voHf7CPHVsv+7HOSFw5xW4h5XQE=; b=ccQskVCH6SLYOGnVI7YN/slXlDpn1Bmx0H6KnNYHbrRHpf1jOW9v0IgQEnLfq0BctSTsbY9am dfM0z2VGVlLB9gp+dSCYHhaOHt2RGGOUUBDToRDNsdE3uSzxxnooR92 X-Developer-Key: i=cding@ddn.com; a=ed25519; pk=dzzlP8PhiZl3jtcAzbjIIv0kgtoA95fHdILVyjaeePk= X-Endpoint-Received: by B4 Relay for cding@ddn.com/20260306 with auth_id=667 X-Original-From: Cheng Ding Reply-To: cding@ddn.com From: Cheng Ding This fixe does page cache invalidation after DIO and async DIO writes for both O_DIRECT and FOPEN_DIRECT_IO cases. Commit b359af8275a9 ("fuse: Invalidate the page cache after FOPEN_DIRECT_IO write") fixed xfstests generic/209 for DIO writes in the FOPEN_DIRECT_IO path. DIO writes without FOPEN_DIRECT_IO are already handled by generic_file_direct_write(). However, async DIO writes (xfstests generic/451) remain unhandled. After this fix: - Async write with FUSE_ASYNC_DIO: invalidate in fuse_aio_invalidate_worker() - Otherwise (Sync or async write without FUSE_ASYNC_DIO): - With FOPEN_DIRECT_IO: invalidate in fuse_direct_write_iter() - Without FOPEN_DIRECT_IO: invalidate in generic_file_direct_write() Workqueue is required for async write invalidation to prevent deadlock: calling it directly in the I/O end routine (which is in fuse worker thread context) can block on a folio lock held by a buffered I/O thread waiting for the same fuse worker thread. Co-developed-by: Jingbo Xu Signed-off-by: Jingbo Xu Signed-off-by: Cheng Ding --- Changes in v4: - Add Jingbo as co-developer (with Jingbo's agreement). - Adopt Jingbo's commit message from https://lore.kernel.org/linux-fsdevel/20260111073701.6071-1-jefflexu@linu= x.alibaba.com/, modified to explain why async DIO invalidation needs a workqueue. - Link to v3: https://lore.kernel.org/r/20260309-xfstests-generic-451-v3-1-= bb6ad2f59512@ddn.com Changes in v3: - Address review comments: fix typo - Address review comments: move sb_init_dio_done_wq() to fuse_direct_IO() Note: We could skip sb_init_dio_done_wq() when io->blocking is true, but I opted to keep the change simpler. - Link to v2: https://lore.kernel.org/r/20260306-xfstests-generic-451-v2-1-= 93b2d540304b@ddn.com Changes in v2: - Address review comments: move invalidation from fuse_direct_io() to fuse_direct_write_iter() - Link to v1: https://lore.kernel.org/r/20260303-async-dio-aio-cache-invali= dation-v1-1-fba0fd0426c3@ddn.com --- fs/fuse/file.c | 59 +++++++++++++++++++++++++++++++++++++++++++++-------= ---- fs/fuse/fuse_i.h | 1 + 2 files changed, 49 insertions(+), 11 deletions(-) diff --git a/fs/fuse/file.c b/fs/fuse/file.c index b1bb7153cb78..c43fe74cdd46 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -23,6 +23,8 @@ #include #include =20 +int sb_init_dio_done_wq(struct super_block *sb); + static int fuse_send_open(struct fuse_mount *fm, u64 nodeid, unsigned int open_flags, int opcode, struct fuse_open_out *outargp) @@ -629,6 +631,19 @@ static ssize_t fuse_get_res_by_io(struct fuse_io_priv = *io) return io->bytes < 0 ? io->size : io->bytes; } =20 +static void fuse_aio_invalidate_worker(struct work_struct *work) +{ + struct fuse_io_priv *io =3D container_of(work, struct fuse_io_priv, work); + struct address_space *mapping =3D io->iocb->ki_filp->f_mapping; + ssize_t res =3D fuse_get_res_by_io(io); + pgoff_t start =3D io->offset >> PAGE_SHIFT; + pgoff_t end =3D (io->offset + res - 1) >> PAGE_SHIFT; + + invalidate_inode_pages2_range(mapping, start, end); + io->iocb->ki_complete(io->iocb, res); + kref_put(&io->refcnt, fuse_io_release); +} + /* * In case of short read, the caller sets 'pos' to the position of * actual end of fuse request in IO request. Otherwise, if bytes_requested @@ -661,10 +676,11 @@ static void fuse_aio_complete(struct fuse_io_priv *io= , int err, ssize_t pos) spin_unlock(&io->lock); =20 if (!left && !io->blocking) { + struct inode *inode =3D file_inode(io->iocb->ki_filp); + struct address_space *mapping =3D io->iocb->ki_filp->f_mapping; ssize_t res =3D fuse_get_res_by_io(io); =20 if (res >=3D 0) { - struct inode *inode =3D file_inode(io->iocb->ki_filp); struct fuse_conn *fc =3D get_fuse_conn(inode); struct fuse_inode *fi =3D get_fuse_inode(inode); =20 @@ -673,6 +689,17 @@ static void fuse_aio_complete(struct fuse_io_priv *io,= int err, ssize_t pos) spin_unlock(&fi->lock); } =20 + if (io->write && res > 0 && mapping->nrpages) { + /* + * As in generic_file_direct_write(), invalidate after the + * write, to invalidate read-ahead cache that may have competed + * with the write. + */ + INIT_WORK(&io->work, fuse_aio_invalidate_worker); + queue_work(inode->i_sb->s_dio_done_wq, &io->work); + return; + } + io->iocb->ki_complete(io->iocb, res); } =20 @@ -1738,15 +1765,6 @@ ssize_t fuse_direct_io(struct fuse_io_priv *io, stru= ct iov_iter *iter, if (res > 0) *ppos =3D pos; =20 - if (res > 0 && write && fopen_direct_io) { - /* - * As in generic_file_direct_write(), invalidate after the - * write, to invalidate read-ahead cache that may have competed - * with the write. - */ - invalidate_inode_pages2_range(mapping, idx_from, idx_to); - } - return res > 0 ? res : err; } EXPORT_SYMBOL_GPL(fuse_direct_io); @@ -1785,6 +1803,8 @@ static ssize_t fuse_direct_read_iter(struct kiocb *io= cb, struct iov_iter *to) static ssize_t fuse_direct_write_iter(struct kiocb *iocb, struct iov_iter = *from) { struct inode *inode =3D file_inode(iocb->ki_filp); + struct address_space *mapping =3D inode->i_mapping; + loff_t pos =3D iocb->ki_pos; ssize_t res; bool exclusive; =20 @@ -1801,6 +1821,16 @@ static ssize_t fuse_direct_write_iter(struct kiocb *= iocb, struct iov_iter *from) FUSE_DIO_WRITE); fuse_write_update_attr(inode, iocb->ki_pos, res); } + if (res > 0 && mapping->nrpages) { + /* + * As in generic_file_direct_write(), invalidate after + * write, to invalidate read-ahead cache that may have + * with the write. + */ + invalidate_inode_pages2_range(mapping, + pos >> PAGE_SHIFT, + (pos + res - 1) >> PAGE_SHIFT); + } } fuse_dio_unlock(iocb, exclusive); =20 @@ -2826,6 +2856,7 @@ fuse_direct_IO(struct kiocb *iocb, struct iov_iter *i= ter) size_t count =3D iov_iter_count(iter), shortened =3D 0; loff_t offset =3D iocb->ki_pos; struct fuse_io_priv *io; + bool async =3D ff->fm->fc->async_dio; =20 pos =3D offset; inode =3D file->f_mapping->host; @@ -2834,6 +2865,12 @@ fuse_direct_IO(struct kiocb *iocb, struct iov_iter *= iter) if ((iov_iter_rw(iter) =3D=3D READ) && (offset >=3D i_size)) return 0; =20 + if ((iov_iter_rw(iter) =3D=3D WRITE) && async && !inode->i_sb->s_dio_done= _wq) { + ret =3D sb_init_dio_done_wq(inode->i_sb); + if (ret < 0) + return ret; + } + io =3D kmalloc_obj(struct fuse_io_priv); if (!io) return -ENOMEM; @@ -2849,7 +2886,7 @@ fuse_direct_IO(struct kiocb *iocb, struct iov_iter *i= ter) * By default, we want to optimize all I/Os with async request * submission to the client filesystem if supported. */ - io->async =3D ff->fm->fc->async_dio; + io->async =3D async; io->iocb =3D iocb; io->blocking =3D is_sync_kiocb(iocb); =20 diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 7f16049387d1..6e8c8cf6b2c8 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -377,6 +377,7 @@ union fuse_file_args { /** The request IO state (for asynchronous processing) */ struct fuse_io_priv { struct kref refcnt; + struct work_struct work; int async; spinlock_t lock; unsigned reqs; --- base-commit: b29fb8829bff243512bb8c8908fd39406f9fd4c3 change-id: 20260420-b4-new-451-ecec7cf7412e Best regards, --=20 Cheng Ding