]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
hung_task: dump blocker task if it is not hung
authorMasami Hiramatsu (Google) <mhiramat@kernel.org>
Wed, 30 Jul 2025 22:11:54 +0000 (07:11 +0900)
committerAndrew Morton <akpm@linux-foundation.org>
Sun, 14 Sep 2025 00:32:43 +0000 (17:32 -0700)
Dump the lock blocker task if it is not hung because if the blocker task
is also hung, it should be dumped by the detector.  This will de-duplicate
the same stackdumps if the blocker task is also blocked by another task
(and hung).

Link: https://lkml.kernel.org/r/175391351423.688839.11917911323784986774.stgit@devnote2
Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
Suggested-by: Sergey Senozhatsky <senozhatsky@chromium.org>
Tested-by: Sergey Senozhatsky <senozhatsky@chromium.org>
Acked-by: Lance Yang <lance.yang@linux.dev>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
kernel/hung_task.c

index 8708a1205f82e3f798d3465a9d52bb5a4305557a..b2c1f14b8129fc90bc614c61e2cf7286e106f485 100644 (file)
@@ -95,9 +95,41 @@ static struct notifier_block panic_block = {
        .notifier_call = hung_task_panic,
 };
 
+static bool task_is_hung(struct task_struct *t, unsigned long timeout)
+{
+       unsigned long switch_count = t->nvcsw + t->nivcsw;
+       unsigned int state = READ_ONCE(t->__state);
+
+       /*
+        * skip the TASK_KILLABLE tasks -- these can be killed
+        * skip the TASK_IDLE tasks -- those are genuinely idle
+        * skip the TASK_FROZEN task -- it reasonably stops scheduling by freezer
+        */
+       if (!(state & TASK_UNINTERRUPTIBLE) ||
+           (state & (TASK_WAKEKILL | TASK_NOLOAD | TASK_FROZEN)))
+               return false;
+
+       /*
+        * When a freshly created task is scheduled once, changes its state to
+        * TASK_UNINTERRUPTIBLE without having ever been switched out once, it
+        * musn't be checked.
+        */
+       if (unlikely(!switch_count))
+               return false;
+
+       if (switch_count != t->last_switch_count) {
+               t->last_switch_count = switch_count;
+               t->last_switch_time = jiffies;
+               return false;
+       }
+       if (time_is_after_jiffies(t->last_switch_time + timeout * HZ))
+               return false;
+
+       return true;
+}
 
 #ifdef CONFIG_DETECT_HUNG_TASK_BLOCKER
-static void debug_show_blocker(struct task_struct *task)
+static void debug_show_blocker(struct task_struct *task, unsigned long timeout)
 {
        struct task_struct *g, *t;
        unsigned long owner, blocker, blocker_type;
@@ -174,41 +206,21 @@ static void debug_show_blocker(struct task_struct *task)
                               t->pid, rwsem_blocked_by);
                        break;
                }
-               sched_show_task(t);
+               /* Avoid duplicated task dump, skip if the task is also hung. */
+               if (!task_is_hung(t, timeout))
+                       sched_show_task(t);
                return;
        }
 }
 #else
-static inline void debug_show_blocker(struct task_struct *task)
+static inline void debug_show_blocker(struct task_struct *task, unsigned long timeout)
 {
 }
 #endif
 
 static void check_hung_task(struct task_struct *t, unsigned long timeout)
 {
-       unsigned long switch_count = t->nvcsw + t->nivcsw;
-
-       /*
-        * Ensure the task is not frozen.
-        * Also, skip vfork and any other user process that freezer should skip.
-        */
-       if (unlikely(READ_ONCE(t->__state) & TASK_FROZEN))
-               return;
-
-       /*
-        * When a freshly created task is scheduled once, changes its state to
-        * TASK_UNINTERRUPTIBLE without having ever been switched out once, it
-        * musn't be checked.
-        */
-       if (unlikely(!switch_count))
-               return;
-
-       if (switch_count != t->last_switch_count) {
-               t->last_switch_count = switch_count;
-               t->last_switch_time = jiffies;
-               return;
-       }
-       if (time_is_after_jiffies(t->last_switch_time + timeout * HZ))
+       if (!task_is_hung(t, timeout))
                return;
 
        /*
@@ -243,7 +255,7 @@ static void check_hung_task(struct task_struct *t, unsigned long timeout)
                pr_err("\"echo 0 > /proc/sys/kernel/hung_task_timeout_secs\""
                        " disables this message.\n");
                sched_show_task(t);
-               debug_show_blocker(t);
+               debug_show_blocker(t, timeout);
                hung_task_show_lock = true;
 
                if (sysctl_hung_task_all_cpu_backtrace)
@@ -299,7 +311,6 @@ static void check_hung_uninterruptible_tasks(unsigned long timeout)
        hung_task_show_lock = false;
        rcu_read_lock();
        for_each_process_thread(g, t) {
-               unsigned int state;
 
                if (!max_count--)
                        goto unlock;
@@ -308,15 +319,8 @@ static void check_hung_uninterruptible_tasks(unsigned long timeout)
                                goto unlock;
                        last_break = jiffies;
                }
-               /*
-                * skip the TASK_KILLABLE tasks -- these can be killed
-                * skip the TASK_IDLE tasks -- those are genuinely idle
-                */
-               state = READ_ONCE(t->__state);
-               if ((state & TASK_UNINTERRUPTIBLE) &&
-                   !(state & TASK_WAKEKILL) &&
-                   !(state & TASK_NOLOAD))
-                       check_hung_task(t, timeout);
+
+               check_hung_task(t, timeout);
        }
  unlock:
        rcu_read_unlock();