memcg: clean up memory.high enforcement
Subsystems (3)
Shakeel Butt
maintainercover memcg: clean up memory.high enforcement
This series cleans up memory.high enforcement and uses a worker for kernel threads and remote charges.
Kernel threads never return to userspace to run deferred reclaim. Reclaiming and sleeping during a charge can also delay work they do for other tasks.
Remote charges can go to a different cgroup from the caller's mm, for example through filesystem notifications or faults in another process's memory. The high handler uses current->mm, so it can check the caller's limits instead of the limits of the charged cgroup.
Use high_work to reclaim from the charged hierarchy in these cases. mm_match_cgroup() identifies charges outside the current mm's cgroup and its ancestors. Local user charges keep their current handling.
Skip queuing work from PF_MEMALLOC tasks. Reclaim can charge memory itself, for example when zswap stores a page, and high_work must not keep queuing itself from its own reclaim.
This is best effort. A fast allocator can keep usage well above memory.high. The worker paths also skip memory.swap.high throttling and its high-event reporting. The memory.max and memory.swap.max checks stay the same.
The first patch moves enforcement into memcg_enforce_high(). The next two add kernel thread and remote charge handling. The fourth adds the reclaim guard. The last simplifies the scan and comments. The first and last patches have no intended behavior change.
Based on next-20261009. Tested in an x86-64 VM with loop writes, threaded NAPI, remote faults, notifications, and both vhost worker modes. The remote tests kept same-cgroup and ancestor charges on the local path. An incompressible zswap test queued high_work from its own reclaim 163 times without the guard.
Shakeel Butt (5):
memcg: move memory.high enforcement out of try_charge_memcg()
memcg: use high_work for kernel threads
memcg: use high_work for remote charges
memcg: avoid queuing high_work from reclaim
memcg: simplify high limit handling after a charge
mm/memcontrol.c | 136 +++++++++++++++++++++++++++--------------------- 1 file changed, 77 insertions(+), 59 deletions(-)
base-commit: becb95e45ef0dda9ffb35156d909d3da7a2789c3
Signature · 1 line
2.53.0-Meta
Shakeel Butt
maintainerPATCH 1/5 memcg: move memory.high enforcement out of try_charge_memcg()
try_charge_memcg() does a lot. It charges the page counters, reclaims when a limit is hit, may call the OOM killer, and at the end it checks and enforces memory.high. Move the memory.high part into a new function, memcg_enforce_high(), so that try_charge_memcg() is shorter and the memory.high code is easier to read and change.
The code and its comments move as they are. The only difference is that the new function gets allow_spinning from gfp_mask itself.
No functional change.
--- mm/memcontrol.c | 125 ++++++++++++++++++++++++++---------------------- 1 file changed, 69 insertions(+), 56 deletions(-)
mm/memcontrol.c +69 -56
@@ -2696,6 +2696,74 @@ void __mem_cgroup_handle_over_high(gfp_t gfp_mask)css_put (&memcg->css); } +/* +* Called after @nr_pages were charged to @memcg. If @memcg or one of its +* ancestors is over memory.high or memory.swap.high, start reclaim or slow +* down the task. +*/ +static void memcg_enforce_high (struct mem_cgroup *memcg,unsigned int nr_pages, +gfp_t gfp_mask) +{ +bool allow_spinning =gfpflags_allow_spinning (gfp_mask); + +/* +* If the hierarchy is above the normal consumption range, schedule +* reclaim on returning to userland. We can perform reclaim here +* if __GFP_RECLAIM but let's always punt for simplicity and so that +* GFP_KERNEL can consistently be used during reclaim. @memcg is +* not recorded as it most likely matches current's and won't +* change in the meantime. As high limit is checked again before +* reclaim, the cost of mismatch is negligible. +*/ +do { +bool mem_high, swap_high; + + mem_high =page_counter_read (&memcg->memory) > +READ_ONCE (memcg->memory.high); + swap_high =page_counter_read (&memcg->swap) > +READ_ONCE (memcg->swap.high); + +/* Don't bother a random interrupted task */ +if (!in_task ()) { +if (mem_high) { +if (allow_spinning) +schedule_work (&memcg->high_work); +else +irq_work_queue (&memcg->high_irq_work); +break ; + } +continue ; + } + +if (mem_high || swap_high) { +/* +* The allocating tasks in this cgroup will need to do +* reclaim or be throttled to prevent further growth +* of the memory or swap footprints. +* +* Target some best-effort fairness between the tasks, +* and distribute reclaim work and delay penalties +* based on how much each task is actually allocating. +*/ + current->memcg_nr_pages_over_high += nr_pages; +set_notify_resume (current); +break ; + } + }while ((memcg =parent_mem_cgroup (memcg))); + +/* +* Reclaim is set up above to be called from the userland +* return path. But also attempt synchronous reclaim to avoid +* excessive overrun while the task is still inside the +* kernel. If this is successful, the return path will see it +* when it rechecks the overage and simply bail out. +*/ +if (current->memcg_nr_pages_over_high >MEMCG_CHARGE_BATCH && + !(current->flags &PF_MEMALLOC ) && +gfpflags_allow_blocking (gfp_mask)) +__mem_cgroup_handle_over_high (gfp_mask); +} +static int try_charge_memcg (struct mem_cgroup *memcg,gfp_t gfp_mask,unsigned int nr_pages) {
@@ -2853,62 +2921,7 @@ static int try_charge_memcg(struct mem_cgroup *memcg, gfp_t gfp_mask,if (batch > nr_pages)refill_stock (memcg, batch - nr_pages); -/* -* If the hierarchy is above the normal consumption range, schedule -* reclaim on returning to userland. We can perform reclaim here -* if __GFP_RECLAIM but let's always punt for simplicity and so that -* GFP_KERNEL can consistently be used during reclaim. @memcg is -* not recorded as it most likely matches current's and won't -* change in the meantime. As high limit is checked again before -* reclaim, the cost of mismatch is negligible. -*/ -do { -bool mem_high, swap_high; - - mem_high =page_counter_read (&memcg->memory) > -READ_ONCE (memcg->memory.high); - swap_high =page_counter_read (&memcg->swap) > -READ_ONCE (memcg->swap.high); - -/* Don't bother a random interrupted task */ -if (!in_task ()) { -if (mem_high) { -if (allow_spinning) -schedule_work (&memcg->high_work); -else -irq_work_queue (&memcg->high_irq_work); -break ; - } -continue ; - } - -if (mem_high || swap_high) { -/* -* The allocating tasks in this cgroup will need to do -* reclaim or be throttled to prevent further growth -* of the memory or swap footprints. -* -* Target some best-effort fairness between the tasks, -* and distribute reclaim work and delay penalties -* based on how much each task is actually allocating. -*/ - current->memcg_nr_pages_over_high += batch; -set_notify_resume (current); -break ; - } - }while ((memcg =parent_mem_cgroup (memcg))); - -/* -* Reclaim is set up above to be called from the userland -* return path. But also attempt synchronous reclaim to avoid -* excessive overrun while the task is still inside the -* kernel. If this is successful, the return path will see it -* when it rechecks the overage and simply bail out. -*/ -if (current->memcg_nr_pages_over_high >MEMCG_CHARGE_BATCH && - !(current->flags &PF_MEMALLOC ) && -gfpflags_allow_blocking (gfp_mask)) -__mem_cgroup_handle_over_high (gfp_mask); +memcg_enforce_high (memcg, batch, gfp_mask);return ret; }
Signature · 1 line
2.53.0-Meta
Re: [PATCH for-7.5 1/5] memcg: move memory.high enforcement out of try_charge_memcg()
Quoted 11 lines · Shakeel Butt
try_charge_memcg()does a lot. It charges the page counters, reclaims when a limit is hit, may call the OOM killer, and at the end it checks and enforces memory.high. Move the memory.high part into a new function,memcg_enforce_high(), so thattry_charge_memcg()is shorter and the memory.high code is easier to read and change.The code and its comments move as they are. The only difference is that the new function gets
allow_spinningfromgfp_maskitself.No functional change.
---
Missing Signed-off-by: ?
Otherwise LGTM.
Signature · 2 lines
Cheers, Harry / Hyeonggon
Shakeel Butt
maintainerRe: [PATCH for-7.5 1/5] memcg: move memory.high enforcement out of try_charge_memcg()
Quoted 7 lines · Harry Yoo
Quoted 6 lines · Shakeel Butt
try_charge_memcg()does a lot. It charges the page counters, reclaims when a limit is hit, may call the OOM killer, and at the end it checks and enforces memory.high. Move the memory.high part into a new function,memcg_enforce_high(), so thattry_charge_memcg()is shorter and the memory.high code is easier to read and change.
Quoted 7 lines
Quoted 5 lines
The code and its comments move as they are. The only difference is that the new function gets
allow_spinningfromgfp_maskitself.No functional change.
---Missing Signed-off-by: ?
Oh I don't know how I missed this.
Quoted 3 lines
Otherwise LGTM.
Reviewed-by Harry Yoo (Meta) <[email protected]>
Thanks for the review.
Re: [PATCH for-7.5 1/5] memcg: move memory.high enforcement out of try_charge_memcg()
In reply to Shakeel ButtQuoted 10 lines · Shakeel Butt
try_charge_memcg()does a lot. It charges the page counters, reclaims when a limit is hit, may call the OOM killer, and at the end it checks and enforces memory.high. Move the memory.high part into a new function,memcg_enforce_high(), so thattry_charge_memcg()is shorter and the memory.high code is easier to read and change.The code and its comments move as they are. The only difference is that the new function gets
allow_spinningfromgfp_maskitself.No functional change.
Thanks!
Quoted 149 lines
--- mm/memcontrol.c | 125 ++++++++++++++++++++++++++---------------------- 1 file changed, 69 insertions(+), 56 deletions(-)mm/memcontrol.c +69
-56@@ -2696,6 +2696,74 @@ void __mem_cgroup_handle_over_high(gfp_t gfp_mask)css_put (&memcg->css); } +/* +* Called after @nr_pages were charged to @memcg. If @memcg or one of its +* ancestors is over memory.high or memory.swap.high, start reclaim or slow +* down the task. +*/ +static void memcg_enforce_high (struct mem_cgroup *memcg,unsigned int nr_pages, +gfp_t gfp_mask) +{ +bool allow_spinning =gfpflags_allow_spinning (gfp_mask); + +/* +* If the hierarchy is above the normal consumption range, schedule +* reclaim on returning to userland. We can perform reclaim here +* if __GFP_RECLAIM but let's always punt for simplicity and so that +* GFP_KERNEL can consistently be used during reclaim. @memcg is +* not recorded as it most likely matches current's and won't +* change in the meantime. As high limit is checked again before +* reclaim, the cost of mismatch is negligible. +*/ +do { +bool mem_high, swap_high; + + mem_high =page_counter_read (&memcg->memory) > +READ_ONCE (memcg->memory.high); + swap_high =page_counter_read (&memcg->swap) > +READ_ONCE (memcg->swap.high); + +/* Don't bother a random interrupted task */ +if (!in_task ()) { +if (mem_high) { +if (allow_spinning) +schedule_work (&memcg->high_work); +else +irq_work_queue (&memcg->high_irq_work); +break ; + } +continue ; + } + +if (mem_high || swap_high) { +/* +* The allocating tasks in this cgroup will need to do +* reclaim or be throttled to prevent further growth +* of the memory or swap footprints. +* +* Target some best-effort fairness between the tasks, +* and distribute reclaim work and delay penalties +* based on how much each task is actually allocating. +*/ + current->memcg_nr_pages_over_high += nr_pages; +set_notify_resume (current); +break ; + } + }while ((memcg =parent_mem_cgroup (memcg))); + +/* +* Reclaim is set up above to be called from the userland +* return path. But also attempt synchronous reclaim to avoid +* excessive overrun while the task is still inside the +* kernel. If this is successful, the return path will see it +* when it rechecks the overage and simply bail out. +*/ +if (current->memcg_nr_pages_over_high >MEMCG_CHARGE_BATCH && + !(current->flags &PF_MEMALLOC ) && +gfpflags_allow_blocking (gfp_mask)) +__mem_cgroup_handle_over_high (gfp_mask); +} +static int try_charge_memcg (struct mem_cgroup *memcg,gfp_t gfp_mask,unsigned int nr_pages) {@@ -2853,62 +2921,7 @@ static int try_charge_memcg(struct mem_cgroup *memcg, gfp_t gfp_mask,if (batch > nr_pages)refill_stock (memcg, batch - nr_pages); -/* -* If the hierarchy is above the normal consumption range, schedule -* reclaim on returning to userland. We can perform reclaim here -* if __GFP_RECLAIM but let's always punt for simplicity and so that -* GFP_KERNEL can consistently be used during reclaim. @memcg is -* not recorded as it most likely matches current's and won't -* change in the meantime. As high limit is checked again before -* reclaim, the cost of mismatch is negligible. -*/ -do { -bool mem_high, swap_high; - - mem_high =page_counter_read (&memcg->memory) > -READ_ONCE (memcg->memory.high); - swap_high =page_counter_read (&memcg->swap) > -READ_ONCE (memcg->swap.high); - -/* Don't bother a random interrupted task */ -if (!in_task ()) { -if (mem_high) { -if (allow_spinning) -schedule_work (&memcg->high_work); -else -irq_work_queue (&memcg->high_irq_work); -break ; - } -continue ; - } - -if (mem_high || swap_high) { -/* -* The allocating tasks in this cgroup will need to do -* reclaim or be throttled to prevent further growth -* of the memory or swap footprints. -* -* Target some best-effort fairness between the tasks, -* and distribute reclaim work and delay penalties -* based on how much each task is actually allocating. -*/ - current->memcg_nr_pages_over_high += batch; -set_notify_resume (current); -break ; - } - }while ((memcg =parent_mem_cgroup (memcg))); - -/* -* Reclaim is set up above to be called from the userland -* return path. But also attempt synchronous reclaim to avoid -* excessive overrun while the task is still inside the -* kernel. If this is successful, the return path will see it -* when it rechecks the overage and simply bail out. -*/ -if (current->memcg_nr_pages_over_high >MEMCG_CHARGE_BATCH && - !(current->flags &PF_MEMALLOC ) && -gfpflags_allow_blocking (gfp_mask)) -__mem_cgroup_handle_over_high (gfp_mask); +memcg_enforce_high (memcg, batch, gfp_mask);return ret; }Signature · 1 line
2.53.0-Meta
Signature · 2 lines
Michal Hocko SUSE Labs
Shakeel Butt
maintainerPATCH 2/5 memcg: use high_work for kernel threads
In reply to Shakeel ButtKernel threads never return to userspace, so deferred memory.high reclaim does not run. Reclaim and throttling during a charge can also delay work they do for other tasks.
Treat kernel threads like interrupts. Queue high_work for the first cgroup over memory.high and skip reclaim and throttling in the charging thread.
User workers created by create_io_thread() and vhost_task_create() keep their current behavior. Vhost kernel threads use the new path, even when they borrow the owner's mm.
The worker requests a fixed batch of pages each time. A fast allocator can keep usage well above memory.high. Kernel threads also skip memory.swap.high throttling and its high-event reporting. The memory.max and memory.swap.max checks stay the same.
--- mm/memcontrol.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-)
mm/memcontrol.c +11 -2
@@ -2705,6 +2705,7 @@ static void memcg_enforce_high(struct mem_cgroup *memcg, unsigned int nr_pages, gfp_t gfp_mask) {bool allow_spinning =gfpflags_allow_spinning (gfp_mask); +bool use_worker = !in_task () || (current->flags &PF_KTHREAD );/* * If the hierarchy is above the normal consumption range, schedule
@@ -2723,8 +2724,12 @@ static void memcg_enforce_high(struct mem_cgroup *memcg, unsigned int nr_pages, swap_high =page_counter_read (&memcg->swap) >READ_ONCE (memcg->swap.high); -/* Don't bother a random interrupted task */ -if (!in_task ()) { +/* +* Don't make an unrelated interrupted task handle this charge. +* Kernel threads may be working for other tasks, so let a worker +* reclaim instead. +*/ +if (use_worker) {if (mem_high) {if (allow_spinning)schedule_work (&memcg->high_work);
@@ -2751,6 +2756,10 @@ static void memcg_enforce_high(struct mem_cgroup *memcg, unsigned int nr_pages, } }while ((memcg =parent_mem_cgroup (memcg))); +/* Interrupts and kernel threads stop here. */ +if (use_worker) +return ; +/* * Reclaim is set up above to be called from the userland * return path. But also attempt synchronous reclaim to avoid
Signature · 1 line
2.53.0-Meta
Re: [PATCH for-7.5 2/5] memcg: use high_work for kernel threads
Quoted 12 lines · Shakeel Butt
Kernel threads never return to userspace, so deferred memory.high reclaim does not run. Reclaim and throttling during a charge can also delay work they do for other tasks.
Treat kernel threads like interrupts. Queue
high_workfor the first cgroup over memory.high and skip reclaim and throttling in the charging thread.User workers created by
create_io_thread()andvhost_task_create()keep their current behavior. Vhost kernel threads use the new path, even when they borrow the owner's mm.
Quoted 7 lines
The worker requests a fixed batch of pages each time. A fast allocator can keep usage well above memory.high. Kernel threads also skip memory.swap.high throttling and its high-event reporting. The memory.max and memory.swap.max checks stay the same.
Suggested-by Tejun Heo <[email protected]>Signed-off-by Shakeel Butt <[email protected]>
yes this makes sense as a general improvemnt.
Thanks!
Quoted 45 lines
--- mm/memcontrol.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-)mm/memcontrol.c +11
-2@@ -2705,6 +2705,7 @@ static void memcg_enforce_high(struct mem_cgroup *memcg, unsigned int nr_pages, gfp_t gfp_mask) {bool allow_spinning =gfpflags_allow_spinning (gfp_mask); +bool use_worker = !in_task () || (current->flags &PF_KTHREAD );/* * If the hierarchy is above the normal consumption range, schedule @@ -2723,8 +2724,12 @@ static void memcg_enforce_high(struct mem_cgroup *memcg, unsigned int nr_pages, swap_high =page_counter_read (&memcg->swap) >READ_ONCE (memcg->swap.high); -/* Don't bother a random interrupted task */ -if (!in_task ()) { +/* +* Don't make an unrelated interrupted task handle this charge. +* Kernel threads may be working for other tasks, so let a worker +* reclaim instead. +*/ +if (use_worker) {if (mem_high) {if (allow_spinning)schedule_work (&memcg->high_work);@@ -2751,6 +2756,10 @@ static void memcg_enforce_high(struct mem_cgroup *memcg, unsigned int nr_pages, } }while ((memcg =parent_mem_cgroup (memcg))); +/* Interrupts and kernel threads stop here. */ +if (use_worker) +return ; +/* * Reclaim is set up above to be called from the userland * return path. But also attempt synchronous reclaim to avoid Signature · 1 line
2.53.0-Meta
Signature · 2 lines
Michal Hocko SUSE Labs
Shakeel Butt
maintainerPATCH 3/5 memcg: use high_work for remote charges
In reply to Shakeel ButtSome charges go to a cgroup outside the current mm's cgroup and its ancestors. The high handler uses current->mm, so it cannot handle these overages. Filesystem notifications and remote page faults can take this path.
Use mm_match_cgroup() before checking the high limits. If it does not match, use high_work instead of recording task overage or doing synchronous reclaim and throttling. Charges to the same cgroup or an ancestor keep their current handling. Tasks without an mm also use the worker.
Like kernel threads, remote chargers skip memory.swap.high throttling and its high-event reporting, and a fast allocator can outrun the worker. The memory.max and memory.swap.max checks stay the same.
--- mm/memcontrol.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-)
mm/memcontrol.c +4 -2
@@ -2705,7 +2705,8 @@ static void memcg_enforce_high(struct mem_cgroup *memcg, unsigned int nr_pages, gfp_t gfp_mask) {bool allow_spinning =gfpflags_allow_spinning (gfp_mask); -bool use_worker = !in_task () || (current->flags &PF_KTHREAD ); +bool use_worker = !in_task () || (current->flags &PF_KTHREAD ) || + !current->mm || !mm_match_cgroup (current->mm, memcg);/* * If the hierarchy is above the normal consumption range, schedule
@@ -2728,6 +2729,7 @@ static void memcg_enforce_high(struct mem_cgroup *memcg, unsigned int nr_pages,* Don't make an unrelated interrupted task handle this charge. * Kernel threads may be working for other tasks, so let a worker * reclaim instead. +* Remote charges need reclaim from their charged hierarchy. */ if (use_worker) {if (mem_high) {
@@ -2756,7 +2758,7 @@ static void memcg_enforce_high(struct mem_cgroup *memcg, unsigned int nr_pages, } }while ((memcg =parent_mem_cgroup (memcg))); -/* Interrupts and kernel threads stop here. */ +/* Interrupts, kernel threads, and remote chargers stop here. */ if (use_worker)return ;
Signature · 1 line
2.53.0-Meta
Re: [PATCH for-7.5 3/5] memcg: use high_work for remote charges
Quoted 4 lines · Shakeel Butt
Some charges go to a cgroup outside the current mm's cgroup and its ancestors. The high handler uses
current->mm, so it cannot handle these overages. Filesystem notifications and remote page faults can take this path.
Hmm, you made me look at other places in the charge path where we are assuming current->memcg == memcg. We have reclaim recursion and oom checks. The former makes sense, for the latter it is likely not harmful.
Quoted 9 lines
Use
mm_match_cgroup()before checking the high limits. If it does not match, usehigh_workinstead of recording task overage or doing synchronous reclaim and throttling. Charges to the same cgroup or an ancestor keep their current handling. Tasks without an mm also use the worker.Like kernel threads, remote chargers skip memory.swap.high throttling and its high-event reporting, and a fast allocator can outrun the worker. The memory.max and memory.swap.max checks stay the same.
Makes sense.
Quoted 2 lines
Suggested-by Tejun Heo <[email protected]>Signed-off-by Shakeel Butt <[email protected]>
Thanks!
Quoted 37 lines
--- mm/memcontrol.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-)mm/memcontrol.c +4
-2@@ -2705,7 +2705,8 @@ static void memcg_enforce_high(struct mem_cgroup *memcg, unsigned int nr_pages, gfp_t gfp_mask) {bool allow_spinning =gfpflags_allow_spinning (gfp_mask); -bool use_worker = !in_task () || (current->flags &PF_KTHREAD ); +bool use_worker = !in_task () || (current->flags &PF_KTHREAD ) || + !current->mm || !mm_match_cgroup (current->mm, memcg);/* * If the hierarchy is above the normal consumption range, schedule @@ -2728,6 +2729,7 @@ static void memcg_enforce_high(struct mem_cgroup *memcg, unsigned int nr_pages,* Don't make an unrelated interrupted task handle this charge. * Kernel threads may be working for other tasks, so let a worker * reclaim instead. +* Remote charges need reclaim from their charged hierarchy. */ if (use_worker) {if (mem_high) {@@ -2756,7 +2758,7 @@ static void memcg_enforce_high(struct mem_cgroup *memcg, unsigned int nr_pages, } }while ((memcg =parent_mem_cgroup (memcg))); -/* Interrupts and kernel threads stop here. */ +/* Interrupts, kernel threads, and remote chargers stop here. */ if (use_worker)return ;Signature · 1 line
2.53.0-Meta
Signature · 2 lines
Michal Hocko SUSE Labs
Shakeel Butt
maintainerPATCH 4/5 memcg: avoid queuing high_work from reclaim
In reply to Shakeel ButtKernel threads and remote chargers use high_work when memory.high is exceeded. Their allocations during reclaim can also charge memory, for example when zswap stores a page.
Without a check, the high_work worker can queue itself from its own reclaim. The pending bit is cleared before high_work runs, so each run can queue another run.
Skip memory.high enforcement for charges from task context with PF_MEMALLOC set when they would use high_work. This covers kernel threads and remote charges. The memory is still charged. Interrupt charges keep their current path.
In a VM with incompressible zswap data, the worker queued itself 163 times without this check.
--- mm/memcontrol.c | 8 ++++++++ 1 file changed, 8 insertions(+)
mm/memcontrol.c +8 -0
@@ -2708,6 +2708,14 @@ static void memcg_enforce_high(struct mem_cgroup *memcg, unsigned int nr_pages,bool use_worker = !in_task () || (current->flags &PF_KTHREAD ) || !current->mm || !mm_match_cgroup (current->mm, memcg); +/* +* Reclaim can charge memory, for example when zswap stores a page. +* Don't queue high_work from a reclaiming task, or the +* high_work worker could keep queuing itself. +*/ +if (in_task () && use_worker && (current->flags &PF_MEMALLOC )) +return ; +/* * If the hierarchy is above the normal consumption range, schedule * reclaim on returning to userland. We can perform reclaim here
Signature · 1 line
2.53.0-Meta
Re: [PATCH for-7.5 4/5] memcg: avoid queuing high_work from reclaim
Quoted 7 lines · Shakeel Butt
Kernel threads and remote chargers use
high_workwhen memory.high is exceeded. Their allocations during reclaim can also charge memory, for example when zswap stores a page.Without a check, the
high_workworker can queue itself from its own reclaim. The pending bit is cleared beforehigh_workruns, so each run can queue another run.
What does that mean really? Does this delays the actual run?
Quoted 9 lines
Skip memory.high enforcement for charges from task context with
PF_MEMALLOCset when they would usehigh_work. This covers kernel threads and remote charges. The memory is still charged. Interrupt charges keep their current path.In a VM with incompressible zswap data, the worker queued itself 163 times without this check.
Signed-off-by Shakeel Butt <[email protected]>
The patch itself looks OK. I am wondering whether we can unify the check with the existing one which forces the allocation through in main charging path.
Quoted 25 lines
--- mm/memcontrol.c | 8 ++++++++ 1 file changed, 8 insertions(+)mm/memcontrol.c +8
-0@@ -2708,6 +2708,14 @@ static void memcg_enforce_high(struct mem_cgroup *memcg, unsigned int nr_pages,bool use_worker = !in_task () || (current->flags &PF_KTHREAD ) || !current->mm || !mm_match_cgroup (current->mm, memcg); +/* +* Reclaim can charge memory, for example when zswap stores a page. +* Don't queue high_work from a reclaiming task, or the +* high_work worker could keep queuing itself. +*/ +if (in_task () && use_worker && (current->flags &PF_MEMALLOC )) +return ; +/* * If the hierarchy is above the normal consumption range, schedule * reclaim on returning to userland. We can perform reclaim here Signature · 1 line
2.53.0-Meta
Signature · 2 lines
Michal Hocko SUSE Labs
Shakeel Butt
maintainerRe: [PATCH for-7.5 4/5] memcg: avoid queuing high_work from reclaim
Quoted 10 lines · Michal Hocko
Quoted 7 lines · Shakeel Butt
Kernel threads and remote chargers use
high_workwhen memory.high is exceeded. Their allocations during reclaim can also charge memory, for example when zswap stores a page.Without a check, the
high_workworker can queue itself from its own reclaim. The pending bit is cleared beforehigh_workruns, so each run can queue another run.What does that mean really? Does this delays the actual run?
It will not delay the actual charge request. This mainly means that we keep enqueing the high reclaim work even from high reclaim context and can potentially keep wasting CPU if high reclaim is not very successful.
Quoted 6 lines
Quoted 6 lines
Skip memory.high enforcement for charges from task context with
PF_MEMALLOCset when they would usehigh_work. This covers kernel threads and remote charges. The memory is still charged. Interrupt charges keep their current path.
Quoted 8 lines
Quoted 4 lines
In a VM with incompressible zswap data, the worker queued itself 163 times without this check.
Signed-off-by Shakeel Butt <[email protected]>The patch itself looks OK. I am wondering whether we can unify the check with the existing one which forces the allocation through in main charging path.
The force charge (and ENOMEM) paths bypass the memory.high enforcement altogether. Only successful charges (done_restock label) goes through memory.high enforcement. So, even if we move this check in try_charge_memcg, it will still be a separate check before calling high enforcement function. I think this check is very high_work specific and better to be in the high enforcement function.
Re: [PATCH for-7.5 4/5] memcg: avoid queuing high_work from reclaim
Quoted 10 lines · Shakeel Butt
Quoted 9 lines · Michal Hocko
Quoted 7 lines · Shakeel Butt
Kernel threads and remote chargers use
high_workwhen memory.high is exceeded. Their allocations during reclaim can also charge memory, for example when zswap stores a page.Without a check, the
high_workworker can queue itself from its own reclaim. The pending bit is cleared beforehigh_workruns, so each run can queue another run.
Quoted 5 lines
Quoted 1 line
What does that mean really? Does this delays the actual run?
It will not delay the actual charge request. This mainly means that we keep enqueing the high reclaim work even from high reclaim context and can potentially keep wasting CPU if high reclaim is not very successful.
Hmm, so is there any practical problem if we do that? What exactly do you mean by "keep wasting CPU"?
Quoted 8 lines
Quoted 8 lines
Quoted 8 lines
Skip memory.high enforcement for charges from task context with
PF_MEMALLOCset when they would usehigh_work. This covers kernel threads and remote charges. The memory is still charged. Interrupt charges keep their current path.In a VM with incompressible zswap data, the worker queued itself 163 times without this check.
Quoted 8 lines
Quoted 5 lines
Quoted 1 line
Signed-off-by Shakeel Butt <[email protected]>The patch itself looks OK. I am wondering whether we can unify the check with the existing one which forces the allocation through in main charging path.
The force charge (and ENOMEM) paths bypass the memory.high enforcement altogether.
Exactly. Isn't that exactl what we want to achieve here as well?
Quoted 5 lines
Only successful charges (
done_restocklabel) goes through memory.high enforcement. So, even if we move this check intry_charge_memcg, it will still be a separate check before calling high enforcement function. I think this check is veryhigh_workspecific and better to be in the high enforcement function.
I meant something like the following
mm/memcontrol.c +11 -18
@@ -2653,7 +2653,6 @@ static int try_charge_memcg(struct mem_cgroup *memcg, gfp_t gfp_mask,bool passed_oom = false;unsigned int reclaim_options;bool drained = false; -bool raised_max_event = false;unsigned long pflags;bool allow_spinning =gfpflags_allow_spinning (gfp_mask);
@@ -2661,6 +2660,15 @@ static int try_charge_memcg(struct mem_cgroup *memcg, gfp_t gfp_mask,if (consume_stock (memcg, nr_pages))return 0 ; +/* +* Prevent unbounded recursion when reclaim operations need to +* allocate memory. This might exceed the limits temporarily, +* but we prefer facilitating memory reclaim and getting back +* under the limit over triggering OOM kills in these cases. +*/ +if (unlikely (current->flags &PF_MEMALLOC )) + goto force; +if (!allow_spinning)/* Avoid the refill and flush of the older stock */ batch = nr_pages;
@@ -2683,15 +2691,6 @@ static int try_charge_memcg(struct mem_cgroup *memcg, gfp_t gfp_mask, goto retry; } -/* -* Prevent unbounded recursion when reclaim operations need to -* allocate memory. This might exceed the limits temporarily, -* but we prefer facilitating memory reclaim and getting back -* under the limit over triggering OOM kills in these cases. -*/ -if (unlikely (current->flags &PF_MEMALLOC )) - goto force; -if (unlikely (task_in_memcg_oom (current))) goto nomem;
@@ -2712,7 +2711,6 @@ static int try_charge_memcg(struct mem_cgroup *memcg, gfp_t gfp_mask, goto nomem;__memcg_memory_event (mem_over_limit,MEMCG_MAX , allow_spinning); - raised_max_event = true;psi_memstall_enter (&pflags); nr_reclaimed = try_to_free_mem_cgroup_pages(mem_over_limit, nr_pages,
@@ -2773,13 +2771,6 @@ static int try_charge_memcg(struct mem_cgroup *memcg, gfp_t gfp_mask,if (!(gfp_mask & (__GFP_NOFAIL | __GFP_HIGH)))return -ENOMEM ; force: -/* -* If the allocation has to be enforced, don't forget to raise -* a MEMCG_MAX event. -*/ -if (!raised_max_event) -__memcg_memory_event (mem_over_limit,MEMCG_MAX , allow_spinning); -/* * The allocation either can't fail or will lead to more memory * being freed very soon. Allow memory usage go over the limit
@@ -2789,6 +2780,8 @@ static int try_charge_memcg(struct mem_cgroup *memcg, gfp_t gfp_mask,if (do_memsw_account ())page_counter_charge (&memcg->memsw, nr_pages); +if (mem_cgroup_margin (memcg)) +__memcg_memory_event (memcg,MEMCG_MAX , allow_spinning);return 0 ; done_restock:
Signature · 2 lines
Michal Hocko SUSE Labs
Shakeel Butt
maintainerRe: [PATCH for-7.5 4/5] memcg: avoid queuing high_work from reclaim
Quoted 11 lines · Michal Hocko
Quoted 10 lines · Shakeel Butt
Quoted 9 lines · Michal Hocko
Quoted 7 lines · Shakeel Butt
Kernel threads and remote chargers use
high_workwhen memory.high is exceeded. Their allocations during reclaim can also charge memory, for example when zswap stores a page.Without a check, the
high_workworker can queue itself from its own reclaim. The pending bit is cleared beforehigh_workruns, so each run can queue another run.
Quoted 8 lines
Quoted 5 lines
Quoted 1 line
What does that mean really? Does this delays the actual run?
It will not delay the actual charge request. This mainly means that we keep enqueing the high reclaim work even from high reclaim context and can potentially keep wasting CPU if high reclaim is not very successful.
Hmm, so is there any practical problem if we do that? What exactly do you mean by "keep wasting CPU"?
More specifically if high_work kworker keeps enqueing high_work, most probably the memcg does not have reclaimable memory and keep retrying it wastes CPU.
Quoted 20 lines
Quoted 9 lines
Quoted 9 lines
Quoted 9 lines
Skip memory.high enforcement for charges from task context with
PF_MEMALLOCset when they would usehigh_work. This covers kernel threads and remote charges. The memory is still charged. Interrupt charges keep their current path.In a VM with incompressible zswap data, the worker queued itself 163 times without this check.
Quoted 8 lines
Quoted 5 lines
Quoted 1 line
Signed-off-by Shakeel Butt <[email protected]>The patch itself looks OK. I am wondering whether we can unify the check with the existing one which forces the allocation through in main charging path.
The force charge (and ENOMEM) paths bypass the memory.high enforcement altogether.
Exactly. Isn't that exactl what we want to achieve here as well?
Quoted 7 lines
Quoted 5 lines
Only successful charges (
done_restocklabel) goes through memory.high enforcement. So, even if we move this check intry_charge_memcg, it will still be a separate check before calling high enforcement function. I think this check is veryhigh_workspecific and better to be in the high enforcement function.I meant something like the following
Oh ok this makes sense. Do you want to send a patch or should I add this in this series replacing the 4th patch?
Quoted 80 lines
mm/memcontrol.c +11
-18@@ -2653,7 +2653,6 @@ static int try_charge_memcg(struct mem_cgroup *memcg, gfp_t gfp_mask,bool passed_oom = false;unsigned int reclaim_options;bool drained = false; -bool raised_max_event = false;unsigned long pflags;bool allow_spinning =gfpflags_allow_spinning (gfp_mask);@@ -2661,6 +2660,15 @@ static int try_charge_memcg(struct mem_cgroup *memcg, gfp_t gfp_mask,if (consume_stock (memcg, nr_pages))return 0 ; +/* +* Prevent unbounded recursion when reclaim operations need to +* allocate memory. This might exceed the limits temporarily, +* but we prefer facilitating memory reclaim and getting back +* under the limit over triggering OOM kills in these cases. +*/ +if (unlikely (current->flags &PF_MEMALLOC )) + goto force; +if (!allow_spinning)/* Avoid the refill and flush of the older stock */ batch = nr_pages;@@ -2683,15 +2691,6 @@ static int try_charge_memcg(struct mem_cgroup *memcg, gfp_t gfp_mask, goto retry; } -/* -* Prevent unbounded recursion when reclaim operations need to -* allocate memory. This might exceed the limits temporarily, -* but we prefer facilitating memory reclaim and getting back -* under the limit over triggering OOM kills in these cases. -*/ -if (unlikely (current->flags &PF_MEMALLOC )) - goto force; -if (unlikely (task_in_memcg_oom (current))) goto nomem;@@ -2712,7 +2711,6 @@ static int try_charge_memcg(struct mem_cgroup *memcg, gfp_t gfp_mask, goto nomem;__memcg_memory_event (mem_over_limit,MEMCG_MAX , allow_spinning); - raised_max_event = true;psi_memstall_enter (&pflags); nr_reclaimed = try_to_free_mem_cgroup_pages(mem_over_limit, nr_pages,@@ -2773,13 +2771,6 @@ static int try_charge_memcg(struct mem_cgroup *memcg, gfp_t gfp_mask,if (!(gfp_mask & (__GFP_NOFAIL | __GFP_HIGH)))return -ENOMEM ; force: -/* -* If the allocation has to be enforced, don't forget to raise -* a MEMCG_MAX event. -*/ -if (!raised_max_event) -__memcg_memory_event (mem_over_limit,MEMCG_MAX , allow_spinning); -/* * The allocation either can't fail or will lead to more memory * being freed very soon. Allow memory usage go over the limit @@ -2789,6 +2780,8 @@ static int try_charge_memcg(struct mem_cgroup *memcg, gfp_t gfp_mask,if (do_memsw_account ())page_counter_charge (&memcg->memsw, nr_pages); +if (mem_cgroup_margin (memcg)) +__memcg_memory_event (memcg,MEMCG_MAX , allow_spinning);return 0 ; done_restock:Signature · 3 lines
-- Michal Hocko SUSE Labs
Re: [PATCH for-7.5 4/5] memcg: avoid queuing high_work from reclaim
Quoted 18 lines · Shakeel Butt
Quoted 17 lines · Michal Hocko
Quoted 15 lines · Shakeel Butt
Quoted 10 lines · Michal Hocko
Quoted 7 lines · Shakeel Butt
Kernel threads and remote chargers use
high_workwhen memory.high is exceeded. Their allocations during reclaim can also charge memory, for example when zswap stores a page.Without a check, the
high_workworker can queue itself from its own reclaim. The pending bit is cleared beforehigh_workruns, so each run can queue another run.What does that mean really? Does this delays the actual run?
It will not delay the actual charge request. This mainly means that we keep enqueing the high reclaim work even from high reclaim context and can potentially keep wasting CPU if high reclaim is not very successful.
Quoted 5 lines
Quoted 2 lines
Hmm, so is there any practical problem if we do that? What exactly do you mean by "keep wasting CPU"?
More specifically if
high_workkworker keeps enqueinghigh_work, most probably the memcg does not have reclaimable memory and keep retrying it wastes CPU.
Yes but this is not specific to PF_MEMALLOC context, or do I miss something?
Quoted 25 lines
Quoted 25 lines
Quoted 8 lines
Quoted 8 lines
Quoted 8 lines
Skip memory.high enforcement for charges from task context with
PF_MEMALLOCset when they would usehigh_work. This covers kernel threads and remote charges. The memory is still charged. Interrupt charges keep their current path.In a VM with incompressible zswap data, the worker queued itself 163 times without this check.
Quoted 8 lines
Quoted 5 lines
Quoted 1 line
Signed-off-by Shakeel Butt <[email protected]>The patch itself looks OK. I am wondering whether we can unify the check with the existing one which forces the allocation through in main charging path.
The force charge (and ENOMEM) paths bypass the memory.high enforcement altogether.
Exactly. Isn't that exactl what we want to achieve here as well?
Quoted 5 lines
Only successful charges (
done_restocklabel) goes through memory.high enforcement. So, even if we move this check intry_charge_memcg, it will still be a separate check before calling high enforcement function. I think this check is veryhigh_workspecific and better to be in the high enforcement function.
Quoted 4 lines
Quoted 1 line
I meant something like the following
Oh ok this makes sense. Do you want to send a patch or should I add this in this series replacing the 4th patch?
Feel free to take it. I haven't even tried to compile this. Just wanted to show what I have in mind.
Signature · 2 lines
Michal Hocko SUSE Labs
Shakeel Butt
maintainerRe: [PATCH for-7.5 4/5] memcg: avoid queuing high_work from reclaim
Quoted 19 lines · Michal Hocko
Quoted 18 lines · Shakeel Butt
Quoted 17 lines · Michal Hocko
Quoted 15 lines · Shakeel Butt
Quoted 10 lines · Michal Hocko
Quoted 7 lines · Shakeel Butt
Kernel threads and remote chargers use
high_workwhen memory.high is exceeded. Their allocations during reclaim can also charge memory, for example when zswap stores a page.Without a check, the
high_workworker can queue itself from its own reclaim. The pending bit is cleared beforehigh_workruns, so each run can queue another run.What does that mean really? Does this delays the actual run?
It will not delay the actual charge request. This mainly means that we keep enqueing the high reclaim work even from high reclaim context and can potentially keep wasting CPU if high reclaim is not very successful.
Quoted 8 lines
Quoted 5 lines
Quoted 2 lines
Hmm, so is there any practical problem if we do that? What exactly do you mean by "keep wasting CPU"?
More specifically if
high_workkworker keeps enqueinghigh_work, most probably the memcg does not have reclaimable memory and keep retrying it wastes CPU.Yes but this is not specific to
PF_MEMALLOCcontext, or do I miss something?
Yes you are right and you didn't miss anything.
Quoted 26 lines
Quoted 26 lines
Quoted 26 lines
Quoted 9 lines
Quoted 9 lines
Quoted 9 lines
Skip memory.high enforcement for charges from task context with
PF_MEMALLOCset when they would usehigh_work. This covers kernel threads and remote charges. The memory is still charged. Interrupt charges keep their current path.In a VM with incompressible zswap data, the worker queued itself 163 times without this check.
Quoted 8 lines
Quoted 5 lines
Quoted 1 line
Signed-off-by Shakeel Butt <[email protected]>The patch itself looks OK. I am wondering whether we can unify the check with the existing one which forces the allocation through in main charging path.
The force charge (and ENOMEM) paths bypass the memory.high enforcement altogether.
Exactly. Isn't that exactl what we want to achieve here as well?
Quoted 5 lines
Only successful charges (
done_restocklabel) goes through memory.high enforcement. So, even if we move this check intry_charge_memcg, it will still be a separate check before calling high enforcement function. I think this check is veryhigh_workspecific and better to be in the high enforcement function.
Quoted 7 lines
Quoted 4 lines
Quoted 1 line
I meant something like the following
Oh ok this makes sense. Do you want to send a patch or should I add this in this series replacing the 4th patch?
Feel free to take it. I haven't even tried to compile this. Just wanted to show what I have in mind.
Cool, I will resend the updated series after the merge window.
Shakeel Butt
maintainerPATCH 5/5 memcg: simplify high limit handling after a charge
In reply to Shakeel ButtSeparate the hierarchy scan from high-limit enforcement. Find the first cgroup over the applicable limit, then queue high_work or record the task's overage after the scan. Worker contexts check only memory.high; local charges also check memory.swap.high.
Read in_task() once and clarify the enforcement comments. Keep the synchronous pending-overage check independent of the scan result so a blocking charge still handles overage left by earlier charges.
No functional change intended.
--- mm/memcontrol.c | 100 +++++++++++++++++++++--------------------------- 1 file changed, 43 insertions(+), 57 deletions(-)
mm/memcontrol.c +43 -57
@@ -2602,9 +2602,8 @@ static unsigned long calculate_high_delay(unsigned int nr_pages, }/* -* Reclaims memory over the high limit. Called directly from -* try_charge() (context permitting), as well as from the userland -* return path where reclaim is always able to block. +* Handle pending high overage during a blocking charge or on return +* to userspace. */ void __mem_cgroup_handle_over_high (gfp_t gfp_mask) {
@@ -2704,78 +2703,65 @@ void __mem_cgroup_handle_over_high(gfp_t gfp_mask)static void memcg_enforce_high (struct mem_cgroup *memcg,unsigned int nr_pages,gfp_t gfp_mask) { +struct mem_cgroup *iter;bool allow_spinning =gfpflags_allow_spinning (gfp_mask); -bool use_worker = !in_task () || (current->flags &PF_KTHREAD ) || - !current->mm || !mm_match_cgroup (current->mm, memcg); +bool task_context =in_task (); +bool kthread = task_context && (current->flags &PF_KTHREAD ); +bool use_high_work = !task_context || kthread || !current->mm || + !mm_match_cgroup (current->mm, memcg);/* * Reclaim can charge memory, for example when zswap stores a page. * Don't queue high_work from a reclaiming task, or the * high_work worker could keep queuing itself. */ -if (in_task () && use_worker && (current->flags &PF_MEMALLOC )) +if (task_context && use_high_work && (current->flags &PF_MEMALLOC ))return ;/* -* If the hierarchy is above the normal consumption range, schedule -* reclaim on returning to userland. We can perform reclaim here -* if __GFP_RECLAIM but let's always punt for simplicity and so that -* GFP_KERNEL can consistently be used during reclaim. @memcg is -* not recorded as it most likely matches current's and won't -* change in the meantime. As high limit is checked again before -* reclaim, the cost of mismatch is negligible. +* Check the charged cgroup and its parents. Local charges normally defer +* reclaim until return to userspace so it can use GFP_KERNEL. Large +* pending overages may be handled synchronously below. The handler looks +* up current->mm and rechecks the limits. */ -do { +for (iter = memcg; iter; iter =parent_mem_cgroup (iter)) {bool mem_high, swap_high; - mem_high =page_counter_read (&memcg->memory) > -READ_ONCE (memcg->memory.high); - swap_high =page_counter_read (&memcg->swap) > -READ_ONCE (memcg->swap.high); - -/* -* Don't make an unrelated interrupted task handle this charge. -* Kernel threads may be working for other tasks, so let a worker -* reclaim instead. -* Remote charges need reclaim from their charged hierarchy. -*/ -if (use_worker) { -if (mem_high) { -if (allow_spinning) -schedule_work (&memcg->high_work); -else -irq_work_queue (&memcg->high_irq_work); -break ; - } -continue ; - } - -if (mem_high || swap_high) { -/* -* The allocating tasks in this cgroup will need to do -* reclaim or be throttled to prevent further growth -* of the memory or swap footprints. -* -* Target some best-effort fairness between the tasks, -* and distribute reclaim work and delay penalties -* based on how much each task is actually allocating. -*/ - current->memcg_nr_pages_over_high += nr_pages; -set_notify_resume (current); + mem_high =page_counter_read (&iter->memory) > +READ_ONCE (iter->memory.high); + swap_high = !use_high_work && + (page_counter_read (&iter->swap) >READ_ONCE (iter->swap.high)); +if (mem_high || swap_high)break ; - } - }while ((memcg =parent_mem_cgroup (memcg))); + } -/* Interrupts, kernel threads, and remote chargers stop here. */ -if (use_worker) +/* +* Use a worker for interrupts, kernel threads, and tasks whose mm +* does not match the charged hierarchy. +*/ +if (use_high_work) { +if (!iter) +return ; +if (allow_spinning) +schedule_work (&iter->high_work); +else +irq_work_queue (&iter->high_irq_work);return ; + } + +if (iter) { +/* +* Count the full batch so reclaim work and delay penalties scale +* with the pages charged by this task. +*/ + current->memcg_nr_pages_over_high += nr_pages; +set_notify_resume (current); + }/* -* Reclaim is set up above to be called from the userland -* return path. But also attempt synchronous reclaim to avoid -* excessive overrun while the task is still inside the -* kernel. If this is successful, the return path will see it -* when it rechecks the overage and simply bail out. +* Earlier charges may have left high overage pending even if this charge +* found no new overage. Handle it now if this charge can block, to limit +* overrun while the task remains in the kernel. */ if (current->memcg_nr_pages_over_high > MEMCG_CHARGE_BATCH && !(current->flags & PF_MEMALLOC) &&
Signature · 1 line
2.53.0-Meta
Re: [PATCH for-7.5 5/5] memcg: simplify high limit handling after a charge
On Sat 10-10-26 14:09:57, Shakeel Butt wrote:
[...]
Quoted 7 lines
mm/memcontrol.c +5
-2-/* Interrupts, kernel threads, and remote chargers stop here. */ -if (use_worker) +/* +* Use a worker for interrupts, kernel threads, and tasks whose mm +* does not match the charged hierarchy. +*/ +if (use_high_work) {
mm/memcontrol.c void __mem_cgroup_handle_over_high(gfp_t gfp_mask) · line 2752
Quoted 10 lines
mm/memcontrol.c +9
-0+if (!iter) +return ; +if (allow_spinning) +schedule_work (&iter->high_work); +else +irq_work_queue (&iter->high_irq_work);return ; + } + +if (iter) {
Would it be much simpler to bail out on iter==NULL (i.e. nothing over high limit) right after the loop?
Quoted 22 lines
mm/memcontrol.c +10
-5+/* +* Count the full batch so reclaim work and delay penalties scale +* with the pages charged by this task. +*/ + current->memcg_nr_pages_over_high += nr_pages; +set_notify_resume (current); + }/* -* Reclaim is set up above to be called from the userland -* return path. But also attempt synchronous reclaim to avoid -* excessive overrun while the task is still inside the -* kernel. If this is successful, the return path will see it -* when it rechecks the overage and simply bail out. +* Earlier charges may have left high overage pending even if this charge +* found no new overage. Handle it now if this charge can block, to limit +* overrun while the task remains in the kernel. */ if (current->memcg_nr_pages_over_high > MEMCG_CHARGE_BATCH && !(current->flags & PF_MEMALLOC) &&Signature · 1 line
2.53.0-Meta
Signature · 2 lines
Michal Hocko SUSE Labs
Shakeel Butt
maintainerRe: [PATCH for-7.5 5/5] memcg: simplify high limit handling after a charge
Quoted 14 lines · Michal Hocko
On Sat 10-10-26 14:09:57, Shakeel Butt wrote:
[...]Quoted 12 lines
mm/memcontrol.c +10
-2-/* Interrupts, kernel threads, and remote chargers stop here. */ -if (use_worker) +/* +* Use a worker for interrupts, kernel threads, and tasks whose mm +* does not match the charged hierarchy. +*/ +if (use_high_work) { +if (!iter) +return ; +if (allow_spinning) +schedule_work (&iter->high_work); +else
Quoted 9 lines
mm/memcontrol.c void __mem_cgroup_handle_over_high(gfp_t gfp_mask) · line 2752
Quoted 5 lines
mm/memcontrol.c +4
-0+irq_work_queue (&iter->high_irq_work);return ; + } + +if (iter) {Would it be much simpler to bail out on iter==NULL (i.e. nothing over high limit) right after the loop?
I want this patch to not change any behavior. If we return here when iter is NULL, we will skip a potential call to __mem_cgroup_handle_over_high() when current->memcg_nr_pages_over_high > MEMCG_CHARGE_BATCH. Though I agree that is very subtle and most probably unexpected. If we decide to remove it, we should do it in a separate patch. Actually I am working on a followup series where I plan to remove the sync enforcement of memory.high and put high enforcement on the code paths which can potentially do a lot of allocation within a single kernel entry.
Quoted 26 lines
Quoted 22 lines
mm/memcontrol.c +10
-5+/* +* Count the full batch so reclaim work and delay penalties scale +* with the pages charged by this task. +*/ + current->memcg_nr_pages_over_high += nr_pages; +set_notify_resume (current); + }/* -* Reclaim is set up above to be called from the userland -* return path. But also attempt synchronous reclaim to avoid -* excessive overrun while the task is still inside the -* kernel. If this is successful, the return path will see it -* when it rechecks the overage and simply bail out. +* Earlier charges may have left high overage pending even if this charge +* found no new overage. Handle it now if this charge can block, to limit +* overrun while the task remains in the kernel. */ if (current->memcg_nr_pages_over_high > MEMCG_CHARGE_BATCH && !(current->flags & PF_MEMALLOC) &&Signature · 1 line
2.53.0-MetaSignature · 2 lines
Michal Hocko SUSE Labs
Michal Hocko
Acked-byRe: [PATCH for-7.5 5/5] memcg: simplify high limit handling after a charge
Quoted 25 lines · Shakeel Butt
Quoted 14 lines · Michal Hocko
On Sat 10-10-26 14:09:57, Shakeel Butt wrote:
[...]Quoted 12 lines
mm/memcontrol.c +10
-2-/* Interrupts, kernel threads, and remote chargers stop here. */ -if (use_worker) +/* +* Use a worker for interrupts, kernel threads, and tasks whose mm +* does not match the charged hierarchy. +*/ +if (use_high_work) { +if (!iter) +return ; +if (allow_spinning) +schedule_work (&iter->high_work); +else Quoted 9 lines
mm/memcontrol.c void __mem_cgroup_handle_over_high(gfp_t gfp_mask) · line 2752
Quoted 5 lines
mm/memcontrol.c +4
-0+irq_work_queue (&iter->high_irq_work);return ; + } + +if (iter) {Would it be much simpler to bail out on iter==NULL (i.e. nothing over high limit) right after the loop?
Quoted 5 lines
I want this patch to not change any behavior. If we return here when iter is NULL, we will skip a potential call to
__mem_cgroup_handle_over_high()whencurrent->memcg_nr_pages_over_high>MEMCG_CHARGE_BATCH. Though I agree that is very subtle and most probably unexpected. If we decide to remove it, we should do it in a separate patch.
OK, makes sense.
Signature · 2 lines
Michal Hocko SUSE Labs