Re: 9p EBADF with cache enabled (Was: 9p fs-cache tests/benchmark (was: 9p fscache Duplicate cookie detected))

From: David Howells
Date: Mon Apr 25 2022 - 10:10:32 EST


There may be a quick and dirty workaround. I think the problem is that unless
the O_APPEND read starts at the beginning of a page, netfs is going to enforce
a read. Does the attached patch fix the problem? (note that it's untested)

Also, can you get the contents of /proc/fs/fscache/stats from after
reproducing the problem?

David
---
diff --git a/fs/9p/vfs_addr.c b/fs/9p/vfs_addr.c
index 501128188343..5f61fdb950b0 100644
--- a/fs/9p/vfs_addr.c
+++ b/fs/9p/vfs_addr.c
@@ -291,16 +291,25 @@ static int v9fs_write_end(struct file *filp, struct address_space *mapping,
struct folio *folio = page_folio(subpage);
struct inode *inode = mapping->host;
struct v9fs_inode *v9inode = V9FS_I(inode);
+ size_t fsize = folio_size(folio);
+ size_t offset = pos & (fsize - 1);
+ /* With multipage folio support, we may be given len > fsize */
+ size_t copy_size = min_t(size_t, len, fsize - offset);

p9_debug(P9_DEBUG_VFS, "filp %p, mapping %p\n", filp, mapping);

if (!folio_test_uptodate(folio)) {
- if (unlikely(copied < len)) {
+ if (unlikely(copied < copy_size)) {
copied = 0;
goto out;
}
-
- folio_mark_uptodate(folio);
+ if (offset == 0) {
+ if (copied == fsize)
+ folio_mark_uptodate(folio);
+ /* Could clear to end of page if last_pos == new EOF
+ * and then mark uptodate
+ */
+ }
}

/*
diff --git a/fs/netfs/buffered_read.c b/fs/netfs/buffered_read.c
index 281a88a5b8dc..78439f628c23 100644
--- a/fs/netfs/buffered_read.c
+++ b/fs/netfs/buffered_read.c
@@ -364,6 +364,12 @@ int netfs_write_begin(struct file *file, struct address_space *mapping,
if (folio_test_uptodate(folio))
goto have_folio;

+ if (!netfs_is_cache_enabled(ctx) &&
+ (file->f_flags & (O_APPEND | O_ACCMODE)) == (O_APPEND | O_WRONLY)) {
+ netfs_stat(&netfs_n_rh_write_append);
+ goto have_folio_no_wait;
+ }
+
/* If the page is beyond the EOF, we want to clear it - unless it's
* within the cache granule containing the EOF, in which case we need
* to preload the granule.
diff --git a/fs/netfs/internal.h b/fs/netfs/internal.h
index b7b0e3d18d9e..a1cd649197dc 100644
--- a/fs/netfs/internal.h
+++ b/fs/netfs/internal.h
@@ -67,6 +67,7 @@ extern atomic_t netfs_n_rh_read_failed;
extern atomic_t netfs_n_rh_zero;
extern atomic_t netfs_n_rh_short_read;
extern atomic_t netfs_n_rh_write;
+extern atomic_t netfs_n_rh_write_append;
extern atomic_t netfs_n_rh_write_begin;
extern atomic_t netfs_n_rh_write_done;
extern atomic_t netfs_n_rh_write_failed;
diff --git a/fs/netfs/stats.c b/fs/netfs/stats.c
index 5510a7a14a40..fce87f86f950 100644
--- a/fs/netfs/stats.c
+++ b/fs/netfs/stats.c
@@ -23,6 +23,7 @@ atomic_t netfs_n_rh_read_failed;
atomic_t netfs_n_rh_zero;
atomic_t netfs_n_rh_short_read;
atomic_t netfs_n_rh_write;
+atomic_t netfs_n_rh_write_append;
atomic_t netfs_n_rh_write_begin;
atomic_t netfs_n_rh_write_done;
atomic_t netfs_n_rh_write_failed;
@@ -37,10 +38,11 @@ void netfs_stats_show(struct seq_file *m)
atomic_read(&netfs_n_rh_write_zskip),
atomic_read(&netfs_n_rh_rreq),
atomic_read(&netfs_n_rh_sreq));
- seq_printf(m, "RdHelp : ZR=%u sh=%u sk=%u\n",
+ seq_printf(m, "RdHelp : ZR=%u sh=%u sk=%u wa=%u\n",
atomic_read(&netfs_n_rh_zero),
atomic_read(&netfs_n_rh_short_read),
- atomic_read(&netfs_n_rh_write_zskip));
+ atomic_read(&netfs_n_rh_write_zskip),
+ atomic_read(&netfs_n_rh_write_append));
seq_printf(m, "RdHelp : DL=%u ds=%u df=%u di=%u\n",
atomic_read(&netfs_n_rh_download),
atomic_read(&netfs_n_rh_download_done),