Skip to content

Commit e2f7c76

Browse files
nhatsmrtgregkh
authored andcommitted
cachestat: do not flush stats in recency check
commit 5a4d894 upstream. syzbot detects that cachestat() is flushing stats, which can sleep, in its RCU read section (see [1]). This is done in the workingset_test_recent() step (which checks if the folio's eviction is recent). Move the stat flushing step to before the RCU read section of cachestat, and skip stat flushing during the recency check. [1]: https://lore.kernel.org/cgroups/000000000000f71227061bdf97e0@google.com/ Link: https://lkml.kernel.org/r/20240627201737.3506959-1-nphamcs@gmail.com Fixes: b006847 ("mm: workingset: move the stats flush into workingset_test_recent()") Signed-off-by: Nhat Pham <nphamcs@gmail.com> Reported-by: syzbot+b7f13b2d0cc156edf61a@syzkaller.appspotmail.com Closes: https://lore.kernel.org/cgroups/000000000000f71227061bdf97e0@google.com/ Debugged-by: Johannes Weiner <hannes@cmpxchg.org> Suggested-by: Johannes Weiner <hannes@cmpxchg.org> Acked-by: Johannes Weiner <hannes@cmpxchg.org> Acked-by: Shakeel Butt <shakeel.butt@linux.dev> Cc: Al Viro <viro@zeniv.linux.org.uk> Cc: David Hildenbrand <david@redhat.com> Cc: "Huang, Ying" <ying.huang@intel.com> Cc: Kairui Song <kasong@tencent.com> Cc: Matthew Wilcox (Oracle) <willy@infradead.org> Cc: Ryan Roberts <ryan.roberts@arm.com> Cc: Yosry Ahmed <yosryahmed@google.com> Cc: <stable@vger.kernel.org> [6.8+] Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
1 parent 1f45e5c commit e2f7c76

3 files changed

Lines changed: 17 additions & 5 deletions

File tree

include/linux/swap.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,8 @@ static inline swp_entry_t page_swap_entry(struct page *page)
343343
}
344344

345345
/* linux/mm/workingset.c */
346-
bool workingset_test_recent(void *shadow, bool file, bool *workingset);
346+
bool workingset_test_recent(void *shadow, bool file, bool *workingset,
347+
bool flush);
347348
void workingset_age_nonresident(struct lruvec *lruvec, unsigned long nr_pages);
348349
void *workingset_eviction(struct folio *folio, struct mem_cgroup *target_memcg);
349350
void workingset_refault(struct folio *folio, void *shadow);

mm/filemap.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4210,6 +4210,9 @@ static void filemap_cachestat(struct address_space *mapping,
42104210
XA_STATE(xas, &mapping->i_pages, first_index);
42114211
struct folio *folio;
42124212

4213+
/* Flush stats (and potentially sleep) outside the RCU read section. */
4214+
mem_cgroup_flush_stats_ratelimited(NULL);
4215+
42134216
rcu_read_lock();
42144217
xas_for_each(&xas, folio, last_index) {
42154218
int order;
@@ -4273,7 +4276,7 @@ static void filemap_cachestat(struct address_space *mapping,
42734276
goto resched;
42744277
}
42754278
#endif
4276-
if (workingset_test_recent(shadow, true, &workingset))
4279+
if (workingset_test_recent(shadow, true, &workingset, false))
42774280
cs->nr_recently_evicted += nr_pages;
42784281

42794282
goto resched;

mm/workingset.c

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -411,10 +411,12 @@ void *workingset_eviction(struct folio *folio, struct mem_cgroup *target_memcg)
411411
* @file: whether the corresponding folio is from the file lru.
412412
* @workingset: where the workingset value unpacked from shadow should
413413
* be stored.
414+
* @flush: whether to flush cgroup rstat.
414415
*
415416
* Return: true if the shadow is for a recently evicted folio; false otherwise.
416417
*/
417-
bool workingset_test_recent(void *shadow, bool file, bool *workingset)
418+
bool workingset_test_recent(void *shadow, bool file, bool *workingset,
419+
bool flush)
418420
{
419421
struct mem_cgroup *eviction_memcg;
420422
struct lruvec *eviction_lruvec;
@@ -466,10 +468,16 @@ bool workingset_test_recent(void *shadow, bool file, bool *workingset)
466468

467469
/*
468470
* Flush stats (and potentially sleep) outside the RCU read section.
471+
*
472+
* Note that workingset_test_recent() itself might be called in RCU read
473+
* section (for e.g, in cachestat) - these callers need to skip flushing
474+
* stats (via the flush argument).
475+
*
469476
* XXX: With per-memcg flushing and thresholding, is ratelimiting
470477
* still needed here?
471478
*/
472-
mem_cgroup_flush_stats_ratelimited(eviction_memcg);
479+
if (flush)
480+
mem_cgroup_flush_stats_ratelimited(eviction_memcg);
473481

474482
eviction_lruvec = mem_cgroup_lruvec(eviction_memcg, pgdat);
475483
refault = atomic_long_read(&eviction_lruvec->nonresident_age);
@@ -557,7 +565,7 @@ void workingset_refault(struct folio *folio, void *shadow)
557565

558566
mod_lruvec_state(lruvec, WORKINGSET_REFAULT_BASE + file, nr);
559567

560-
if (!workingset_test_recent(shadow, file, &workingset))
568+
if (!workingset_test_recent(shadow, file, &workingset, true))
561569
return;
562570

563571
folio_set_active(folio);

0 commit comments

Comments
 (0)