[PATCH] mm: memcontrol: convert page cache to a new mem_cgroup_charge() API fix

From: Johannes Weiner
Date: Mon May 11 2020 - 10:45:14 EST


Incorporate Hugh's feedback:

- shmem_getpage_gfp() needs to handle the new -ENOENT that was
previously implied in the -EEXIST when a swap entry changed under us
in any way. Otherwise hole punching could cause a racing fault to
SIGBUS instead of allocating a new page.

- It is indeed page reclaim that picks up any swapcache we leave
stranded when free_swap_and_cache() runs on a page locked by
somebody else. Document that our delete_from_swap_cache() is an
optimization, not something we rely on for correctness.

- Style cleanup: testing `expected' to decide on -EEXIST vs -ENOENT
differentiates the callsites, but is a bit awkward to read. Test
`entry' instead.

Signed-off-by: Johannes Weiner <hannes@xxxxxxxxxxx>
---
mm/shmem.c | 15 +++++++++------
1 file changed, 9 insertions(+), 6 deletions(-)

diff --git a/mm/shmem.c b/mm/shmem.c
index afd5a057ebb7..00fb001e8f3e 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -638,7 +638,7 @@ static int shmem_add_to_page_cache(struct page *page,
xas_lock_irq(&xas);
entry = xas_find_conflict(&xas);
if (entry != expected)
- xas_set_err(&xas, expected ? -ENOENT : -EEXIST);
+ xas_set_err(&xas, entry ? -EEXIST : -ENOENT);
xas_create_range(&xas);
if (xas_error(&xas))
goto unlock;
@@ -1686,10 +1686,13 @@ static int shmem_swapin_page(struct inode *inode, pgoff_t index,
* We already confirmed swap under page lock, but
* free_swap_and_cache() only trylocks a page, so it
* is just possible that the entry has been truncated
- * or holepunched since swap was confirmed.
- * shmem_undo_range() will have done some of the
- * unaccounting, now delete_from_swap_cache() will do
- * the rest.
+ * or holepunched since swap was confirmed. This could
+ * occur at any time while the page is locked, and
+ * usually page reclaim will take care of the stranded
+ * swapcache page. But when we catch it, we may as
+ * well clean up after ourselves: shmem_undo_range()
+ * will have done some of the unaccounting, now
+ * delete_from_swap_cache() will do the rest.
*/
if (error == -ENOENT)
delete_from_swap_cache(page);
@@ -1765,7 +1768,7 @@ static int shmem_getpage_gfp(struct inode *inode, pgoff_t index,
if (xa_is_value(page)) {
error = shmem_swapin_page(inode, index, &page,
sgp, gfp, vma, fault_type);
- if (error == -EEXIST)
+ if (error == -EEXIST || error == -ENOENT)
goto repeat;

*pagep = page;
--
2.26.2