Commit 92a714d7 authored by David Howells's avatar David Howells

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: default avatarMarc Dionne <marc.dionne@auristor.com>
Signed-off-by: default avatarDavid 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
parent 4088e389
...@@ -528,12 +528,12 @@ int __cachefiles_prepare_write(struct cachefiles_object *object, ...@@ -528,12 +528,12 @@ int __cachefiles_prepare_write(struct cachefiles_object *object,
/* Round to DIO size */ /* Round to DIO size */
start = round_down(*_start, PAGE_SIZE); start = round_down(*_start, PAGE_SIZE);
if (start != *_start) { if (start != *_start || *_len > upper_len) {
kleave(" = -ENOBUFS [down]"); /* Probably asked to cache a streaming write written into the
return -ENOBUFS; * pagecache when the cookie was temporarily out of service to
} * culling.
if (*_len > upper_len) { */
kleave(" = -ENOBUFS [up]"); fscache_count_dio_misfit();
return -ENOBUFS; return -ENOBUFS;
} }
......
...@@ -80,10 +80,19 @@ static enum netfs_how_to_modify netfs_how_to_modify(struct netfs_inode *ctx, ...@@ -80,10 +80,19 @@ static enum netfs_how_to_modify netfs_how_to_modify(struct netfs_inode *ctx,
return NETFS_WHOLE_FOLIO_MODIFY; return NETFS_WHOLE_FOLIO_MODIFY;
if (file->f_mode & FMODE_READ) if (file->f_mode & FMODE_READ)
return NETFS_JUST_PREFETCH; goto no_write_streaming;
if (test_bit(NETFS_ICTX_NO_WRITE_STREAMING, &ctx->flags))
if (netfs_is_cache_enabled(ctx)) goto no_write_streaming;
return NETFS_JUST_PREFETCH;
if (netfs_is_cache_enabled(ctx)) {
/* We don't want to get a streaming write on a file that loses
* caching service temporarily because the backing store got
* culled.
*/
if (!test_bit(NETFS_ICTX_NO_WRITE_STREAMING, &ctx->flags))
set_bit(NETFS_ICTX_NO_WRITE_STREAMING, &ctx->flags);
goto no_write_streaming;
}
if (!finfo) if (!finfo)
return NETFS_STREAMING_WRITE; return NETFS_STREAMING_WRITE;
...@@ -95,6 +104,13 @@ static enum netfs_how_to_modify netfs_how_to_modify(struct netfs_inode *ctx, ...@@ -95,6 +104,13 @@ static enum netfs_how_to_modify netfs_how_to_modify(struct netfs_inode *ctx,
if (offset == finfo->dirty_offset + finfo->dirty_len) if (offset == finfo->dirty_offset + finfo->dirty_len)
return NETFS_STREAMING_WRITE_CONT; return NETFS_STREAMING_WRITE_CONT;
return NETFS_FLUSH_CONTENT; return NETFS_FLUSH_CONTENT;
no_write_streaming:
if (finfo) {
netfs_stat(&netfs_n_wh_wstream_conflict);
return NETFS_FLUSH_CONTENT;
}
return NETFS_JUST_PREFETCH;
} }
/* /*
......
...@@ -48,13 +48,15 @@ atomic_t fscache_n_no_create_space; ...@@ -48,13 +48,15 @@ atomic_t fscache_n_no_create_space;
EXPORT_SYMBOL(fscache_n_no_create_space); EXPORT_SYMBOL(fscache_n_no_create_space);
atomic_t fscache_n_culled; atomic_t fscache_n_culled;
EXPORT_SYMBOL(fscache_n_culled); EXPORT_SYMBOL(fscache_n_culled);
atomic_t fscache_n_dio_misfit;
EXPORT_SYMBOL(fscache_n_dio_misfit);
/* /*
* display the general statistics * display the general statistics
*/ */
int fscache_stats_show(struct seq_file *m) int fscache_stats_show(struct seq_file *m)
{ {
seq_puts(m, "FS-Cache statistics\n"); seq_puts(m, "-- FS-Cache statistics --\n");
seq_printf(m, "Cookies: n=%d v=%d vcol=%u voom=%u\n", seq_printf(m, "Cookies: n=%d v=%d vcol=%u voom=%u\n",
atomic_read(&fscache_n_cookies), atomic_read(&fscache_n_cookies),
atomic_read(&fscache_n_volumes), atomic_read(&fscache_n_volumes),
...@@ -93,8 +95,9 @@ int fscache_stats_show(struct seq_file *m) ...@@ -93,8 +95,9 @@ int fscache_stats_show(struct seq_file *m)
atomic_read(&fscache_n_no_create_space), atomic_read(&fscache_n_no_create_space),
atomic_read(&fscache_n_culled)); atomic_read(&fscache_n_culled));
seq_printf(m, "IO : rd=%u wr=%u\n", seq_printf(m, "IO : rd=%u wr=%u mis=%u\n",
atomic_read(&fscache_n_read), atomic_read(&fscache_n_read),
atomic_read(&fscache_n_write)); atomic_read(&fscache_n_write),
atomic_read(&fscache_n_dio_misfit));
return 0; return 0;
} }
...@@ -123,6 +123,7 @@ extern atomic_t netfs_n_rh_write_begin; ...@@ -123,6 +123,7 @@ extern atomic_t netfs_n_rh_write_begin;
extern atomic_t netfs_n_rh_write_done; extern atomic_t netfs_n_rh_write_done;
extern atomic_t netfs_n_rh_write_failed; extern atomic_t netfs_n_rh_write_failed;
extern atomic_t netfs_n_rh_write_zskip; extern atomic_t netfs_n_rh_write_zskip;
extern atomic_t netfs_n_wh_wstream_conflict;
extern atomic_t netfs_n_wh_upload; extern atomic_t netfs_n_wh_upload;
extern atomic_t netfs_n_wh_upload_done; extern atomic_t netfs_n_wh_upload_done;
extern atomic_t netfs_n_wh_upload_failed; extern atomic_t netfs_n_wh_upload_failed;
......
...@@ -29,6 +29,7 @@ atomic_t netfs_n_rh_write_begin; ...@@ -29,6 +29,7 @@ atomic_t netfs_n_rh_write_begin;
atomic_t netfs_n_rh_write_done; atomic_t netfs_n_rh_write_done;
atomic_t netfs_n_rh_write_failed; atomic_t netfs_n_rh_write_failed;
atomic_t netfs_n_rh_write_zskip; atomic_t netfs_n_rh_write_zskip;
atomic_t netfs_n_wh_wstream_conflict;
atomic_t netfs_n_wh_upload; atomic_t netfs_n_wh_upload;
atomic_t netfs_n_wh_upload_done; atomic_t netfs_n_wh_upload_done;
atomic_t netfs_n_wh_upload_failed; atomic_t netfs_n_wh_upload_failed;
...@@ -66,9 +67,10 @@ int netfs_stats_show(struct seq_file *m, void *v) ...@@ -66,9 +67,10 @@ int netfs_stats_show(struct seq_file *m, void *v)
atomic_read(&netfs_n_wh_write), atomic_read(&netfs_n_wh_write),
atomic_read(&netfs_n_wh_write_done), atomic_read(&netfs_n_wh_write_done),
atomic_read(&netfs_n_wh_write_failed)); atomic_read(&netfs_n_wh_write_failed));
seq_printf(m, "Netfs : rr=%u sr=%u\n", seq_printf(m, "Netfs : rr=%u sr=%u wsc=%u\n",
atomic_read(&netfs_n_rh_rreq), atomic_read(&netfs_n_rh_rreq),
atomic_read(&netfs_n_rh_sreq)); atomic_read(&netfs_n_rh_sreq),
atomic_read(&netfs_n_wh_wstream_conflict));
return fscache_stats_show(m); return fscache_stats_show(m);
} }
EXPORT_SYMBOL(netfs_stats_show); EXPORT_SYMBOL(netfs_stats_show);
...@@ -189,17 +189,20 @@ extern atomic_t fscache_n_write; ...@@ -189,17 +189,20 @@ extern atomic_t fscache_n_write;
extern atomic_t fscache_n_no_write_space; extern atomic_t fscache_n_no_write_space;
extern atomic_t fscache_n_no_create_space; extern atomic_t fscache_n_no_create_space;
extern atomic_t fscache_n_culled; extern atomic_t fscache_n_culled;
extern atomic_t fscache_n_dio_misfit;
#define fscache_count_read() atomic_inc(&fscache_n_read) #define fscache_count_read() atomic_inc(&fscache_n_read)
#define fscache_count_write() atomic_inc(&fscache_n_write) #define fscache_count_write() atomic_inc(&fscache_n_write)
#define fscache_count_no_write_space() atomic_inc(&fscache_n_no_write_space) #define fscache_count_no_write_space() atomic_inc(&fscache_n_no_write_space)
#define fscache_count_no_create_space() atomic_inc(&fscache_n_no_create_space) #define fscache_count_no_create_space() atomic_inc(&fscache_n_no_create_space)
#define fscache_count_culled() atomic_inc(&fscache_n_culled) #define fscache_count_culled() atomic_inc(&fscache_n_culled)
#define fscache_count_dio_misfit() atomic_inc(&fscache_n_dio_misfit)
#else #else
#define fscache_count_read() do {} while(0) #define fscache_count_read() do {} while(0)
#define fscache_count_write() do {} while(0) #define fscache_count_write() do {} while(0)
#define fscache_count_no_write_space() do {} while(0) #define fscache_count_no_write_space() do {} while(0)
#define fscache_count_no_create_space() do {} while(0) #define fscache_count_no_create_space() do {} while(0)
#define fscache_count_culled() do {} while(0) #define fscache_count_culled() do {} while(0)
#define fscache_count_dio_misfit() do {} while(0)
#endif #endif
#endif /* _LINUX_FSCACHE_CACHE_H */ #endif /* _LINUX_FSCACHE_CACHE_H */
...@@ -142,6 +142,7 @@ struct netfs_inode { ...@@ -142,6 +142,7 @@ struct netfs_inode {
#define NETFS_ICTX_ODIRECT 0 /* The file has DIO in progress */ #define NETFS_ICTX_ODIRECT 0 /* The file has DIO in progress */
#define NETFS_ICTX_UNBUFFERED 1 /* I/O should not use the pagecache */ #define NETFS_ICTX_UNBUFFERED 1 /* I/O should not use the pagecache */
#define NETFS_ICTX_WRITETHROUGH 2 /* Write-through caching */ #define NETFS_ICTX_WRITETHROUGH 2 /* Write-through caching */
#define NETFS_ICTX_NO_WRITE_STREAMING 3 /* Don't engage in write-streaming */
}; };
/* /*
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment