lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Message-Id: <cover.1750234270.git.hezhongkun.hzk@bytedance.com>
Date: Wed, 18 Jun 2025 19:39:56 +0800
From: Zhongkun He <hezhongkun.hzk@...edance.com>
To: akpm@...ux-foundation.org,
	tytso@....edu,
	jack@...e.com,
	hannes@...xchg.org,
	mhocko@...nel.org
Cc: muchun.song@...ux.dev,
	linux-ext4@...r.kernel.org,
	linux-kernel@...r.kernel.org,
	linux-mm@...ck.org,
	cgroups@...r.kernel.org,
	Zhongkun He <hezhongkun.hzk@...edance.com>
Subject: [PATCH 0/2] Postpone memcg reclaim to return-to-user path

# Introduction

This patchset aims to introduce an approach to ensure that memory
allocations are forced to be accounted to the memory cgroup, even if
they exceed the cgroup's maximum limit. In such cases, the reclaim
process is postponed until the task returns to the user. This is
beneficial for users who perform over-max reclaim while holding multiple
locks or other resources (especially resources related to file system
writeback). If a task needs any of these resources, it would otherwise
have to wait until the other task completes reclaim and releases the
resources. Postponing reclaim to the return-to-user path helps avoid this issue.

# Background

We have been encountering an hungtask issue for a long time. Specifically,
when a task holds the jbd2 handler and subsequently enters direct reclaim
because it reaches the hard limit within a memory cgroup, the system may become
blocked for a long time. The stack trace of waiting thread holding the jbd2
handle is as follows (and so many other threads are waiting on the same jbd2
handle):

 #0 __schedule at ffffffff97abc6c9
 #1 preempt_schedule_common at ffffffff97abcdaa
 #2 __cond_resched at ffffffff97abcddd
 #3 shrink_active_list at ffffffff9744dca2
 #4 shrink_lruvec at ffffffff97451407
 #5 shrink_node at ffffffff974517c9
 #6 do_try_to_free_pages at ffffffff97451dae
 #7 try_to_free_mem_cgroup_pages at ffffffff974542b8
 #8 try_charge_memcg at ffffffff974f0ede
 #9 charge_memcg at ffffffff974f1d0e
#10 __mem_cgroup_charge at ffffffff974f391c
#11 __add_to_page_cache_locked at ffffffff974313e5
#12 add_to_page_cache_lru at ffffffff974324b2
#13 pagecache_get_page at ffffffff974338e3
#14 __getblk_gfp at ffffffff97556798
#15 __ext4_get_inode_loc at ffffffffc07a5518 [ext4]
#16 ext4_get_inode_loc at ffffffffc07a7fec [ext4]
#17 ext4_reserve_inode_write at ffffffffc07a9fb1 [ext4]
#18 __ext4_mark_inode_dirty at ffffffffc07aa249 [ext4]
#19 __ext4_new_inode at ffffffffc079cbae [ext4]
#20 ext4_create at ffffffffc07c3e56 [ext4]
#21 path_openat at ffffffff9751f471
#22 do_filp_open at ffffffff97521384
#23 do_sys_openat2 at ffffffff97508fd6
#24 do_sys_open at ffffffff9750a65b
#25 do_syscall_64 at ffffffff97aaed14

We've obtained a coredump and dumped struct scan_control from it by using crash tool.

struct scan_control {
  nr_to_reclaim = 32,
  order = 0 '\000',
  priority = 1 '\001',
  reclaim_idx = 4 '\004',
  gfp_mask = 17861706, __GFP_NOFAIL
  nr_scanned = 27810, 
  nr_reclaimed = 0,
  nr = {
        dirty = 27797,
        unqueued_dirty = 27797,
        congested = 0,
        writeback = 0,
        immediate = 0,
        file_taken = 27810,
        taken = 27810
  },
}

The ->nr_reclaimed is zero meaning there is no memory we have reclaimed because
most of the file pages are unqueued dirty. And ->priority is 1 also meaning we
spent so much time on memory reclamation. Since this thread has held the jbd2
handler, the jbd2 thread was waiting for the same jbd2 handler, which blocked
so many other threads from writing dirty pages as well.

0 [] __schedule at ffffffff97abc6c9
1 [] schedule at ffffffff97abcd01
2 [] jbd2_journal_wait_updates at ffffffffc05a522f [jbd2]
3 [] jbd2_journal_commit_transaction at ffffffffc05a72c6 [jbd2]
4 [] kjournald2 at ffffffffc05ad66d [jbd2]
5 [] kthread at ffffffff972bc4c0
6 [] ret_from_fork at ffffffff9720440f

Furthermore, we observed that memory usage far exceeded the configured memory maximum,
reaching around 38GB.

memory.max  : 134896020    514 GB
memory.usage: 144747169    552 GB

We investigated this issue and identified the root cause:
  try_charge_memcg:
    retry charge
     charge failed
       -> direct reclaim
        -> mem_cgroup_oom    return true,but selected task is in an uninterruptible state
           -> retry charge

In which cases, we saw many tasks in the uninterruptible (D) state with a pending
SIGKILL signal. The OOM killer selects a victim and returns success, allowing the
current thread to retry the memory charge. However, the selected task cannot acknowledge
the SIGKILL signal because it is stuck in an uninterruptible state. As a result,
the charging task resets nr_retries and attempts to reclaim again, but the victim
task never exits. This causes the current thread to enter a prolonged retry loop
during direct reclaim, holding the jbd2 handler for much more time and leading to
system-wide blocking. Why are there so many uninterruptible (D) state tasks?
Check the most common stack trace.

crash> task_struct.__state ffff8c53a15b3080
  __state = 2,   #define TASK_UNINTERRUPTIBLE        0x0002
 0 [] __schedule at ffffffff97abc6c9
 1 [] schedule at ffffffff97abcd01
 2 [] schedule_preempt_disabled at ffffffff97abdf1a
 3 [] rwsem_down_read_slowpath at ffffffff97ac05bf
 4 [] down_read at ffffffff97ac06b1
 5 [] do_user_addr_fault at ffffffff9727f1e7
 6 [] exc_page_fault at ffffffff97ab286e
 7 [] asm_exc_page_fault at ffffffff97c00d42

Check the owner of mm_struct.mmap_lock. The task below was entering memory reclaim
holding mmap lock and there are 68 tasks in this memory cgroup, with 23 of them in
the memory reclaim context.

 7 [] shrink_active_list at ffffffff9744dd46
 8 [] shrink_lruvec at ffffffff97451407
 9 [] shrink_node at ffffffff974517c9
10 [] do_try_to_free_pages at ffffffff97451dae
11 [] try_to_free_mem_cgroup_pages at ffffffff974542b8
12 [] try_charge_memcg at ffffffff974f0ede
13 [] obj_cgroup_charge_pages at ffffffff974f1dae
14 [] obj_cgroup_charge at ffffffff974f2fc2
15 [] kmem_cache_alloc at ffffffff974d054c
16 [] vm_area_dup at ffffffff972923f1
17 [] __split_vma at ffffffff97486c16
18 [] __do_munmap at ffffffff97486e78
19 [] __vm_munmap at ffffffff97487307
20 [] __x64_sys_munmap at ffffffff974873e7
21 [] do_syscall_64 at ffffffff97aaed14

Many threads was entering the memory reclaim in UN state, other threads was blocking
on mmap_lock. Although the OOM killer selects a victim, it cannot terminate it. The
task holding the jbd2 handle retries memory charge, but it fails. Reclaiming continues
while holding the jbd2 handler. write_pages also fails while waiting for the same jbd2
handler, causing repeated shrink failures and potentially leading to a system-wide block.

ps | grep UN | wc -l
1463

While the system has 1463 UN state tasks, so the way to break this akin to "deadlock" is
to let the thread holding jbd2 handler quickly exit the memory reclamation process.

We found that a related issue was reported and partially fixed in previous patches [1][2].
However, those fixes only skip direct reclamation and return a failure for some cases such
as readahead requests. As sb_getblk() is called multiple times in __ext4_get_inode_loc()
with the NOFAIL flag, the problem still exists. And it is not feasible to simply remove
__GFP_RECLAIMABLE when holding jbd2 handle to avoid potential very long memory reclaim
latency,  as __GFP_NOFAIL is not supported without __GFP_DIRECT_RECLAIM.

# Fundamentals

This patchset introduces a new task flag of PF_MEMALLOC_ACFORCE to indicate that memory
allocations are forced to be accounted to the memory cgroup, even if they exceed the cgroup's
maximum limit. The reclaim process is deferred until the task returns to the user without
holding any kernel resources for memory reclamation, thereby preventing priority inversion
problems. Any users who might encounter potential similar issues can utilize this new flag
to allocate memory and prevent long-term latency for the entire system.

# References

[1] https://lore.kernel.org/linux-fsdevel/20230811071519.1094-1-teawaterz@linux.alibaba.com/
[2] https://lore.kernel.org/all/20230914150011.843330-1-willy@infradead.org/T/#u

Zhongkun He (2):
  mm: memcg: introduce PF_MEMALLOC_ACCOUNTFORCE to postpone reclaim to
    return-to-userland path
  jbd2: mark the transaction context with the scope PF_MEMALLOC_ACFORCE
    context

 fs/jbd2/transaction.c            | 15 +++++--
 include/linux/memcontrol.h       |  6 +++
 include/linux/resume_user_mode.h |  1 +
 include/linux/sched.h            | 11 ++++-
 include/linux/sched/mm.h         | 35 ++++++++++++++++
 mm/memcontrol.c                  | 71 ++++++++++++++++++++++++++++++++
 6 files changed, 133 insertions(+), 6 deletions(-)

-- 
2.39.5


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ