If the server stops responding, a task in fsync() sits in __filemap_fdatawait_range() -> folio_wait_writeback(), which is TASK_UNINTERRUPTIBLE, so SIGKILL can't get it out. On a hard mount the only way to recover is to reboot the client. Do the wait in NFS with folio_wait_writeback_killable() instead. Only the wait for writeback to complete becomes killable. Submission is unchanged and can still block. When the wait is interrupted, fsync returns -EINTR without advancing the file's writeback error cursor, so any error is still reported to a later caller. Writeback itself carries on in the background. The wait loop is a copy of __filemap_fdatawait_range() built from existing exports, so mm doesn't need to change. The interruption is reported separately from the submission error so that an -EINTR from submission (e.g. a pNFS layout wait for an exiting task) is handled exactly as before. Signed-off-by: Rushil Patel --- fs/nfs/file.c | 2 +- fs/nfs/internal.h | 2 ++ fs/nfs/write.c | 64 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 1 deletion(-) diff --git a/fs/nfs/file.c b/fs/nfs/file.c index e1bdd10b35f1..ce160051b0c6 100644 --- a/fs/nfs/file.c +++ b/fs/nfs/file.c @@ -270,7 +270,7 @@ nfs_file_fsync(struct file *file, loff_t start, loff_t end, int datasync) trace_nfs_fsync_enter(inode); for (;;) { - ret = file_write_and_wait_range(file, start, end); + ret = nfs_file_write_and_wait_range(file, start, end); if (ret != 0) break; ret = nfs_file_fsync_commit(file, datasync); diff --git a/fs/nfs/internal.h b/fs/nfs/internal.h index abc81f5ae578..c7bb7133afa5 100644 --- a/fs/nfs/internal.h +++ b/fs/nfs/internal.h @@ -592,6 +592,8 @@ int nfs_show_stats(struct seq_file *, struct dentry *); int nfs_reconfigure(struct fs_context *); /* write.c */ +extern int nfs_file_write_and_wait_range(struct file *file, loff_t start, + loff_t end); extern void nfs_pageio_init_write(struct nfs_pageio_descriptor *pgio, struct inode *inode, int ioflags, bool force_mds, const struct nfs_pgio_completion_ops *compl_ops); diff --git a/fs/nfs/write.c b/fs/nfs/write.c index 623e7ef1f73d..c5a911305da7 100644 --- a/fs/nfs/write.c +++ b/fs/nfs/write.c @@ -1985,6 +1985,70 @@ int nfs_filemap_write_and_wait_range(struct address_space *mapping, } EXPORT_SYMBOL_GPL(nfs_filemap_write_and_wait_range); +/* + * Like filemap_write_and_wait_range(), but a fatal signal interrupts the wait + * for writeback to complete, and errors are left for the caller to collect. + * Starting writeback can still block. *@interrupted tells an interrupted + * wait apart from an error returned by writeback itself. + */ +static int nfs_write_and_wait_range_killable(struct address_space *mapping, + loff_t start, loff_t end, + bool *interrupted) +{ + pgoff_t index = start >> PAGE_SHIFT; + pgoff_t last = end >> PAGE_SHIFT; + struct folio_batch fbatch; + unsigned int i, nr; + int err; + + *interrupted = false; + if (!mapping->nrpages) + return 0; + err = filemap_fdatawrite_range(mapping, start, end); + if (err == -EIO) + return err; + folio_batch_init(&fbatch); + while (index <= last && + (nr = filemap_get_folios_tag(mapping, &index, last, + PAGECACHE_TAG_WRITEBACK, &fbatch))) { + for (i = 0; i < nr; i++) { + if (folio_wait_writeback_killable(fbatch.folios[i])) { + folio_batch_release(&fbatch); + *interrupted = true; + return -EINTR; + } + } + folio_batch_release(&fbatch); + cond_resched(); + } + return err; +} + +/** + * nfs_file_write_and_wait_range - killable file_write_and_wait_range() + * @file: file to write back + * @start: first byte of the range + * @end: last byte of the range (inclusive) + * + * Return: 0 on success, -EINTR if a fatal signal interrupted the wait, in + * which case the file's writeback error cursor is not advanced, or another + * negative error code. + */ +int nfs_file_write_and_wait_range(struct file *file, loff_t start, loff_t end) +{ + bool interrupted; + int err, err2; + + if (end < start) + return 0; + err = nfs_write_and_wait_range_killable(file->f_mapping, start, end, + &interrupted); + if (interrupted) + return err; + err2 = file_check_and_advance_wb_err(file); + return err ? err : err2; +} + /* * flush the inode to disk. */ -- 2.47.3 For details of how GSA uses your personal information, please see our Privacy Notice here: https://www.gsacapital.com/privacy-notice This email and any files transmitted with it contain confidential and proprietary information and is solely for the use of the intended recipient. If you are not the intended recipient please return the email to the sender and delete it from your computer and you must not use, disclose, distribute, copy, print or rely on this email or its contents. This communication is for informational purposes only. It is not intended as an offer or solicitation for the purchase or sale of any financial instrument or as an official confirmation of any transaction. Any comments or statements made herein do not necessarily reflect those of GSA Capital. GSA Capital Partners LLP is authorised and regulated by the Financial Conduct Authority and is registered in England and Wales at Stratton House, 5 Stratton Street, London W1J 8LA, number OC309261. GSA Capital Services Limited is registered in England and Wales at the same address, number 5320529.