Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c32b3cbe authored by Michal Hocko's avatar Michal Hocko Committed by Linus Torvalds
Browse files

oom, PM: make OOM detection in the freezer path raceless



Commit 5695be14 ("OOM, PM: OOM killed task shouldn't escape PM
suspend") has left a race window when OOM killer manages to
note_oom_kill after freeze_processes checks the counter.  The race
window is quite small and really unlikely and partial solution deemed
sufficient at the time of submission.

Tejun wasn't happy about this partial solution though and insisted on a
full solution.  That requires the full OOM and freezer's task freezing
exclusion, though.  This is done by this patch which introduces oom_sem
RW lock and turns oom_killer_disable() into a full OOM barrier.

oom_killer_disabled check is moved from the allocation path to the OOM
level and we take oom_sem for reading for both the check and the whole
OOM invocation.

oom_killer_disable() takes oom_sem for writing so it waits for all
currently running OOM killer invocations.  Then it disable all the further
OOMs by setting oom_killer_disabled and checks for any oom victims.
Victims are counted via mark_tsk_oom_victim resp.  unmark_oom_victim.  The
last victim wakes up all waiters enqueued by oom_killer_disable().
Therefore this function acts as the full OOM barrier.

The page fault path is covered now as well although it was assumed to be
safe before.  As per Tejun, "We used to have freezing points deep in file
system code which may be reacheable from page fault." so it would be
better and more robust to not rely on freezing points here.  Same applies
to the memcg OOM killer.

out_of_memory tells the caller whether the OOM was allowed to trigger and
the callers are supposed to handle the situation.  The page allocation
path simply fails the allocation same as before.  The page fault path will
retry the fault (more on that later) and Sysrq OOM trigger will simply
complain to the log.

Normally there wouldn't be any unfrozen user tasks after
try_to_freeze_tasks so the function will not block. But if there was an
OOM killer racing with try_to_freeze_tasks and the OOM victim didn't
finish yet then we have to wait for it. This should complete in a finite
time, though, because

	- the victim cannot loop in the page fault handler (it would die
	  on the way out from the exception)
	- it cannot loop in the page allocator because all the further
	  allocation would fail and __GFP_NOFAIL allocations are not
	  acceptable at this stage
	- it shouldn't be blocked on any locks held by frozen tasks
	  (try_to_freeze expects lockless context) and kernel threads and
	  work queues are not frozen yet

Signed-off-by: default avatarMichal Hocko <mhocko@suse.cz>
Suggested-by: default avatarTejun Heo <tj@kernel.org>
Cc: David Rientjes <rientjes@google.com>
Cc: Johannes Weiner <hannes@cmpxchg.org>
Cc: Oleg Nesterov <oleg@redhat.com>
Cc: Cong Wang <xiyou.wangcong@gmail.com>
Cc: "Rafael J. Wysocki" <rjw@rjwysocki.net>
Signed-off-by: default avatarAndrew Morton <akpm@linux-foundation.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
parent 401e4a7c
Loading
Loading
Loading
Loading
+3 −2
Original line number Diff line number Diff line
@@ -355,8 +355,9 @@ static struct sysrq_key_op sysrq_term_op = {

static void moom_callback(struct work_struct *ignored)
{
	out_of_memory(node_zonelist(first_memory_node, GFP_KERNEL), GFP_KERNEL,
		      0, NULL, true);
	if (!out_of_memory(node_zonelist(first_memory_node, GFP_KERNEL),
			   GFP_KERNEL, 0, NULL, true))
		pr_info("OOM request ignored because killer is disabled\n");
}

static DECLARE_WORK(moom_work, moom_callback);
+3 −11
Original line number Diff line number Diff line
@@ -72,22 +72,14 @@ extern enum oom_scan_t oom_scan_process_thread(struct task_struct *task,
		unsigned long totalpages, const nodemask_t *nodemask,
		bool force_kill);

extern void out_of_memory(struct zonelist *zonelist, gfp_t gfp_mask,
extern bool out_of_memory(struct zonelist *zonelist, gfp_t gfp_mask,
		int order, nodemask_t *mask, bool force_kill);
extern int register_oom_notifier(struct notifier_block *nb);
extern int unregister_oom_notifier(struct notifier_block *nb);

extern bool oom_killer_disabled;

static inline void oom_killer_disable(void)
{
	oom_killer_disabled = true;
}

static inline void oom_killer_enable(void)
{
	oom_killer_disabled = false;
}
extern bool oom_killer_disable(void);
extern void oom_killer_enable(void);

extern struct task_struct *find_lock_task_mm(struct task_struct *p);

+2 −1
Original line number Diff line number Diff line
@@ -435,6 +435,7 @@ static void exit_mm(struct task_struct *tsk)
	task_unlock(tsk);
	mm_update_next_owner(mm);
	mmput(mm);
	if (test_thread_flag(TIF_MEMDIE))
		unmark_oom_victim();
}

+9 −41
Original line number Diff line number Diff line
@@ -108,30 +108,6 @@ static int try_to_freeze_tasks(bool user_only)
	return todo ? -EBUSY : 0;
}

static bool __check_frozen_processes(void)
{
	struct task_struct *g, *p;

	for_each_process_thread(g, p)
		if (p != current && !freezer_should_skip(p) && !frozen(p))
			return false;

	return true;
}

/*
 * Returns true if all freezable tasks (except for current) are frozen already
 */
static bool check_frozen_processes(void)
{
	bool ret;

	read_lock(&tasklist_lock);
	ret = __check_frozen_processes();
	read_unlock(&tasklist_lock);
	return ret;
}

/**
 * freeze_processes - Signal user space processes to enter the refrigerator.
 * The current thread will not be frozen.  The same process that calls
@@ -142,7 +118,6 @@ static bool check_frozen_processes(void)
int freeze_processes(void)
{
	int error;
	int oom_kills_saved;

	error = __usermodehelper_disable(UMH_FREEZING);
	if (error)
@@ -157,29 +132,22 @@ int freeze_processes(void)
	pm_wakeup_clear();
	pr_info("Freezing user space processes ... ");
	pm_freezing = true;
	oom_kills_saved = oom_kills_count();
	error = try_to_freeze_tasks(true);
	if (!error) {
		__usermodehelper_set_disable_depth(UMH_DISABLED);
		oom_killer_disable();

		/*
		 * There might have been an OOM kill while we were
		 * freezing tasks and the killed task might be still
		 * on the way out so we have to double check for race.
		 */
		if (oom_kills_count() != oom_kills_saved &&
		    !check_frozen_processes()) {
			__usermodehelper_set_disable_depth(UMH_ENABLED);
			pr_cont("OOM in progress.");
			error = -EBUSY;
		} else {
		pr_cont("done.");
	}
	}
	pr_cont("\n");
	BUG_ON(in_atomic());

	/*
	 * Now that the whole userspace is frozen we need to disbale
	 * the OOM killer to disallow any further interference with
	 * killable tasks.
	 */
	if (!error && !oom_killer_disable())
		error = -EBUSY;

	if (error)
		thaw_processes();
	return error;
+1 −1
Original line number Diff line number Diff line
@@ -1930,7 +1930,7 @@ bool mem_cgroup_oom_synchronize(bool handle)
	if (!memcg)
		return false;

	if (!handle)
	if (!handle || oom_killer_disabled)
		goto cleanup;

	owait.memcg = memcg;
Loading