Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

netfs: Fix interaction between write-streaming and cachefiles culling

An issue can occur between write-streaming (storing dirty data in partial
non-uptodate pages) and a cachefiles object being culled to make space.
The problem occurs because the cache object is only marked in use while
there are files open using it. Once it has been released, it can be culled
and the cookie marked disabled.

At this point, a streaming write is permitted to occur (if the cache is
active, we require pages to be prefetched and cached), but the cache can
become active again before this gets flushed out - and then two effects can
occur:

(1) The cache may be asked to write out a region that's less than its DIO
block size (assumed by cachefiles to be PAGE_SIZE) - and this causes
one of two debugging statements to be emitted.

(2) netfs_how_to_modify() gets confused because it sees a page that isn't
allowed to be non-uptodate being uptodate and tries to prefetch it -
leading to a warning that PG_fscache is set twice.

Fix this by the following means:

(1) Add a netfs_inode flag to disallow write-streaming to an inode and set
it if we ever do local caching of that inode. It remains set for the
lifetime of that inode - even if the cookie becomes disabled.

(2) If the no-write-streaming flag is set, then make netfs_how_to_modify()
always want to prefetch instead.

(3) If netfs_how_to_modify() decides it wants to prefetch a folio, but
that folio has write-streamed data in it, then it requires the folio
be flushed first.

(4) Export a counter of the number of times we wanted to prefetch a
non-uptodate page, but found it had write-streamed data in it.

(5) Export a counter of the number of times we cancelled a write to the
cache because it didn't DIO align and remove the debug statements.

Reported-by: Marc Dionne <marc.dionne@auristor.com>
Signed-off-by: David Howells <dhowells@redhat.com>
cc: Jeff Layton <jlayton@kernel.org>
cc: linux-cachefs@redhat.com
cc: linux-erofs@lists.ozlabs.org
cc: linux-fsdevel@vger.kernel.org
cc: linux-mm@kvack.org

+40 -14
+6 -6
fs/cachefiles/io.c
··· 528 528 529 529 /* Round to DIO size */ 530 530 start = round_down(*_start, PAGE_SIZE); 531 - if (start != *_start) { 532 - kleave(" = -ENOBUFS [down]"); 533 - return -ENOBUFS; 534 - } 535 - if (*_len > upper_len) { 536 - kleave(" = -ENOBUFS [up]"); 531 + if (start != *_start || *_len > upper_len) { 532 + /* Probably asked to cache a streaming write written into the 533 + * pagecache when the cookie was temporarily out of service to 534 + * culling. 535 + */ 536 + fscache_count_dio_misfit(); 537 537 return -ENOBUFS; 538 538 } 539 539
+19 -3
fs/netfs/buffered_write.c
··· 80 80 return NETFS_WHOLE_FOLIO_MODIFY; 81 81 82 82 if (file->f_mode & FMODE_READ) 83 - return NETFS_JUST_PREFETCH; 83 + goto no_write_streaming; 84 + if (test_bit(NETFS_ICTX_NO_WRITE_STREAMING, &ctx->flags)) 85 + goto no_write_streaming; 84 86 85 - if (netfs_is_cache_enabled(ctx)) 86 - return NETFS_JUST_PREFETCH; 87 + if (netfs_is_cache_enabled(ctx)) { 88 + /* We don't want to get a streaming write on a file that loses 89 + * caching service temporarily because the backing store got 90 + * culled. 91 + */ 92 + if (!test_bit(NETFS_ICTX_NO_WRITE_STREAMING, &ctx->flags)) 93 + set_bit(NETFS_ICTX_NO_WRITE_STREAMING, &ctx->flags); 94 + goto no_write_streaming; 95 + } 87 96 88 97 if (!finfo) 89 98 return NETFS_STREAMING_WRITE; ··· 104 95 if (offset == finfo->dirty_offset + finfo->dirty_len) 105 96 return NETFS_STREAMING_WRITE_CONT; 106 97 return NETFS_FLUSH_CONTENT; 98 + 99 + no_write_streaming: 100 + if (finfo) { 101 + netfs_stat(&netfs_n_wh_wstream_conflict); 102 + return NETFS_FLUSH_CONTENT; 103 + } 104 + return NETFS_JUST_PREFETCH; 107 105 } 108 106 109 107 /*
+6 -3
fs/netfs/fscache_stats.c
··· 48 48 EXPORT_SYMBOL(fscache_n_no_create_space); 49 49 atomic_t fscache_n_culled; 50 50 EXPORT_SYMBOL(fscache_n_culled); 51 + atomic_t fscache_n_dio_misfit; 52 + EXPORT_SYMBOL(fscache_n_dio_misfit); 51 53 52 54 /* 53 55 * display the general statistics 54 56 */ 55 57 int fscache_stats_show(struct seq_file *m) 56 58 { 57 - seq_puts(m, "FS-Cache statistics\n"); 59 + seq_puts(m, "-- FS-Cache statistics --\n"); 58 60 seq_printf(m, "Cookies: n=%d v=%d vcol=%u voom=%u\n", 59 61 atomic_read(&fscache_n_cookies), 60 62 atomic_read(&fscache_n_volumes), ··· 95 93 atomic_read(&fscache_n_no_create_space), 96 94 atomic_read(&fscache_n_culled)); 97 95 98 - seq_printf(m, "IO : rd=%u wr=%u\n", 96 + seq_printf(m, "IO : rd=%u wr=%u mis=%u\n", 99 97 atomic_read(&fscache_n_read), 100 - atomic_read(&fscache_n_write)); 98 + atomic_read(&fscache_n_write), 99 + atomic_read(&fscache_n_dio_misfit)); 101 100 return 0; 102 101 }
+1
fs/netfs/internal.h
··· 123 123 extern atomic_t netfs_n_rh_write_done; 124 124 extern atomic_t netfs_n_rh_write_failed; 125 125 extern atomic_t netfs_n_rh_write_zskip; 126 + extern atomic_t netfs_n_wh_wstream_conflict; 126 127 extern atomic_t netfs_n_wh_upload; 127 128 extern atomic_t netfs_n_wh_upload_done; 128 129 extern atomic_t netfs_n_wh_upload_failed;
+4 -2
fs/netfs/stats.c
··· 29 29 atomic_t netfs_n_rh_write_done; 30 30 atomic_t netfs_n_rh_write_failed; 31 31 atomic_t netfs_n_rh_write_zskip; 32 + atomic_t netfs_n_wh_wstream_conflict; 32 33 atomic_t netfs_n_wh_upload; 33 34 atomic_t netfs_n_wh_upload_done; 34 35 atomic_t netfs_n_wh_upload_failed; ··· 67 66 atomic_read(&netfs_n_wh_write), 68 67 atomic_read(&netfs_n_wh_write_done), 69 68 atomic_read(&netfs_n_wh_write_failed)); 70 - seq_printf(m, "Netfs : rr=%u sr=%u\n", 69 + seq_printf(m, "Netfs : rr=%u sr=%u wsc=%u\n", 71 70 atomic_read(&netfs_n_rh_rreq), 72 - atomic_read(&netfs_n_rh_sreq)); 71 + atomic_read(&netfs_n_rh_sreq), 72 + atomic_read(&netfs_n_wh_wstream_conflict)); 73 73 return fscache_stats_show(m); 74 74 } 75 75 EXPORT_SYMBOL(netfs_stats_show);
+3
include/linux/fscache-cache.h
··· 189 189 extern atomic_t fscache_n_no_write_space; 190 190 extern atomic_t fscache_n_no_create_space; 191 191 extern atomic_t fscache_n_culled; 192 + extern atomic_t fscache_n_dio_misfit; 192 193 #define fscache_count_read() atomic_inc(&fscache_n_read) 193 194 #define fscache_count_write() atomic_inc(&fscache_n_write) 194 195 #define fscache_count_no_write_space() atomic_inc(&fscache_n_no_write_space) 195 196 #define fscache_count_no_create_space() atomic_inc(&fscache_n_no_create_space) 196 197 #define fscache_count_culled() atomic_inc(&fscache_n_culled) 198 + #define fscache_count_dio_misfit() atomic_inc(&fscache_n_dio_misfit) 197 199 #else 198 200 #define fscache_count_read() do {} while(0) 199 201 #define fscache_count_write() do {} while(0) 200 202 #define fscache_count_no_write_space() do {} while(0) 201 203 #define fscache_count_no_create_space() do {} while(0) 202 204 #define fscache_count_culled() do {} while(0) 205 + #define fscache_count_dio_misfit() do {} while(0) 203 206 #endif 204 207 205 208 #endif /* _LINUX_FSCACHE_CACHE_H */
+1
include/linux/netfs.h
··· 142 142 #define NETFS_ICTX_ODIRECT 0 /* The file has DIO in progress */ 143 143 #define NETFS_ICTX_UNBUFFERED 1 /* I/O should not use the pagecache */ 144 144 #define NETFS_ICTX_WRITETHROUGH 2 /* Write-through caching */ 145 + #define NETFS_ICTX_NO_WRITE_STREAMING 3 /* Don't engage in write-streaming */ 145 146 }; 146 147 147 148 /*