Commit c40f7d74 authored by Linus Torvalds's avatar Linus Torvalds Committed by Ingo Molnar

sched/fair: Fix infinite loop in update_blocked_averages() by reverting a9e7f654

Zhipeng Xie, Xie XiuQi and Sargun Dhillon reported lockups in the
scheduler under high loads, starting at around the v4.18 time frame,
and Zhipeng Xie tracked it down to bugs in the rq->leaf_cfs_rq_list
manipulation.

Do a (manual) revert of:

  a9e7f654 ("sched/fair: Fix O(nr_cgroups) in load balance path")

It turns out that the list_del_leaf_cfs_rq() introduced by this commit
is a surprising property that was not considered in followup commits
such as:

  9c2791f9 ("sched/fair: Fix hierarchical order in rq->leaf_cfs_rq_list")

As Vincent Guittot explains:

 "I think that there is a bigger problem with commit a9e7f654 and
  cfs_rq throttling:

  Let take the example of the following topology TG2 --> TG1 --> root:

   1) The 1st time a task is enqueued, we will add TG2 cfs_rq then TG1
      cfs_rq to leaf_cfs_rq_list and we are sure to do the whole branch in
      one path because it has never been used and can't be throttled so
      tmp_alone_branch will point to leaf_cfs_rq_list at the end.

   2) Then TG1 is throttled

   3) and we add TG3 as a new child of TG1.

   4) The 1st enqueue of a task on TG3 will add TG3 cfs_rq just before TG1
      cfs_rq and tmp_alone_branch will stay  on rq->leaf_cfs_rq_list.

  With commit a9e7f654, we can del a cfs_rq from rq->leaf_cfs_rq_list.
  So if the load of TG1 cfs_rq becomes NULL before step 2) above, TG1
  cfs_rq is removed from the list.
  Then at step 4), TG3 cfs_rq is added at the beginning of rq->leaf_cfs_rq_list
  but tmp_alone_branch still points to TG3 cfs_rq because its throttled
  parent can't be enqueued when the lock is released.
  tmp_alone_branch doesn't point to rq->leaf_cfs_rq_list whereas it should.

  So if TG3 cfs_rq is removed or destroyed before tmp_alone_branch
  points on another TG cfs_rq, the next TG cfs_rq that will be added,
  will be linked outside rq->leaf_cfs_rq_list - which is bad.

  In addition, we can break the ordering of the cfs_rq in
  rq->leaf_cfs_rq_list but this ordering is used to update and
  propagate the update from leaf down to root."

Instead of trying to work through all these cases and trying to reproduce
the very high loads that produced the lockup to begin with, simplify
the code temporarily by reverting a9e7f654 - which change was clearly
not thought through completely.

This (hopefully) gives us a kernel that doesn't lock up so people
can continue to enjoy their holidays without worrying about regressions. ;-)

[ mingo: Wrote changelog, fixed weird spelling in code comment while at it. ]
Analyzed-by: default avatarXie XiuQi <xiexiuqi@huawei.com>
Analyzed-by: default avatarVincent Guittot <vincent.guittot@linaro.org>
Reported-by: default avatarZhipeng Xie <xiezhipeng1@huawei.com>
Reported-by: default avatarSargun Dhillon <sargun@sargun.me>
Reported-by: default avatarXie XiuQi <xiexiuqi@huawei.com>
Tested-by: default avatarZhipeng Xie <xiezhipeng1@huawei.com>
Tested-by: default avatarSargun Dhillon <sargun@sargun.me>
Signed-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
Acked-by: default avatarVincent Guittot <vincent.guittot@linaro.org>
Cc: <stable@vger.kernel.org> # v4.13+
Cc: Bin Li <huawei.libin@huawei.com>
Cc: Mike Galbraith <efault@gmx.de>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Tejun Heo <tj@kernel.org>
Cc: Thomas Gleixner <tglx@linutronix.de>
Fixes: a9e7f654 ("sched/fair: Fix O(nr_cgroups) in load balance path")
Link: http://lkml.kernel.org/r/1545879866-27809-1-git-send-email-xiexiuqi@huawei.comSigned-off-by: default avatarIngo Molnar <mingo@kernel.org>
parent 6d101ba6
...@@ -352,10 +352,9 @@ static inline void list_del_leaf_cfs_rq(struct cfs_rq *cfs_rq) ...@@ -352,10 +352,9 @@ static inline void list_del_leaf_cfs_rq(struct cfs_rq *cfs_rq)
} }
} }
/* Iterate thr' all leaf cfs_rq's on a runqueue */ /* Iterate through all leaf cfs_rq's on a runqueue: */
#define for_each_leaf_cfs_rq_safe(rq, cfs_rq, pos) \ #define for_each_leaf_cfs_rq(rq, cfs_rq) \
list_for_each_entry_safe(cfs_rq, pos, &rq->leaf_cfs_rq_list, \ list_for_each_entry_rcu(cfs_rq, &rq->leaf_cfs_rq_list, leaf_cfs_rq_list)
leaf_cfs_rq_list)
/* Do the two (enqueued) entities belong to the same group ? */ /* Do the two (enqueued) entities belong to the same group ? */
static inline struct cfs_rq * static inline struct cfs_rq *
...@@ -447,8 +446,8 @@ static inline void list_del_leaf_cfs_rq(struct cfs_rq *cfs_rq) ...@@ -447,8 +446,8 @@ static inline void list_del_leaf_cfs_rq(struct cfs_rq *cfs_rq)
{ {
} }
#define for_each_leaf_cfs_rq_safe(rq, cfs_rq, pos) \ #define for_each_leaf_cfs_rq(rq, cfs_rq) \
for (cfs_rq = &rq->cfs, pos = NULL; cfs_rq; cfs_rq = pos) for (cfs_rq = &rq->cfs; cfs_rq; cfs_rq = NULL)
static inline struct sched_entity *parent_entity(struct sched_entity *se) static inline struct sched_entity *parent_entity(struct sched_entity *se)
{ {
...@@ -7647,27 +7646,10 @@ static inline bool others_have_blocked(struct rq *rq) ...@@ -7647,27 +7646,10 @@ static inline bool others_have_blocked(struct rq *rq)
#ifdef CONFIG_FAIR_GROUP_SCHED #ifdef CONFIG_FAIR_GROUP_SCHED
static inline bool cfs_rq_is_decayed(struct cfs_rq *cfs_rq)
{
if (cfs_rq->load.weight)
return false;
if (cfs_rq->avg.load_sum)
return false;
if (cfs_rq->avg.util_sum)
return false;
if (cfs_rq->avg.runnable_load_sum)
return false;
return true;
}
static void update_blocked_averages(int cpu) static void update_blocked_averages(int cpu)
{ {
struct rq *rq = cpu_rq(cpu); struct rq *rq = cpu_rq(cpu);
struct cfs_rq *cfs_rq, *pos; struct cfs_rq *cfs_rq;
const struct sched_class *curr_class; const struct sched_class *curr_class;
struct rq_flags rf; struct rq_flags rf;
bool done = true; bool done = true;
...@@ -7679,7 +7661,7 @@ static void update_blocked_averages(int cpu) ...@@ -7679,7 +7661,7 @@ static void update_blocked_averages(int cpu)
* Iterates the task_group tree in a bottom up fashion, see * Iterates the task_group tree in a bottom up fashion, see
* list_add_leaf_cfs_rq() for details. * list_add_leaf_cfs_rq() for details.
*/ */
for_each_leaf_cfs_rq_safe(rq, cfs_rq, pos) { for_each_leaf_cfs_rq(rq, cfs_rq) {
struct sched_entity *se; struct sched_entity *se;
/* throttled entities do not contribute to load */ /* throttled entities do not contribute to load */
...@@ -7694,13 +7676,6 @@ static void update_blocked_averages(int cpu) ...@@ -7694,13 +7676,6 @@ static void update_blocked_averages(int cpu)
if (se && !skip_blocked_update(se)) if (se && !skip_blocked_update(se))
update_load_avg(cfs_rq_of(se), se, 0); update_load_avg(cfs_rq_of(se), se, 0);
/*
* There can be a lot of idle CPU cgroups. Don't let fully
* decayed cfs_rqs linger on the list.
*/
if (cfs_rq_is_decayed(cfs_rq))
list_del_leaf_cfs_rq(cfs_rq);
/* Don't need periodic decay once load/util_avg are null */ /* Don't need periodic decay once load/util_avg are null */
if (cfs_rq_has_blocked(cfs_rq)) if (cfs_rq_has_blocked(cfs_rq))
done = false; done = false;
...@@ -10570,10 +10545,10 @@ const struct sched_class fair_sched_class = { ...@@ -10570,10 +10545,10 @@ const struct sched_class fair_sched_class = {
#ifdef CONFIG_SCHED_DEBUG #ifdef CONFIG_SCHED_DEBUG
void print_cfs_stats(struct seq_file *m, int cpu) void print_cfs_stats(struct seq_file *m, int cpu)
{ {
struct cfs_rq *cfs_rq, *pos; struct cfs_rq *cfs_rq;
rcu_read_lock(); rcu_read_lock();
for_each_leaf_cfs_rq_safe(cpu_rq(cpu), cfs_rq, pos) for_each_leaf_cfs_rq(cpu_rq(cpu), cfs_rq)
print_cfs_rq(m, cpu, cfs_rq); print_cfs_rq(m, cpu, cfs_rq);
rcu_read_unlock(); rcu_read_unlock();
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment