Skip to contentSkip to conversation

memcg, oom: move out_of_memory back to the charge path

PATCH In review Michal Hocko 2 participants 8 messages Jun 28, 2018 – Jul 3, 2018

Subsystems (4)
lore
Share & tools
lore

Michal Hocko

author maintainer
#0 lore raw

PATCH 1/1 memcg, oom: move out_of_memory back to the charge path

From: Michal Hocko <[email protected]>

3812c8c8f395 ("mm: memcg: do not trap chargers with full callstack on OOM") has changed the ENOMEM semantic of memcg charges. Rather than invoking the oom killer from the charging context it delays the oom killer to the page fault path (pagefault_out_of_memory). This in turn means that many users (e.g. slab or g-u-p) will get ENOMEM when the corresponding memcg hits the hard limit and the memcg is is OOM. This is behavior is inconsistent with !memcg case where the oom killer is invoked from the allocation context and the allocator keeps retrying until it succeeds.

The difference in the behavior is user visible. mmap(MAP_POPULATE) might result in not fully populated ranges while the mmap return code doesn't tell that to the userspace. Random syscalls might fail with ENOMEM etc.

The primary motivation of the different memcg oom semantic was the deadlock avoidance. Things have changed since then, though. We have an async oom teardown by the oom reaper now and so we do not have to rely on the victim to tear down its memory anymore. Therefore we can return to the original semantic as long as the memcg oom killer is not handed over to the users space.

There is still one thing to be careful about here though. If the oom killer is not able to make any forward progress - e.g. because there is no eligible task to kill - then we have to bail out of the charge path to prevent from same class of deadlocks. We have basically two options here. Either we fail the charge with ENOMEM or force the charge and allow overcharge. The first option has been considered more harmful than useful because rare inconsistencies in the ENOMEM behavior is hard to test for and error prone. Basically the same reason why the page allocator doesn't fail allocations under such conditions. The later might allow runaways but those should be really unlikely unless somebody misconfigures the system. E.g. allowing to migrate tasks away from the memcg to a different unlimited memcg with move_charge_at_immigrate disabled.

Changes since rfc v1
- s@memcg_may_oom@in_user_fault@ suggested by Johannes. It is much more clear what is the purpose of the flag now
- s@mem_cgroup_oom_enable@mem_cgroup_enter_user_fault@g s@mem_cgroup_oom_disable@mem_cgroup_exit_user_fault@g as per Johannes
- make oom_kill_disable an exceptional case because it should be rare and the normal oom handling a core of the function - per Johannes

---

Hi,
I've posted this as an RFC previously [1]. There was no fundamental disagreement so I've integrated all the suggested changes and tested it. mmap(MAP_POPULATE) hits the oom killer again rather than silently fails to populate the mapping on the hard limit excess. On the other hand g-u-p and other charge path keep the ENOMEM semantic when the memcg oom killer is disabled. All the forward progress guarantee relies on the oom reaper.

Unless there are objections I think this is ready to go to mmotm and ready for the next merge window

[1] http://lkml.kernel.org/r/[email protected]

 include/linux/memcontrol.h | 16 ++++----
 include/linux/sched.h      |  2 +-
 mm/memcontrol.c            | 75 ++++++++++++++++++++++++++++++--------
 mm/memory.c                |  4 +-
 4 files changed, 71 insertions(+), 26 deletions(-)
include/linux/memcontrol.h +8 -8
index 6c6fb116e925..5a69bb4026f6 100644
@@ -494,16 +494,16 @@ unsigned long mem_cgroup_get_max(struct mem_cgroup *memcg);
 void mem_cgroup_print_oom_info(struct mem_cgroup *memcg,
 				struct task_struct *p);
 
-static inline void mem_cgroup_oom_enable(void)
+static inline void mem_cgroup_enter_user_fault(void)
 {
-	WARN_ON(current->memcg_may_oom);
-	current->memcg_may_oom = 1;
+	WARN_ON(current->in_user_fault);
+	current->in_user_fault = 1;
 }
 
-static inline void mem_cgroup_oom_disable(void)
+static inline void mem_cgroup_exit_user_fault(void)
 {
-	WARN_ON(!current->memcg_may_oom);
-	current->memcg_may_oom = 0;
+	WARN_ON(!current->in_user_fault);
+	current->in_user_fault = 0;
 }
 
 static inline bool task_in_memcg_oom(struct task_struct *p)
@@ -924,11 +924,11 @@ static inline void mem_cgroup_handle_over_high(void)
 {
 }
 
-static inline void mem_cgroup_oom_enable(void)
+static inline void mem_cgroup_enter_user_fault(void)
 {
 }
 
-static inline void mem_cgroup_oom_disable(void)
+static inline void mem_cgroup_exit_user_fault(void)
 {
 }
 
include/linux/sched.h +1 -1
index 87bf02d93a27..34cc95b751cd 100644
@@ -722,7 +722,7 @@ struct task_struct {
 	unsigned			restore_sigmask:1;
 #endif
 #ifdef CONFIG_MEMCG
-	unsigned			memcg_may_oom:1;
+	unsigned			in_user_fault:1;
 #ifndef CONFIG_SLOB
 	unsigned			memcg_kmem_skip_account:1;
 #endif
mm/memcontrol.c +60 -15

108 lines not on this page · raw message

mm/memory.c +2 -2
index 7206a634270b..a4b1f8c24884 100644
@@ -4125,7 +4125,7 @@ int handle_mm_fault(struct vm_area_struct *vma, unsigned long address,
 	 * space.  Kernel faults are handled more gracefully.
 	 */
 	if (flags & FAULT_FLAG_USER)
-		mem_cgroup_oom_enable();
+		mem_cgroup_enter_user_fault();
 
 	if (unlikely(is_vm_hugetlb_page(vma)))
 		ret = hugetlb_fault(vma->vm_mm, vma, address, flags);
@@ -4133,7 +4133,7 @@ int handle_mm_fault(struct vm_area_struct *vma, unsigned long address,
 		ret = __handle_mm_fault(vma, address, flags);
 
 	if (flags & FAULT_FLAG_USER) {
-		mem_cgroup_oom_disable();
+		mem_cgroup_exit_user_fault();
 		/*
 		 * The task may have entered a memcg OOM situation but
 		 * if the allocation error was handled gracefully (no
Signature · 1 line
2.18.0
Acked-by on v1 1/1
#1 lore raw
Quoted 44 lines · Michal Hocko

From: Michal Hocko <[email protected]>

3812c8c8f395 ("mm: memcg: do not trap chargers with full callstack on OOM") has changed the ENOMEM semantic of memcg charges. Rather than invoking the oom killer from the charging context it delays the oom killer to the page fault path (pagefault_out_of_memory). This in turn means that many users (e.g. slab or g-u-p) will get ENOMEM when the corresponding memcg hits the hard limit and the memcg is is OOM. This is behavior is inconsistent with !memcg case where the oom killer is invoked from the allocation context and the allocator keeps retrying until it succeeds.

The difference in the behavior is user visible. mmap(MAP_POPULATE) might result in not fully populated ranges while the mmap return code doesn't tell that to the userspace. Random syscalls might fail with ENOMEM etc.

The primary motivation of the different memcg oom semantic was the deadlock avoidance. Things have changed since then, though. We have an async oom teardown by the oom reaper now and so we do not have to rely on the victim to tear down its memory anymore. Therefore we can return to the original semantic as long as the memcg oom killer is not handed over to the users space.

There is still one thing to be careful about here though. If the oom killer is not able to make any forward progress - e.g. because there is no eligible task to kill - then we have to bail out of the charge path to prevent from same class of deadlocks. We have basically two options here. Either we fail the charge with ENOMEM or force the charge and allow overcharge. The first option has been considered more harmful than useful because rare inconsistencies in the ENOMEM behavior is hard to test for and error prone. Basically the same reason why the page allocator doesn't fail allocations under such conditions. The later might allow runaways but those should be really unlikely unless somebody misconfigures the system. E.g. allowing to migrate tasks away from the memcg to a different unlimited memcg with move_charge_at_immigrate disabled.

Changes since rfc v1
- s@memcg_may_oom@in_user_fault@ suggested by Johannes. It is much more clear what is the purpose of the flag now
- s@mem_cgroup_oom_enable@mem_cgroup_enter_user_fault@g s@mem_cgroup_oom_disable@mem_cgroup_exit_user_fault@g as per Johannes
- make oom_kill_disable an exceptional case because it should be rare and the normal oom handling a core of the function - per Johannes

Quoted 1 line

Thanks! One comment below.

Quoted 133 lines

---

Hi,
I've posted this as an RFC previously [1]. There was no fundamental disagreement so I've integrated all the suggested changes and tested it. mmap(MAP_POPULATE) hits the oom killer again rather than silently fails to populate the mapping on the hard limit excess. On the other hand g-u-p and other charge path keep the ENOMEM semantic when the memcg oom killer is disabled. All the forward progress guarantee relies on the oom reaper.

Unless there are objections I think this is ready to go to mmotm and ready for the next merge window

[1] http://lkml.kernel.org/r/[email protected]

 include/linux/memcontrol.h | 16 ++++----
 include/linux/sched.h      |  2 +-
 mm/memcontrol.c            | 75 ++++++++++++++++++++++++++++++--------
 mm/memory.c                |  4 +-
 4 files changed, 71 insertions(+), 26 deletions(-)
include/linux/memcontrol.h +8 -8
index 6c6fb116e925..5a69bb4026f6 100644
@@ -494,16 +494,16 @@ unsigned long mem_cgroup_get_max(struct mem_cgroup *memcg);
 void mem_cgroup_print_oom_info(struct mem_cgroup *memcg,
 				struct task_struct *p);
 
-static inline void mem_cgroup_oom_enable(void)
+static inline void mem_cgroup_enter_user_fault(void)
 {
-	WARN_ON(current->memcg_may_oom);
-	current->memcg_may_oom = 1;
+	WARN_ON(current->in_user_fault);
+	current->in_user_fault = 1;
 }
 
-static inline void mem_cgroup_oom_disable(void)
+static inline void mem_cgroup_exit_user_fault(void)
 {
-	WARN_ON(!current->memcg_may_oom);
-	current->memcg_may_oom = 0;
+	WARN_ON(!current->in_user_fault);
+	current->in_user_fault = 0;
 }
 
 static inline bool task_in_memcg_oom(struct task_struct *p)
@@ -924,11 +924,11 @@ static inline void mem_cgroup_handle_over_high(void)
 {
 }
 
-static inline void mem_cgroup_oom_enable(void)
+static inline void mem_cgroup_enter_user_fault(void)
 {
 }
 
-static inline void mem_cgroup_oom_disable(void)
+static inline void mem_cgroup_exit_user_fault(void)
 {
 }
 
include/linux/sched.h +1 -1
index 87bf02d93a27..34cc95b751cd 100644
@@ -722,7 +722,7 @@ struct task_struct {
 	unsigned			restore_sigmask:1;
 #endif
 #ifdef CONFIG_MEMCG
-	unsigned			memcg_may_oom:1;
+	unsigned			in_user_fault:1;
 #ifndef CONFIG_SLOB
 	unsigned			memcg_kmem_skip_account:1;
 #endif
mm/memcontrol.c +28 -14
index e6f0d5ef320a..cff6c75137c1 100644
@@ -1483,28 +1483,53 @@ static void memcg_oom_recover(struct mem_cgroup *memcg)
 		__wake_up(&memcg_oom_waitq, TASK_NORMAL, 0, memcg);
 }
 
-static void mem_cgroup_oom(struct mem_cgroup *memcg, gfp_t mask, int order)
+enum oom_status {
+	OOM_SUCCESS,
+	OOM_FAILED,
+	OOM_ASYNC,
+	OOM_SKIPPED
+};
+
+static enum oom_status mem_cgroup_oom(struct mem_cgroup *memcg, gfp_t mask, int order)
 {
-	if (!current->memcg_may_oom || order > PAGE_ALLOC_COSTLY_ORDER)
-		return;
+	if (order > PAGE_ALLOC_COSTLY_ORDER)
+		return OOM_SKIPPED;
+
 	/*
 	 * We are in the middle of the charge context here, so we
 	 * don't want to block when potentially sitting on a callstack
 	 * that holds all kinds of filesystem and mm locks.
 	 *
-	 * Also, the caller may handle a failed allocation gracefully
-	 * (like optional page cache readahead) and so an OOM killer
-	 * invocation might not even be necessary.
+	 * cgroup1 allows disabling the OOM killer and waiting for outside
+	 * handling until the charge can succeed; remember the context and put
+	 * the task to sleep at the end of the page fault when all locks are
+	 * released.
+	 *
+	 * On the other hand, in-kernel OOM killer allows for an async victim
+	 * memory reclaim (oom_reaper) and that means that we are not solely
+	 * relying on the oom victim to make a forward progress and we can
+	 * invoke the oom killer here.
 	 *
-	 * That's why we don't do anything here except remember the
-	 * OOM context and then deal with it at the end of the page
-	 * fault when the stack is unwound, the locks are released,
-	 * and when we know whether the fault was overall successful.
+	 * Please note that mem_cgroup_out_of_memory might fail to find a
+	 * victim and then we have to bail out from the charge path.
 	 */
-	css_get(&memcg->css);
-	current->memcg_in_oom = memcg;
-	current->memcg_oom_gfp_mask = mask;
-	current->memcg_oom_order = order;
+	if (memcg->oom_kill_disable) {
+		if (!current->in_user_fault)
+			return OOM_SKIPPED;
+		css_get(&memcg->css);
+		current->memcg_in_oom = memcg;
+		current->memcg_oom_gfp_mask = mask;

mm/memcontrol.c static void memcg_oom_recover(struct mem_cgroup *memcg) · line 1531

Quoted 10 lines
mm/memcontrol.c +10 -0
+		current->memcg_oom_order = order;
+
+		return OOM_ASYNC;
+	}
+
+	if (mem_cgroup_out_of_memory(memcg, mask, order))
+		return OOM_SUCCESS;
+
+	WARN(1,"Memory cgroup charge failed because of no reclaimable memory! "
+		"This looks like a misconfiguration or a kernel bug.");

I'm not sure here if the warning should here or so strongly worded. It seems like the current task could be oom reaped with MMF_OOM_SKIP and thus mem_cgroup_out_of_memory() will return false. So there's nothing alarming in that case.

Quoted 70 lines
mm/memcontrol.c +22 -1
+	return OOM_FAILED;
 }
 
 /**
@@ -1899,6 +1924,8 @@ static int try_charge(struct mem_cgroup *memcg, gfp_t gfp_mask,
 	unsigned long nr_reclaimed;
 	bool may_swap = true;
 	bool drained = false;
+	bool oomed = false;
+	enum oom_status oom_status;
 
 	if (mem_cgroup_is_root(memcg))
 		return 0;
@@ -1986,6 +2013,9 @@ static int try_charge(struct mem_cgroup *memcg, gfp_t gfp_mask,
 	if (nr_retries--)
 		goto retry;
 
+	if (gfp_mask & __GFP_RETRY_MAYFAIL && oomed)
+		goto nomem;
+
 	if (gfp_mask & __GFP_NOFAIL)
 		goto force;
 
@@ -1994,8 +2024,23 @@ static int try_charge(struct mem_cgroup *memcg, gfp_t gfp_mask,
 
 	memcg_memory_event(mem_over_limit, MEMCG_OOM);
 
-	mem_cgroup_oom(mem_over_limit, gfp_mask,
+	/*
+	 * keep retrying as long as the memcg oom killer is able to make
+	 * a forward progress or bypass the charge if the oom killer
+	 * couldn't make any progress.
+	 */
+	oom_status = mem_cgroup_oom(mem_over_limit, gfp_mask,
 		       get_order(nr_pages * PAGE_SIZE));
+	switch (oom_status) {
+	case OOM_SUCCESS:
+		nr_retries = MEM_CGROUP_RECLAIM_RETRIES;
+		oomed = true;
+		goto retry;
+	case OOM_FAILED:
+		goto force;
+	default:
+		goto nomem;
+	}
 nomem:
 	if (!(gfp_mask & __GFP_NOFAIL))
 		return -ENOMEM;
mm/memory.c +2 -2
index 7206a634270b..a4b1f8c24884 100644
@@ -4125,7 +4125,7 @@ int handle_mm_fault(struct vm_area_struct *vma, unsigned long address,
 	 * space.  Kernel faults are handled more gracefully.
 	 */
 	if (flags & FAULT_FLAG_USER)
-		mem_cgroup_oom_enable();
+		mem_cgroup_enter_user_fault();
 
 	if (unlikely(is_vm_hugetlb_page(vma)))
 		ret = hugetlb_fault(vma->vm_mm, vma, address, flags);
@@ -4133,7 +4133,7 @@ int handle_mm_fault(struct vm_area_struct *vma, unsigned long address,
 		ret = __handle_mm_fault(vma, address, flags);
 
 	if (flags & FAULT_FLAG_USER) {
-		mem_cgroup_oom_disable();
+		mem_cgroup_exit_user_fault();
 		/*
 		 * The task may have entered a memcg OOM situation but
 		 * if the allocation error was handled gracefully (no
author maintainer
#2 lore raw
Quoted 1 line · Greg Thelen

Michal Hocko <[email protected]> wrote:

[...]

Quoted 10 lines

mm/memcontrol.c static void memcg_oom_recover(struct mem_cgroup *memcg) · line 1531

Quoted 5 lines
mm/memcontrol.c +5 -0
+	if (mem_cgroup_out_of_memory(memcg, mask, order))
+		return OOM_SUCCESS;
+
+	WARN(1,"Memory cgroup charge failed because of no reclaimable memory! "
+		"This looks like a misconfiguration or a kernel bug.");

I'm not sure here if the warning should here or so strongly worded. It seems like the current task could be oom reaped with MMF_OOM_SKIP and thus mem_cgroup_out_of_memory() will return false. So there's nothing alarming in that case.

If the task is reaped then its charges should be released as well and that means that we should get below the limit. Sure there is some room for races but this should be still unlikely. Maybe I am just underestimating though.

What would you suggest instead?

Signature · 2 lines
Michal Hocko
SUSE Labs
#3 lore raw
Quoted 14 lines · Michal Hocko
Quoted 1 line · Greg Thelen

Michal Hocko <[email protected]> wrote:

[...]

Quoted 10 lines

mm/memcontrol.c static void memcg_oom_recover(struct mem_cgroup *memcg) · line 1531

Quoted 5 lines
mm/memcontrol.c +5 -0
+	if (mem_cgroup_out_of_memory(memcg, mask, order))
+		return OOM_SUCCESS;
+
+	WARN(1,"Memory cgroup charge failed because of no reclaimable memory! "
+		"This looks like a misconfiguration or a kernel bug.");

I'm not sure here if the warning should here or so strongly worded. It seems like the current task could be oom reaped with MMF_OOM_SKIP and thus mem_cgroup_out_of_memory() will return false. So there's nothing alarming in that case.

Quoted 6 lines

If the task is reaped then its charges should be released as well and that means that we should get below the limit. Sure there is some room for races but this should be still unlikely. Maybe I am just underestimating though.

What would you suggest instead?

I suggest checking MMF_OOM_SKIP or deleting the warning. But I don't feel strongly.

author maintainer
#4 lore raw
Quoted 16 lines · Greg Thelen
Quoted 14 lines · Michal Hocko
Quoted 1 line · Greg Thelen

Michal Hocko <[email protected]> wrote:

[...]

Quoted 10 lines

mm/memcontrol.c static void memcg_oom_recover(struct mem_cgroup *memcg) · line 1531

Quoted 5 lines
mm/memcontrol.c +5 -0
+	if (mem_cgroup_out_of_memory(memcg, mask, order))
+		return OOM_SUCCESS;
+
+	WARN(1,"Memory cgroup charge failed because of no reclaimable memory! "
+		"This looks like a misconfiguration or a kernel bug.");

I'm not sure here if the warning should here or so strongly worded. It seems like the current task could be oom reaped with MMF_OOM_SKIP and thus mem_cgroup_out_of_memory() will return false. So there's nothing alarming in that case.

Quoted 8 lines
Quoted 6 lines

If the task is reaped then its charges should be released as well and that means that we should get below the limit. Sure there is some room for races but this should be still unlikely. Maybe I am just underestimating though.

What would you suggest instead?

I suggest checking MMF_OOM_SKIP or deleting the warning.

So what do you do when you have MMF_OOM_SKIP task? Do not warn? Checking for all the tasks would be quite expensive and remembering that from the task selection not nice either. Why do you think it would help much?

I feel strongly that we have to warn when bypassing the charge limit during the corner case because it can lead to unexpected behavior and users should be aware of this fact. I am open to the wording or some optimizations. I would prefer the latter on top with a clear description how it helped in a particular case though. I would rather not over optimize now without any story to back it.

Signature · 2 lines
Michal Hocko
SUSE Labs
#5 lore raw
Quoted 22 lines · Michal Hocko
Quoted 21 lines · Greg Thelen
Quoted 19 lines · Michal Hocko
Quoted 1 line · Greg Thelen

Michal Hocko <[email protected]> wrote:

[...]

Quoted 10 lines

mm/memcontrol.c static void memcg_oom_recover(struct mem_cgroup *memcg) · line 1531

Quoted 5 lines
mm/memcontrol.c +5 -0
+	if (mem_cgroup_out_of_memory(memcg, mask, order))
+		return OOM_SUCCESS;
+
+	WARN(1,"Memory cgroup charge failed because of no reclaimable memory! "
+		"This looks like a misconfiguration or a kernel bug.");

I'm not sure here if the warning should here or so strongly worded. It seems like the current task could be oom reaped with MMF_OOM_SKIP and thus mem_cgroup_out_of_memory() will return false. So there's nothing alarming in that case.

If the task is reaped then its charges should be released as well and that means that we should get below the limit. Sure there is some room for races but this should be still unlikely. Maybe I am just underestimating though.

Quoted 7 lines
Quoted 3 lines
Quoted 1 line

What would you suggest instead?

I suggest checking MMF_OOM_SKIP or deleting the warning.

So what do you do when you have MMF_OOM_SKIP task? Do not warn? Checking for all the tasks would be quite expensive and remembering that from the task selection not nice either. Why do you think it would help much?

I assume we could just check current's MMF_OOM_SKIP - no need to check all tasks. My only (minor) objection is that the warning text suggests misconfiguration or kernel bug, when there may be neither.

Quoted 6 lines

I feel strongly that we have to warn when bypassing the charge limit during the corner case because it can lead to unexpected behavior and users should be aware of this fact. I am open to the wording or some optimizations. I would prefer the latter on top with a clear description how it helped in a particular case though. I would rather not over optimize now without any story to back it.

I'm fine with the warning. I know enough to look at dmesg logs to take an educates that the race occurred. We can refine it later if/when the reports start rolling in. No change needed.

author maintainer
#6 lore raw
Quoted 26 lines · Greg Thelen
Quoted 24 lines · Michal Hocko
Quoted 23 lines · Greg Thelen
Quoted 20 lines · Michal Hocko
Quoted 1 line · Greg Thelen

Michal Hocko <[email protected]> wrote:

[...]

Quoted 10 lines

mm/memcontrol.c static void memcg_oom_recover(struct mem_cgroup *memcg) · line 1531

Quoted 5 lines
mm/memcontrol.c +5 -0
+	if (mem_cgroup_out_of_memory(memcg, mask, order))
+		return OOM_SUCCESS;
+
+	WARN(1,"Memory cgroup charge failed because of no reclaimable memory! "
+		"This looks like a misconfiguration or a kernel bug.");

I'm not sure here if the warning should here or so strongly worded. It seems like the current task could be oom reaped with MMF_OOM_SKIP and thus mem_cgroup_out_of_memory() will return false. So there's nothing alarming in that case.

If the task is reaped then its charges should be released as well and that means that we should get below the limit. Sure there is some room for races but this should be still unlikely. Maybe I am just underestimating though.

What would you suggest instead?

Quoted 8 lines
Quoted 5 lines
Quoted 1 line

I suggest checking MMF_OOM_SKIP or deleting the warning.

So what do you do when you have MMF_OOM_SKIP task? Do not warn? Checking for all the tasks would be quite expensive and remembering that from the task selection not nice either. Why do you think it would help much?

I assume we could just check current's MMF_OOM_SKIP - no need to check all tasks.

I still do not follow. If you are after a single task memcg then we should be ok. try_charge has a runaway for oom victims

	if (unlikely(tsk_is_oom_victim(current) ||
		     fatal_signal_pending(current) ||
		     current->flags & PF_EXITING))
		goto force;

regardless of MMF_OOM_SKIP. So if there is a single process in the memcg, we kill it and the oom reaper kicks in and sets MMF_OOM_SKIP then we should bail out there. Or do I miss your intention?

Quoted 10 lines

My only (minor) objection is that the warning text suggests misconfiguration or kernel bug, when there may be neither.

Quoted 6 lines

I feel strongly that we have to warn when bypassing the charge limit during the corner case because it can lead to unexpected behavior and users should be aware of this fact. I am open to the wording or some optimizations. I would prefer the latter on top with a clear description how it helped in a particular case though. I would rather not over optimize now without any story to back it.

Quoted 3 lines

I'm fine with the warning. I know enough to look at dmesg logs to take an educates that the race occurred. We can refine it later if/when the reports start rolling in. No change needed.

OK. Thanks!

Signature · 2 lines
Michal Hocko
SUSE Labs
#7 lore raw
Quoted 38 lines · Michal Hocko
Quoted 26 lines · Greg Thelen
Quoted 24 lines · Michal Hocko
Quoted 23 lines · Greg Thelen
Quoted 20 lines · Michal Hocko
Quoted 1 line · Greg Thelen

Michal Hocko <[email protected]> wrote:

[...]

Quoted 10 lines

Review

Quoted 5 lines
(quoted diff) +5 -0
+	if (mem_cgroup_out_of_memory(memcg, mask, order))
+		return OOM_SUCCESS;
+
+	WARN(1,"Memory cgroup charge failed because of no reclaimable memory! "
+		"This looks like a misconfiguration or a kernel bug.");

I'm not sure here if the warning should here or so strongly worded. It seems like the current task could be oom reaped with MMF_OOM_SKIP and thus mem_cgroup_out_of_memory() will return false. So there's nothing alarming in that case.

If the task is reaped then its charges should be released as well and that means that we should get below the limit. Sure there is some room for races but this should be still unlikely. Maybe I am just underestimating though.

What would you suggest instead?

Quoted 8 lines
Quoted 5 lines
Quoted 1 line

I suggest checking MMF_OOM_SKIP or deleting the warning.

So what do you do when you have MMF_OOM_SKIP task? Do not warn? Checking for all the tasks would be quite expensive and remembering that from the task selection not nice either. Why do you think it would help much?

I assume we could just check current's MMF_OOM_SKIP - no need to check all tasks.

I still do not follow. If you are after a single task memcg then we should be ok. try_charge has a runaway for oom victims

Quoted 8 lines
	if (unlikely(tsk_is_oom_victim(current) ||
		     fatal_signal_pending(current) ||
		     current->flags & PF_EXITING))
		goto force;

regardless of MMF_OOM_SKIP. So if there is a single process in the memcg, we kill it and the oom reaper kicks in and sets MMF_OOM_SKIP then we should bail out there. Or do I miss your intention?

For a single task memcg it seems that racing process cgroup migration could trigger the new warning (I have attempted to reproduce this):

Processes A,B in memcg M1,M2. M1 is oom.

Process A[M1] Process B[M2]

M1 is oom
try_charge(M1)

                              Move A M1=>M2

mem_cgroup_oom()
mem_cgroup_out_of_memory()
out_of_memory()
select_bad_process()
sees nothing in M1
return 0
return 0
WARN()

Another variant might be possible, this time with global oom:

Processes A,B in memcg M1,M2. M1 is oom.

Process A[M1] Process B[M2]

try_charge()
trigger global oom reaper sets A.MMF_OOM_SKIP
mem_cgroup_oom()
mem_cgroup_out_of_memory()
out_of_memory()
select_bad_process()
sees nothing in M1
return 0
return 0
WARN()

These seem unlikely, so I'm fine with taking a wait-and-see approach.