memcg: skip cgroup_file_notify if spinning is not allowed
Subsystems (4)
Shakeel Butt
maintainerPATCH 1/1 memcg: skip cgroup_file_notify if spinning is not allowed
Generally memcg charging is allowed from all the contexts including NMI where even spinning on spinlock can cause locking issues. However one call chain was missed during the addition of memcg charging from any context support. That is try_charge_memcg() -> memcg_memory_event() -> cgroup_file_notify().
The possible function call tree under cgroup_file_notify() can acquire many different spin locks in spinning mode. Some of them are cgroup_file_kn_lock, kernfs_notify_lock, pool_workqeue's lock. So, let's just skip cgroup_file_notify() from memcg charging if the context does not allow spinning.
--- include/linux/memcontrol.h | 23 ++++++++++++++++------- mm/memcontrol.c | 7 ++++--- 2 files changed, 20 insertions(+), 10 deletions(-)
include/linux/memcontrol.h +16 -7
@@ -993,22 +993,25 @@ static inline void count_memcg_event_mm(struct mm_struct *mm,count_memcg_events_mm (mm, idx,1 ); } -static inline void memcg_memory_event (struct mem_cgroup *memcg, -enum memcg_memory_event event) +static inline void __memcg_memory_event (struct mem_cgroup *memcg, +enum memcg_memory_event event, +bool allow_spinning) {bool swap_event = event ==MEMCG_SWAP_HIGH || event ==MEMCG_SWAP_MAX || event ==MEMCG_SWAP_FAIL ;atomic_long_inc (&memcg->memory_events_local[event]); -if (!swap_event) +if (!swap_event && allow_spinning)cgroup_file_notify (&memcg->events_local_file); do { atomic_long_inc(&memcg->memory_events[event]); - if (swap_event) - cgroup_file_notify(&memcg->swap_events_file); - else - cgroup_file_notify(&memcg->events_file); + if (allow_spinning) { + if (swap_event) + cgroup_file_notify(&memcg->swap_events_file); + else + cgroup_file_notify(&memcg->events_file); + } if (!cgroup_subsys_on_dfl(memory_cgrp_subsys)) break;
@@ -1018,6 +1021,12 @@ static inline void memcg_memory_event(struct mem_cgroup *memcg, !mem_cgroup_is_root(memcg)); } +static inline void memcg_memory_event (struct mem_cgroup *memcg, +enum memcg_memory_event event) +{ +__memcg_memory_event (memcg, event, true); +} +static inline void memcg_memory_event_mm (struct mm_struct *mm,enum memcg_memory_event event) {
mm/memcontrol.c +4 -3
@@ -2306,12 +2306,13 @@ static int try_charge_memcg(struct mem_cgroup *memcg, gfp_t gfp_mask,bool drained = false;bool raised_max_event = false;unsigned long pflags; +bool allow_spinning =gfpflags_allow_spinning (gfp_mask); retry:if (consume_stock (memcg, nr_pages))return 0 ; -if (!gfpflags_allow_spinning (gfp_mask)) +if (!allow_spinning)/* Avoid the refill and flush of the older stock */ batch = nr_pages;
@@ -2347,7 +2348,7 @@ static int try_charge_memcg(struct mem_cgroup *memcg, gfp_t gfp_mask,if (!gfpflags_allow_blocking (gfp_mask)) goto nomem; -memcg_memory_event (mem_over_limit,MEMCG_MAX ); +__memcg_memory_event (mem_over_limit,MEMCG_MAX , allow_spinning); raised_max_event = true;psi_memstall_enter (&pflags);
@@ -2414,7 +2415,7 @@ static int try_charge_memcg(struct mem_cgroup *memcg, gfp_t gfp_mask,* a MEMCG_MAX event. */ if (!raised_max_event) -memcg_memory_event (mem_over_limit,MEMCG_MAX ); +__memcg_memory_event (mem_over_limit,MEMCG_MAX , allow_spinning);/* * The allocation either can't fail or will lead to more memory
Signature · 1 line
2.47.3
Quoted 6 lines · Shakeel Butt
Generally memcg charging is allowed from all the contexts including NMI where even spinning on spinlock can cause locking issues. However one call chain was missed during the addition of memcg charging from any context support. That is
try_charge_memcg()->memcg_memory_event()->cgroup_file_notify().
Quoted 7 lines
The possible function call tree under
cgroup_file_notify()can acquire many different spin locks in spinning mode. Some of them arecgroup_file_kn_lock,kernfs_notify_lock, pool_workqeue's lock. So, let's just skipcgroup_file_notify()from memcg charging if the context does not allow spinning.Signed-off-by Shakeel Butt <[email protected]>
The repro described in [1] no longer triggers locking issues after applying this patch and making __bpf_async_init() use __GFP_HIGH instead of GFP_ATOMIC:
kernel/bpf/helpers.c +1 -1
@@ -1275,7 +1275,7 @@ static int __bpf_async_init(struct bpf_async_kern *async, struct bpf_map *map, u }/* allocate hrtimer via map_kmalloc to use memcg accounting */ - cb =bpf_map_kmalloc_node (map, size,GFP_ATOMIC , map->numa_node); + cb =bpf_map_kmalloc_node (map, size, __GFP_HIGH, map->numa_node);if (!cb) { ret = -ENOMEM ; goto out;
[1] https://lore.kernel.org/bpf/[email protected]/#t
Thanks,
Peilin Ye
Shakeel Butt
maintainerQuoted 13 lines · Peilin Ye
Quoted 12 lines · Shakeel Butt
Generally memcg charging is allowed from all the contexts including NMI where even spinning on spinlock can cause locking issues. However one call chain was missed during the addition of memcg charging from any context support. That is
try_charge_memcg()->memcg_memory_event()->cgroup_file_notify().The possible function call tree under
cgroup_file_notify()can acquire many different spin locks in spinning mode. Some of them arecgroup_file_kn_lock,kernfs_notify_lock, pool_workqeue's lock. So, let's just skipcgroup_file_notify()from memcg charging if the context does not allow spinning.
Quoted 3 lines
Quoted 1 line
Signed-off-by Shakeel Butt <[email protected]>Tested-by Peilin Ye <[email protected]>
Thanks Peilin. When you post the official patch for __GFP_HIGH in __bpf_async_init(), please add a comment on why __GFP_HIGH is used instead of GFP_ATOMIC.
Quoted 22 lines
The repro described in [1] no longer triggers locking issues after applying this patch and making
__bpf_async_init()use__GFP_HIGHinstead ofGFP_ATOMIC:kernel/bpf/helpers.c +1
-1@@ -1275,7 +1275,7 @@ static int __bpf_async_init(struct bpf_async_kern *async, struct bpf_map *map, u }/* allocate hrtimer via map_kmalloc to use memcg accounting */ - cb =bpf_map_kmalloc_node (map, size,GFP_ATOMIC , map->numa_node); + cb =bpf_map_kmalloc_node (map, size, __GFP_HIGH, map->numa_node);if (!cb) { ret = -ENOMEM ; goto out;[1] https://lore.kernel.org/bpf/[email protected]/#t
Thanks,
Peilin Ye
Quoted 14 lines · Shakeel Butt
Quoted 13 lines · Peilin Ye
Quoted 12 lines · Shakeel Butt
Generally memcg charging is allowed from all the contexts including NMI where even spinning on spinlock can cause locking issues. However one call chain was missed during the addition of memcg charging from any context support. That is
try_charge_memcg()->memcg_memory_event()->cgroup_file_notify().The possible function call tree under
cgroup_file_notify()can acquire many different spin locks in spinning mode. Some of them arecgroup_file_kn_lock,kernfs_notify_lock, pool_workqeue's lock. So, let's just skipcgroup_file_notify()from memcg charging if the context does not allow spinning.
Quoted 7 lines
Quoted 3 lines
Quoted 1 line
Signed-off-by Shakeel Butt <[email protected]>Tested-by Peilin Ye <[email protected]>Thanks Peilin. When you post the official patch for
__GFP_HIGHin__bpf_async_init(), please add a comment on why__GFP_HIGHis used instead ofGFP_ATOMIC.
Got it! I'll schedule to have that done today.
Thanks,
Peilin Ye
Quoted 24 lines · Peilin Ye
Quoted 6 lines · Shakeel Butt
Generally memcg charging is allowed from all the contexts including NMI where even spinning on spinlock can cause locking issues. However one call chain was missed during the addition of memcg charging from any context support. That is
try_charge_memcg()->memcg_memory_event()->cgroup_file_notify().Quoted 7 lines
The possible function call tree under
cgroup_file_notify()can acquire many different spin locks in spinning mode. Some of them arecgroup_file_kn_lock,kernfs_notify_lock, pool_workqeue's lock. So, let's just skipcgroup_file_notify()from memcg charging if the context does not allow spinning.Signed-off-by Shakeel Butt <[email protected]>Tested-by Peilin Ye <[email protected]>The repro described in [1] no longer triggers locking issues after applying this patch and making
__bpf_async_init()use__GFP_HIGHinstead ofGFP_ATOMIC:
kernel/bpf/helpers.c static int __bpf_async_init(struct bpf_async_kern *async, struct bpf_map *map, u · line 1278
Quoted 5 lines
kernel/bpf/helpers.c +1
-1@@ -1275,7 +1275,7 @@ static int __bpf_async_init(struct bpf_async_kern *async, struct bpf_map *map, u }/* allocate hrtimer via map_kmalloc to use memcg accounting */ - cb =bpf_map_kmalloc_node (map, size,GFP_ATOMIC , map->numa_node); + cb =bpf_map_kmalloc_node (map, size, __GFP_HIGH, map->numa_node);
Why do you need to consume memory reserves? Shouldn't kmalloc_nolock be used instead here?
Quoted 8 lines
kernel/bpf/helpers.c +0
-0if (!cb) { ret = -ENOMEM ; goto out;[1] https://lore.kernel.org/bpf/[email protected]/#t
Thanks,
Peilin Ye
Signature · 2 lines
Michal Hocko SUSE Labs
Quoted 28 lines · Michal Hocko
Quoted 26 lines · Peilin Ye
Quoted 6 lines · Shakeel Butt
Generally memcg charging is allowed from all the contexts including NMI where even spinning on spinlock can cause locking issues. However one call chain was missed during the addition of memcg charging from any context support. That is
try_charge_memcg()->memcg_memory_event()->cgroup_file_notify().Quoted 7 lines
The possible function call tree under
cgroup_file_notify()can acquire many different spin locks in spinning mode. Some of them arecgroup_file_kn_lock,kernfs_notify_lock, pool_workqeue's lock. So, let's just skipcgroup_file_notify()from memcg charging if the context does not allow spinning.Signed-off-by Shakeel Butt <[email protected]>Tested-by Peilin Ye <[email protected]>The repro described in [1] no longer triggers locking issues after applying this patch and making
__bpf_async_init()use__GFP_HIGHinstead ofGFP_ATOMIC:kernel/bpf/helpers.c +0
-0@@ -1275,7 +1275,7 @@ static int __bpf_async_init(struct bpf_async_kern *async, struct bpf_map *map, u }
Quoted 6 lines
kernel/bpf/helpers.c static int __bpf_async_init(struct bpf_async_kern *async, struct bpf_map *map, u · line 1278
Quoted 3 lines
kernel/bpf/helpers.c +1
-1/* allocate hrtimer via map_kmalloc to use memcg accounting */ - cb =bpf_map_kmalloc_node (map, size,GFP_ATOMIC , map->numa_node); + cb =bpf_map_kmalloc_node (map, size, __GFP_HIGH, map->numa_node);Why do you need to consume memory reserves? Shouldn't
kmalloc_nolockbe used instead here?
Yes. That's a plan. We'll convert most of bpf allocations to kmalloc_nolock() when it lands.
Quoted 36 lines · Alexei Starovoitov
Quoted 28 lines · Michal Hocko
Quoted 26 lines · Peilin Ye
Quoted 6 lines · Shakeel Butt
Generally memcg charging is allowed from all the contexts including NMI where even spinning on spinlock can cause locking issues. However one call chain was missed during the addition of memcg charging from any context support. That is
try_charge_memcg()->memcg_memory_event()->cgroup_file_notify().Quoted 7 lines
The possible function call tree under
cgroup_file_notify()can acquire many different spin locks in spinning mode. Some of them arecgroup_file_kn_lock,kernfs_notify_lock, pool_workqeue's lock. So, let's just skipcgroup_file_notify()from memcg charging if the context does not allow spinning.Signed-off-by Shakeel Butt <[email protected]>Tested-by Peilin Ye <[email protected]>The repro described in [1] no longer triggers locking issues after applying this patch and making
__bpf_async_init()use__GFP_HIGHinstead ofGFP_ATOMIC:kernel/bpf/helpers.c +0
-0@@ -1275,7 +1275,7 @@ static int __bpf_async_init(struct bpf_async_kern *async, struct bpf_map *map, u }Quoted 6 lines
kernel/bpf/helpers.c static int __bpf_async_init(struct bpf_async_kern *async, struct bpf_map *map, u · line 1278
Quoted 3 lines
kernel/bpf/helpers.c +1
-1/* allocate hrtimer via map_kmalloc to use memcg accounting */ - cb =bpf_map_kmalloc_node (map, size,GFP_ATOMIC , map->numa_node); + cb =bpf_map_kmalloc_node (map, size, __GFP_HIGH, map->numa_node);Why do you need to consume memory reserves? Shouldn't
kmalloc_nolockbe used instead here?
Quoted 2 lines
Yes. That's a plan. We'll convert most of bpf allocations to
kmalloc_nolock()when it lands.
OK, I thought this was merged already. I suspect __GFP_HIGH is used here as a result of manual GFP_ATOMIC & ~GFP_RECLAIM. A TODO/FIXME referring to kmalloc_nolock would clarify the situation.
Signature · 2 lines
Michal Hocko SUSE Labs
Roman Gushchin
maintainerQuoted 11 lines · Shakeel Butt
Generally memcg charging is allowed from all the contexts including NMI where even spinning on spinlock can cause locking issues. However one call chain was missed during the addition of memcg charging from any context support. That is
try_charge_memcg()->memcg_memory_event()->cgroup_file_notify().The possible function call tree under
cgroup_file_notify()can acquire many different spin locks in spinning mode. Some of them arecgroup_file_kn_lock,kernfs_notify_lock, pool_workqeue's lock. So, let's just skipcgroup_file_notify()from memcg charging if the context does not allow spinning.
Hmm, what about OOM events? Losing something like MEMCG_LOW doesn't look like a bit deal, but OOM events can be way more important.
Should we instead preserve the event (e.g. as a pending_event_mask) and raise it on the next occasion / from a different context?
Thanks
Tejun Heo
maintainerQuoted 14 lines · Roman Gushchin
Quoted 11 lines · Shakeel Butt
Generally memcg charging is allowed from all the contexts including NMI where even spinning on spinlock can cause locking issues. However one call chain was missed during the addition of memcg charging from any context support. That is
try_charge_memcg()->memcg_memory_event()->cgroup_file_notify().The possible function call tree under
cgroup_file_notify()can acquire many different spin locks in spinning mode. Some of them arecgroup_file_kn_lock,kernfs_notify_lock, pool_workqeue's lock. So, let's just skipcgroup_file_notify()from memcg charging if the context does not allow spinning.
Quoted 5 lines
Hmm, what about OOM events? Losing something like
MEMCG_LOWdoesn't look like a bit deal, but OOM events can be way more important.Should we instead preserve the event (e.g. as a
pending_event_mask) and raise it on the next occasion / from a different context?
Maybe punt with an irq_work?
Thanks.
Signature · 1 line
tejun
Shakeel Butt
maintainerQuoted 15 lines · Tejun Heo
Quoted 14 lines · Roman Gushchin
Quoted 11 lines · Shakeel Butt
Generally memcg charging is allowed from all the contexts including NMI where even spinning on spinlock can cause locking issues. However one call chain was missed during the addition of memcg charging from any context support. That is
try_charge_memcg()->memcg_memory_event()->cgroup_file_notify().The possible function call tree under
cgroup_file_notify()can acquire many different spin locks in spinning mode. Some of them arecgroup_file_kn_lock,kernfs_notify_lock, pool_workqeue's lock. So, let's just skipcgroup_file_notify()from memcg charging if the context does not allow spinning.
Quoted 7 lines
Quoted 5 lines
Hmm, what about OOM events? Losing something like
MEMCG_LOWdoesn't look like a bit deal, but OOM events can be way more important.Should we instead preserve the event (e.g. as a
pending_event_mask) and raise it on the next occasion / from a different context?Maybe punt with an
irq_work?
Oh good suggestion, I will check irq_work as well. Though I think that can be a followup work.
Shakeel Butt
maintainerQuoted 14 lines · Roman Gushchin
Quoted 11 lines · Shakeel Butt
Generally memcg charging is allowed from all the contexts including NMI where even spinning on spinlock can cause locking issues. However one call chain was missed during the addition of memcg charging from any context support. That is
try_charge_memcg()->memcg_memory_event()->cgroup_file_notify().The possible function call tree under
cgroup_file_notify()can acquire many different spin locks in spinning mode. Some of them arecgroup_file_kn_lock,kernfs_notify_lock, pool_workqeue's lock. So, let's just skipcgroup_file_notify()from memcg charging if the context does not allow spinning.
Quoted 6 lines
Hmm, what about OOM events? Losing something like
MEMCG_LOWdoesn't look like a bit deal, but OOM events can be way more important.Should we instead preserve the event (e.g. as a
pending_event_mask) and raise it on the next occasion / from a different context?
Thanks for the review. For now only MAX can happen in non-spinning context. All others only happen in process context. Maybe with BPF OOM, OOM might be possible in a different context (is that what you are thinking?). I think we can add the complexity of preserving the event when the actual need arise.
Roman Gushchin
maintainerQuoted 22 lines · Shakeel Butt
Quoted 14 lines · Roman Gushchin
Quoted 11 lines · Shakeel Butt
Generally memcg charging is allowed from all the contexts including NMI where even spinning on spinlock can cause locking issues. However one call chain was missed during the addition of memcg charging from any context support. That is
try_charge_memcg()->memcg_memory_event()->cgroup_file_notify().The possible function call tree under
cgroup_file_notify()can acquire many different spin locks in spinning mode. Some of them arecgroup_file_kn_lock,kernfs_notify_lock, pool_workqeue's lock. So, let's just skipcgroup_file_notify()from memcg charging if the context does not allow spinning.Quoted 6 lines
Hmm, what about OOM events? Losing something like
MEMCG_LOWdoesn't look like a bit deal, but OOM events can be way more important.Should we instead preserve the event (e.g. as a
pending_event_mask) and raise it on the next occasion / from a different context?
Quoted 5 lines
Thanks for the review. For now only MAX can happen in non-spinning context. All others only happen in process context. Maybe with BPF OOM, OOM might be possible in a different context (is that what you are thinking?). I think we can add the complexity of preserving the event when the actual need arise.
No, I haven't thought about any particular use case, just a bit worried about silently dropping some events. It might be not an issue now, but might be easy to miss a moment when it becomes a problem.
So in my opinion using some delayed delivery mechanism is better than just dropping these events.
Shakeel Butt
maintainerQuoted 30 lines · Roman Gushchin
Quoted 22 lines · Shakeel Butt
Quoted 14 lines · Roman Gushchin
Quoted 11 lines · Shakeel Butt
Generally memcg charging is allowed from all the contexts including NMI where even spinning on spinlock can cause locking issues. However one call chain was missed during the addition of memcg charging from any context support. That is
try_charge_memcg()->memcg_memory_event()->cgroup_file_notify().The possible function call tree under
cgroup_file_notify()can acquire many different spin locks in spinning mode. Some of them arecgroup_file_kn_lock,kernfs_notify_lock, pool_workqeue's lock. So, let's just skipcgroup_file_notify()from memcg charging if the context does not allow spinning.Quoted 6 lines
Hmm, what about OOM events? Losing something like
MEMCG_LOWdoesn't look like a bit deal, but OOM events can be way more important.Should we instead preserve the event (e.g. as a
pending_event_mask) and raise it on the next occasion / from a different context?Quoted 5 lines
Thanks for the review. For now only MAX can happen in non-spinning context. All others only happen in process context. Maybe with BPF OOM, OOM might be possible in a different context (is that what you are thinking?). I think we can add the complexity of preserving the event when the actual need arise.
Quoted 4 lines
No, I haven't thought about any particular use case, just a bit worried about silently dropping some events. It might be not an issue now, but might be easy to miss a moment when it becomes a problem.
Only the notification can be dropped and not the event (i.e. we are still incrementing the counters). Also for MAX only but I got your point.
Quoted 2 lines
So in my opinion using some delayed delivery mechanism is better than just dropping these events.
Let me see how doing this irq_work looks like and will update here.
Roman Gushchin
maintainerQuoted 36 lines · Shakeel Butt
Quoted 30 lines · Roman Gushchin
Quoted 22 lines · Shakeel Butt
Quoted 14 lines · Roman Gushchin
Quoted 11 lines · Shakeel Butt
Generally memcg charging is allowed from all the contexts including NMI where even spinning on spinlock can cause locking issues. However one call chain was missed during the addition of memcg charging from any context support. That is
try_charge_memcg()->memcg_memory_event()->cgroup_file_notify().The possible function call tree under
cgroup_file_notify()can acquire many different spin locks in spinning mode. Some of them arecgroup_file_kn_lock,kernfs_notify_lock, pool_workqeue's lock. So, let's just skipcgroup_file_notify()from memcg charging if the context does not allow spinning.Quoted 6 lines
Hmm, what about OOM events? Losing something like
MEMCG_LOWdoesn't look like a bit deal, but OOM events can be way more important.Should we instead preserve the event (e.g. as a
pending_event_mask) and raise it on the next occasion / from a different context?Quoted 5 lines
Thanks for the review. For now only MAX can happen in non-spinning context. All others only happen in process context. Maybe with BPF OOM, OOM might be possible in a different context (is that what you are thinking?). I think we can add the complexity of preserving the event when the actual need arise.
Quoted 4 lines
No, I haven't thought about any particular use case, just a bit worried about silently dropping some events. It might be not an issue now, but might be easy to miss a moment when it becomes a problem.
Quoted 8 lines
Only the notification can be dropped and not the event (i.e. we are still incrementing the counters). Also for MAX only but I got your point.
Quoted 2 lines
So in my opinion using some delayed delivery mechanism is better than just dropping these events.
Let me see how doing this
irq_worklooks like and will update here.
Thanks!
If it won't work out for some reason, maybe at least explicitly narrow it down to the MEMCG_MAX events.
Quoted 6 lines · Shakeel Butt
Generally memcg charging is allowed from all the contexts including NMI where even spinning on spinlock can cause locking issues. However one call chain was missed during the addition of memcg charging from any context support. That is
try_charge_memcg()->memcg_memory_event()->cgroup_file_notify().
Quoted 7 lines
The possible function call tree under
cgroup_file_notify()can acquire many different spin locks in spinning mode. Some of them arecgroup_file_kn_lock,kernfs_notify_lock, pool_workqeue's lock. So, let's just skipcgroup_file_notify()from memcg charging if the context does not allow spinning.Signed-off-by Shakeel Butt <[email protected]>
Quoted 13 lines
--- include/linux/memcontrol.h | 23 ++++++++++++++++------- mm/memcontrol.c | 7 ++++--- 2 files changed, 20 insertions(+), 10 deletions(-)include/linux/memcontrol.h +0
-0@@ -993,22 +993,25 @@ static inline void count_memcg_event_mm(struct mm_struct *mm,count_memcg_events_mm (mm, idx,1 ); }
include/linux/memcontrol.h static inline void count_memcg_event_mm(struct mm_struct *mm, · line 1003
Quoted 10 lines
include/linux/memcontrol.h +3
-2-static inline void memcg_memory_event (struct mem_cgroup *memcg, -enum memcg_memory_event event) +static inline void __memcg_memory_event (struct mem_cgroup *memcg, +enum memcg_memory_event event, +bool allow_spinning) {bool swap_event = event ==MEMCG_SWAP_HIGH || event ==MEMCG_SWAP_MAX || event ==MEMCG_SWAP_FAIL ;atomic_long_inc (&memcg->memory_events_local[event]);
Doesn't this involve locking on 32b? I guess we do not care all that much but we might want to bail out early on those arches for !allow_spinning
Quoted 72 lines
include/linux/memcontrol.h +13
-5-if (!swap_event) +if (!swap_event && allow_spinning)cgroup_file_notify (&memcg->events_local_file); do { atomic_long_inc(&memcg->memory_events[event]); - if (swap_event) - cgroup_file_notify(&memcg->swap_events_file); - else - cgroup_file_notify(&memcg->events_file); + if (allow_spinning) { + if (swap_event) + cgroup_file_notify(&memcg->swap_events_file); + else + cgroup_file_notify(&memcg->events_file); + } if (!cgroup_subsys_on_dfl(memory_cgrp_subsys)) break;@@ -1018,6 +1021,12 @@ static inline void memcg_memory_event(struct mem_cgroup *memcg, !mem_cgroup_is_root(memcg)); } +static inline void memcg_memory_event (struct mem_cgroup *memcg, +enum memcg_memory_event event) +{ +__memcg_memory_event (memcg, event, true); +} +static inline void memcg_memory_event_mm (struct mm_struct *mm,enum memcg_memory_event event) {mm/memcontrol.c +4
-3@@ -2306,12 +2306,13 @@ static int try_charge_memcg(struct mem_cgroup *memcg, gfp_t gfp_mask,bool drained = false;bool raised_max_event = false;unsigned long pflags; +bool allow_spinning =gfpflags_allow_spinning (gfp_mask); retry:if (consume_stock (memcg, nr_pages))return 0 ; -if (!gfpflags_allow_spinning (gfp_mask)) +if (!allow_spinning)/* Avoid the refill and flush of the older stock */ batch = nr_pages;@@ -2347,7 +2348,7 @@ static int try_charge_memcg(struct mem_cgroup *memcg, gfp_t gfp_mask,if (!gfpflags_allow_blocking (gfp_mask)) goto nomem; -memcg_memory_event (mem_over_limit,MEMCG_MAX ); +__memcg_memory_event (mem_over_limit,MEMCG_MAX , allow_spinning); raised_max_event = true;psi_memstall_enter (&pflags);@@ -2414,7 +2415,7 @@ static int try_charge_memcg(struct mem_cgroup *memcg, gfp_t gfp_mask,* a MEMCG_MAX event. */ if (!raised_max_event) -memcg_memory_event (mem_over_limit,MEMCG_MAX ); +__memcg_memory_event (mem_over_limit,MEMCG_MAX , allow_spinning);/* * The allocation either can't fail or will lead to more memory Signature · 1 line
2.47.3
Signature · 2 lines
Michal Hocko SUSE Labs
Shakeel Butt
maintainerQuoted 13 lines · Michal Hocko
Quoted 12 lines · Shakeel Butt
Generally memcg charging is allowed from all the contexts including NMI where even spinning on spinlock can cause locking issues. However one call chain was missed during the addition of memcg charging from any context support. That is
try_charge_memcg()->memcg_memory_event()->cgroup_file_notify().The possible function call tree under
cgroup_file_notify()can acquire many different spin locks in spinning mode. Some of them arecgroup_file_kn_lock,kernfs_notify_lock, pool_workqeue's lock. So, let's just skipcgroup_file_notify()from memcg charging if the context does not allow spinning.
Quoted 3 lines
Quoted 1 line
Signed-off-by Shakeel Butt <[email protected]>Acked-by Michal Hocko <[email protected]>
Thanks.
Quoted 23 lines
Quoted 23 lines
--- include/linux/memcontrol.h | 23 ++++++++++++++++------- mm/memcontrol.c | 7 ++++--- 2 files changed, 20 insertions(+), 10 deletions(-)include/linux/memcontrol.h +3
-2@@ -993,22 +993,25 @@ static inline void count_memcg_event_mm(struct mm_struct *mm,count_memcg_events_mm (mm, idx,1 ); } -static inline void memcg_memory_event (struct mem_cgroup *memcg, -enum memcg_memory_event event) +static inline void __memcg_memory_event (struct mem_cgroup *memcg, +enum memcg_memory_event event, +bool allow_spinning) {bool swap_event = event ==MEMCG_SWAP_HIGH || event ==MEMCG_SWAP_MAX || event ==MEMCG_SWAP_FAIL ;
Quoted 6 lines
include/linux/memcontrol.h static inline void count_memcg_event_mm(struct mm_struct *mm, · line 1003
Quoted 1 line
include/linux/memcontrol.h +0
-0atomic_long_inc (&memcg->memory_events_local[event]);Doesn't this involve locking on 32b? I guess we do not care all that much but we might want to bail out early on those arches for !allow_spinning
I am prototyping irq_work based approach and if that looks good then we might not need to worry about 32b at all.
Shakeel Butt
maintainerQuoted 6 lines · Shakeel Butt
Generally memcg charging is allowed from all the contexts including NMI where even spinning on spinlock can cause locking issues. However one call chain was missed during the addition of memcg charging from any context support. That is
try_charge_memcg()->memcg_memory_event()->cgroup_file_notify().
Quoted 7 lines
The possible function call tree under
cgroup_file_notify()can acquire many different spin locks in spinning mode. Some of them arecgroup_file_kn_lock,kernfs_notify_lock, pool_workqeue's lock. So, let's just skipcgroup_file_notify()from memcg charging if the context does not allow spinning.Signed-off-by Shakeel Butt <[email protected]>
Here I am just pasting the irq_work based prototype which is build tested only for now and sharing it early to show how it looks. Overall I think it is adding too much complexity which is not worth it. We have to add per-cpu irq_work and then for each memcg we have to add per-cpu lockless node to queue the deferred event update. Also more reasoning is needed to make sure the updates are not missed by the deferred work.
Anyways, this is the early prototype. Unless there are comments on how to make it better, I will ask Andrew to just pick the previous patch I sent.
From d58d772f306454f0dffa94bfb32195496c450892 Mon Sep 17 00:00:00 2001
From: Shakeel Butt <[email protected]>
Date: Thu, 18 Sep 2025 19:25:37 -0700
Subject: [PATCH] memcg: add support for deferred max memcg event
--- include/linux/memcontrol.h | 3 ++ mm/memcontrol.c | 85 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 84 insertions(+), 4 deletions(-)
include/linux/memcontrol.h +3 -0
@@ -69,6 +69,7 @@ struct mem_cgroup_id {refcount_t ref; }; +struct deferred_events_percpu ;struct memcg_vmstats_percpu ;struct memcg1_events_percpu ;struct memcg_vmstats ;
@@ -268,6 +269,8 @@ struct mem_cgroup {struct memcg_vmstats_percpu __percpu *vmstats_percpu; +struct deferred_events_percpu __percpu *deferred_events; +#ifdef CONFIG_CGROUP_WRITEBACK struct list_head cgwb_list;struct wb_domain cgwb_domain;
mm/memcontrol.c +81 -4
138 lines not on this page · raw message
Signature · 1 line
2.47.3
Quoted 95 lines · Shakeel Butt
Quoted 6 lines · Shakeel Butt
Generally memcg charging is allowed from all the contexts including NMI where even spinning on spinlock can cause locking issues. However one call chain was missed during the addition of memcg charging from any context support. That is
try_charge_memcg()->memcg_memory_event()->cgroup_file_notify().Quoted 7 lines
The possible function call tree under
cgroup_file_notify()can acquire many different spin locks in spinning mode. Some of them arecgroup_file_kn_lock,kernfs_notify_lock, pool_workqeue's lock. So, let's just skipcgroup_file_notify()from memcg charging if the context does not allow spinning.Signed-off-by Shakeel Butt <[email protected]>Here I am just pasting the
irq_workbased prototype which is build tested only for now and sharing it early to show how it looks. Overall I think it is adding too much complexity which is not worth it. We have to add per-cpuirq_workand then for each memcg we have to add per-cpu lockless node to queue the deferred event update. Also more reasoning is needed to make sure the updates are not missed by the deferred work.Anyways, this is the early prototype. Unless there are comments on how to make it better, I will ask Andrew to just pick the previous patch I sent.
From
d58d772f306454f0dffa94bfb32195496c450892Mon Sep 17 00:00:00 2001
From: Shakeel Butt <[email protected]>
Date: Thu, 18 Sep 2025 19:25:37 -0700
Subject: [PATCH] memcg: add support for deferred max memcg eventSigned-off-by Shakeel Butt <[email protected]>--- include/linux/memcontrol.h | 3 ++ mm/memcontrol.c | 85 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 84 insertions(+), 4 deletions(-)include/linux/memcontrol.h +3
-0@@ -69,6 +69,7 @@ struct mem_cgroup_id {refcount_t ref; }; +struct deferred_events_percpu ;struct memcg_vmstats_percpu ;struct memcg1_events_percpu ;struct memcg_vmstats ;@@ -268,6 +269,8 @@ struct mem_cgroup {struct memcg_vmstats_percpu __percpu *vmstats_percpu; +struct deferred_events_percpu __percpu *deferred_events; +#ifdef CONFIG_CGROUP_WRITEBACK struct list_head cgwb_list;struct wb_domain cgwb_domain;mm/memcontrol.c +27
-0@@ -132,6 +132,63 @@ bool mem_cgroup_kmem_disabled(void)return cgroup_memory_nokmem; } +struct deferred_events_percpu { +atomic_t max_events; +struct mem_cgroup *memcg_owner; +struct llist_node lnode; +}; + +struct defer_memcg_events { +struct llist_head memcg_llist; +struct irq_work work; +}; + +static void process_deferred_events (struct irq_work *work) +{ +struct defer_memcg_events *events =container_of (work, + struct defer_memcg_events, work); +struct llist_node *lnode; + +while (lnode =llist_del_first_init (&events->memcg_llist)) { +int i, num; +struct deferred_events_percpu *eventsc; + + eventsc = container_of(lnode, struct deferred_events_percpu, lnode); + +if (!atomic_read (&eventsc->max_events)) +continue ; + num =atomic_xchg (&eventsc->max_events,0 ); +if (!num)
mm/memcontrol.c bool mem_cgroup_kmem_disabled(void) · line 171
Quoted 10 lines
mm/memcontrol.c +10
-0+continue ; +for (i =0 ; i < num; i++) +memcg_memory_event (eventsc->memcg_owner,MEMCG_MAX ); + } +} + +static DEFINE_PER_CPU(struct defer_memcg_events, postpone_events) = { + .memcg_llist =LLIST_HEAD_INIT (memcg_llist), + .work =IRQ_WORK_INIT (process_deferred_events), +};
Why all these per cpu stuff ? I don't understand what it helps with. To have max_events per-cpu?
Just one global llist and irq_work will do just fine.
and global max_events too.
In some previous thread there was a question about atomiciting of atomic_long. It's normal 32-bit atomic on 32-bit archs.
Shakeel Butt
maintainerQuoted 2 lines · Alexei Starovoitov
Quoted 1 line · Shakeel Butt
[...]
Quoted 10 lines
mm/memcontrol.c bool mem_cgroup_kmem_disabled(void) · line 171
Quoted 5 lines
mm/memcontrol.c +5
-0+ +static DEFINE_PER_CPU(struct defer_memcg_events, postpone_events) = { + .memcg_llist =LLIST_HEAD_INIT (memcg_llist), + .work =IRQ_WORK_INIT (process_deferred_events), +};Why all these per cpu stuff ? I don't understand what it helps with. To have
max_eventsper-cpu?
Just one global llist andirq_workwill do just fine.
and globalmax_eventstoo.
Right, global llist and irq_work will work but max_events has to be per-memcg. In addition we will need llist_node per-memcg and I think we will need to do a similar cmpxchg trick I did in css_rstat_updated() to eliminate the potential race of llist_node of a given memcg i.e. multiple CPUs trying to add llist_node of a memcg to the global llist.
This can work but I am still debating if this additional complexity is worth it as compared to the original patch where we skip cgroup_file_notify() for !allow_spinning condition.
Quoted 16 lines · Shakeel Butt
Quoted 2 lines · Alexei Starovoitov
Quoted 1 line · Shakeel Butt
[...]
Quoted 10 lines
mm/memcontrol.c bool mem_cgroup_kmem_disabled(void) · line 171
Quoted 5 lines
mm/memcontrol.c +5
-0+ +static DEFINE_PER_CPU(struct defer_memcg_events, postpone_events) = { + .memcg_llist =LLIST_HEAD_INIT (memcg_llist), + .work =IRQ_WORK_INIT (process_deferred_events), +};Why all these per cpu stuff ? I don't understand what it helps with. To have
max_eventsper-cpu?
Just one global llist andirq_workwill do just fine.
and globalmax_eventstoo.
Quoted 5 lines
Right, global llist and
irq_workwill work butmax_eventshas to be per-memcg. In addition we will needllist_nodeper-memcg and I think we will need to do a similar cmpxchg trick I did incss_rstat_updated()to eliminate the potential race ofllist_nodeof a given memcg i.e. multiple CPUs trying to addllist_nodeof a memcg to the global llist.
ohh. that cmpxchg is quite unusual. Never seen anything like that. I'm not convinced it's correct either.
I would replace that with traditional:
if (!atomic_xchg(&c->enqueued, 1))
if (llist_add(&c->llnode, &global_llist))
irq_work_queue(&global_irq_work);
and use traditional dequeue:
nodes = llist_del_all(&global_llist);for_each(nodes) {
memcg_memory_event(c, MEMCG_MAX);
atomic_set(&c->enqueued, 0);
}
since llist_del_first_init() is slower.
max_events still looks unnecessary to me.
Quoted 3 lines
This can work but I am still debating if this additional complexity is worth it as compared to the original patch where we skip
cgroup_file_notify()for !allow_spinning condition.
I would skip it too. deferral doesn't seem necessary.