FS-Cache: Add a helper to bulk uncache pages on an inode
authorDavid Howells <dhowells@redhat.com>
Thu, 7 Jul 2011 11:19:48 +0000 (12:19 +0100)
committerLinus Torvalds <torvalds@linux-foundation.org>
Thu, 7 Jul 2011 20:21:56 +0000 (13:21 -0700)
Add an FS-Cache helper to bulk uncache pages on an inode.  This will
only work for the circumstance where the pages in the cache correspond
1:1 with the pages attached to an inode's page cache.

This is required for CIFS and NFS: When disabling inode cookie, we were
returning the cookie and setting cifsi->fscache to NULL but failed to
invalidate any previously mapped pages.  This resulted in "Bad page
state" errors and manifested in other kind of errors when running
fsstress.  Fix it by uncaching mapped pages when we disable the inode
cookie.

This patch should fix the following oops and "Bad page state" errors
seen during fsstress testing.

  ------------[ cut here ]------------
  kernel BUG at fs/cachefiles/namei.c:201!
  invalid opcode: 0000 [#1] SMP
  Pid: 5, comm: kworker/u:0 Not tainted 2.6.38.7-30.fc15.x86_64 #1 Bochs Bochs
  RIP: 0010: cachefiles_walk_to_object+0x436/0x745 [cachefiles]
  RSP: 0018:ffff88002ce6dd00  EFLAGS: 00010282
  RAX: ffff88002ef165f0 RBX: ffff88001811f500 RCX: 0000000000000000
  RDX: 0000000000000000 RSI: 0000000000000100 RDI: 0000000000000282
  RBP: ffff88002ce6dda0 R08: 0000000000000100 R09: ffffffff81b3a300
  R10: 0000ffff00066c0a R11: 0000000000000003 R12: ffff88002ae54840
  R13: ffff88002ae54840 R14: ffff880029c29c00 R15: ffff88001811f4b0
  FS:  00007f394dd32720(0000) GS:ffff88002ef00000(0000) knlGS:0000000000000000
  CS:  0010 DS: 0000 ES: 0000 CR0: 000000008005003b
  CR2: 00007fffcb62ddf8 CR3: 000000001825f000 CR4: 00000000000006e0
  DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000
  DR3: 0000000000000000 DR6: 00000000ffff0ff0 DR7: 0000000000000400
  Process kworker/u:0 (pid: 5, threadinfo ffff88002ce6c000, task ffff88002ce55cc0)
  Stack:
   0000000000000246 ffff88002ce55cc0 ffff88002ce6dd58 ffff88001815dc00
   ffff8800185246c0 ffff88001811f618 ffff880029c29d18 ffff88001811f380
   ffff88002ce6dd50 ffffffff814757e4 ffff88002ce6dda0 ffffffff8106ac56
  Call Trace:
   cachefiles_lookup_object+0x78/0xd4 [cachefiles]
   fscache_lookup_object+0x131/0x16d [fscache]
   fscache_object_work_func+0x1bc/0x669 [fscache]
   process_one_work+0x186/0x298
   worker_thread+0xda/0x15d
   kthread+0x84/0x8c
   kernel_thread_helper+0x4/0x10
  RIP  cachefiles_walk_to_object+0x436/0x745 [cachefiles]
  ---[ end trace 1d481c9af1804caa ]---

I tested the uncaching by the following means:

 (1) Create a big file on my NFS server (104857600 bytes).

 (2) Read the file into the cache with md5sum on the NFS client.  Look in
     /proc/fs/fscache/stats:

Pages  : mrk=25601 unc=0

 (3) Open the file for read/write ("bash 5<>/warthog/bigfile").  Look in proc
     again:

Pages  : mrk=25601 unc=25601

Reported-by: Jeff Layton <jlayton@redhat.com>
Signed-off-by: David Howells <dhowells@redhat.com>
Reviewed-and-Tested-by: Suresh Jayaraman <sjayaraman@suse.de>
cc: stable@kernel.org
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Documentation/filesystems/caching/netfs-api.txt
fs/cifs/fscache.c
fs/fscache/page.c
fs/nfs/fscache.c
include/linux/fscache.h

index a167ab876c357111c8e5ba4e1a204bda29a7285e..7cc6bf2871ebfbae736436959a3d8a2e5153334d 100644 (file)
@@ -673,6 +673,22 @@ storage request to complete, or it may attempt to cancel the storage request -
 in which case the page will not be stored in the cache this time.
 
 
+BULK INODE PAGE UNCACHE
+-----------------------
+
+A convenience routine is provided to perform an uncache on all the pages
+attached to an inode.  This assumes that the pages on the inode correspond on a
+1:1 basis with the pages in the cache.
+
+       void fscache_uncache_all_inode_pages(struct fscache_cookie *cookie,
+                                            struct inode *inode);
+
+This takes the netfs cookie that the pages were cached with and the inode that
+the pages are attached to.  This function will wait for pages to finish being
+written to the cache and for the cache to finish with the page generally.  No
+error is returned.
+
+
 ==========================
 INDEX AND DATA FILE UPDATE
 ==========================
index 816696621ec9ea1be2d5b351ac4fc17d0e8f41aa..42e5363b41024c8c34d399398dfb77d1216bcd03 100644 (file)
@@ -92,6 +92,7 @@ static void cifs_fscache_disable_inode_cookie(struct inode *inode)
 
        if (cifsi->fscache) {
                cFYI(1, "%s: (0x%p)", __func__, cifsi->fscache);
+               fscache_uncache_all_inode_pages(cifsi->fscache, inode);
                fscache_relinquish_cookie(cifsi->fscache, 1);
                cifsi->fscache = NULL;
        }
index a2a5d19ece6adc92b3deda283bd6f5db1f2c701c..2f343b4d7a7d87b6792aad379b91ebffee9e15bc 100644 (file)
@@ -954,3 +954,47 @@ void fscache_mark_pages_cached(struct fscache_retrieval *op,
        pagevec_reinit(pagevec);
 }
 EXPORT_SYMBOL(fscache_mark_pages_cached);
+
+/*
+ * Uncache all the pages in an inode that are marked PG_fscache, assuming them
+ * to be associated with the given cookie.
+ */
+void __fscache_uncache_all_inode_pages(struct fscache_cookie *cookie,
+                                      struct inode *inode)
+{
+       struct address_space *mapping = inode->i_mapping;
+       struct pagevec pvec;
+       pgoff_t next;
+       int i;
+
+       _enter("%p,%p", cookie, inode);
+
+       if (!mapping || mapping->nrpages == 0) {
+               _leave(" [no pages]");
+               return;
+       }
+
+       pagevec_init(&pvec, 0);
+       next = 0;
+       while (next <= (loff_t)-1 &&
+              pagevec_lookup(&pvec, mapping, next, PAGEVEC_SIZE)
+              ) {
+               for (i = 0; i < pagevec_count(&pvec); i++) {
+                       struct page *page = pvec.pages[i];
+                       pgoff_t page_index = page->index;
+
+                       ASSERTCMP(page_index, >=, next);
+                       next = page_index + 1;
+
+                       if (PageFsCache(page)) {
+                               __fscache_wait_on_page_write(cookie, page);
+                               __fscache_uncache_page(cookie, page);
+                       }
+               }
+               pagevec_release(&pvec);
+               cond_resched();
+       }
+
+       _leave("");
+}
+EXPORT_SYMBOL(__fscache_uncache_all_inode_pages);
index ce153a6b3aecb42ee0567e6087f4e9597a55be76..419119c371bf81d3a5487448a289cf5e3e219314 100644 (file)
@@ -259,12 +259,10 @@ static void nfs_fscache_disable_inode_cookie(struct inode *inode)
                dfprintk(FSCACHE,
                         "NFS: nfsi 0x%p turning cache off\n", NFS_I(inode));
 
-               /* Need to invalidate any mapped pages that were read in before
-                * turning off the cache.
+               /* Need to uncache any pages attached to this inode that
+                * fscache knows about before turning off the cache.
                 */
-               if (inode->i_mapping && inode->i_mapping->nrpages)
-                       invalidate_inode_pages2(inode->i_mapping);
-
+               fscache_uncache_all_inode_pages(NFS_I(inode)->fscache, inode);
                nfs_fscache_zap_inode_cookie(inode);
        }
 }
index 7c4d72f5581f34ec81580edf108810e4319285e5..9ec20dec3353afa83478ef0509fb15309be5fca0 100644 (file)
@@ -204,6 +204,8 @@ extern bool __fscache_check_page_write(struct fscache_cookie *, struct page *);
 extern void __fscache_wait_on_page_write(struct fscache_cookie *, struct page *);
 extern bool __fscache_maybe_release_page(struct fscache_cookie *, struct page *,
                                         gfp_t);
+extern void __fscache_uncache_all_inode_pages(struct fscache_cookie *,
+                                             struct inode *);
 
 /**
  * fscache_register_netfs - Register a filesystem as desiring caching services
@@ -643,4 +645,23 @@ bool fscache_maybe_release_page(struct fscache_cookie *cookie,
        return false;
 }
 
+/**
+ * fscache_uncache_all_inode_pages - Uncache all an inode's pages
+ * @cookie: The cookie representing the inode's cache object.
+ * @inode: The inode to uncache pages from.
+ *
+ * Uncache all the pages in an inode that are marked PG_fscache, assuming them
+ * to be associated with the given cookie.
+ *
+ * This function may sleep.  It will wait for pages that are being written out
+ * and will wait whilst the PG_fscache mark is removed by the cache.
+ */
+static inline
+void fscache_uncache_all_inode_pages(struct fscache_cookie *cookie,
+                                    struct inode *inode)
+{
+       if (fscache_cookie_valid(cookie))
+               __fscache_uncache_all_inode_pages(cookie, inode);
+}
+
 #endif /* _LINUX_FSCACHE_H */