Re: [PATCH v8 0/2] hung_task: Improve warning budget handling and task reporting

Aaron Tomlin <[email protected]>
Newsgroups gmane.linux.kernel
Message-ID <swjuv2n3sqdzk5jz3wrlbpxfczxgplsa66tctgc5unij774sjb@ydts263nwz3x>
On Fri, Aug 07, 2026 at 12:40:27PM +0800, Lance Yang wrote:
> >Yes; READ_ONCE() and WRITE_ONCE() guarantee single-copy load/store
> >atomicity to prevent compiler optimisations. However, I agree, they do not
> >establish a critical section or serialise multi-step operations across
> >variables 'sysctl_hung_task_warnings' and 'hung_task_warnings_printed'.
> >How about the following?
> 
> Still races with khungtaskd ... It can read 10, sysctl resets budget
> to 0, then khungtaskd writes 9 back. We end up with sysctl at 0 and
> runtime budget at 9 :)

Yes, hung_task_sysctl_mutex does not fix the race because khungtaskd
operates outside the mutex; hung_task_info() is called inside
check_hung_uninterruptible_tasks() while holding rcu_read_lock().

> Let's keep it simpler: make khungtaskd sole owner of
> hung_task_warnings_printed. Sysctl write just sets a reset flag:
> 
> static atomic_t reset_hung_task_warnings = ATOMIC_INIT(0);
> 
> ...
> 
> static int proc_dohung_task_warnings(const struct ctl_table *table, int write,
> ...
> {	ret = proc_dointvec_minmax(table, write, buffer, lenp, ppos);
> 	if (!ret && write)
> 		atomic_set_release(&reset_hung_task_warnings, 1);
> ...
> }
> 
> khungtaskd picks it up next scan:
> 
> 	if (atomic_xchg(&reset_hung_task_warnings, 0))
> 		hung_task_warnings_printed =
> 			READ_ONCE(sysctl_hung_task_warnings);
> 
> 
> That's it. Only khungtaskd touches runtime budget, so no lock needed
> there. Write during a scan takes effect next scan, keeping whole scan
> on one budget. Multiple writes collapse into one reset, and writing
> same value still refills budget.

This looks good!

> diff --git a/kernel/hung_task.c b/kernel/hung_task.c
> index 6ebb3a87ac65..ea7817e1856b 100644
> --- a/kernel/hung_task.c
> +++ b/kernel/hung_task.c
> @@ -60,6 +60,7 @@ static unsigned long __read_mostly sysctl_hung_task_check_interval_secs;
>  static int __read_mostly sysctl_hung_task_warnings = 10;
> 
>  static int hung_task_warnings_printed = 10;
> +static atomic_t reset_hung_task_warnings = ATOMIC_INIT(0);
> 
>  static int __read_mostly did_panic;
>  static bool hung_task_call_panic;
> @@ -244,11 +245,6 @@ static void hung_task_info(struct task_struct *t, unsigned long timeout,
>  		hung_task_call_panic = true;
>  	}
> 
> -	/* Always print the blocked message */
> -	pr_err("INFO: task %s:%d blocked%s for more than %ld seconds.\n",
> -	       t->comm, t->pid, t->in_iowait ? " in I/O wait" : "",
> -	       (jiffies - t->last_switch_time) / HZ);
> -
>  	/*
>  	 * The given task did not get scheduled for more than
>  	 * CONFIG_DEFAULT_HUNG_TASK_TIMEOUT. Therefore, complain
> @@ -257,6 +253,10 @@ static void hung_task_info(struct task_struct *t, unsigned long timeout,
>  	if (hung_task_warnings_printed || hung_task_call_panic) {
>  		if (hung_task_warnings_printed > 0)
>  			hung_task_warnings_printed--;
> +		pr_err("INFO: task %s:%d blocked%s for more than %ld seconds.\n",
> +		       t->comm, t->pid,
> +		       t->in_iowait ? " in I/O wait" : "",
> +		       (jiffies - t->last_switch_time) / HZ);
>  		pr_err("      %s %s %.*s\n",
>  			print_tainted(), init_utsname()->release,
>  			(int)strcspn(init_utsname()->version, " "),
> @@ -308,7 +308,7 @@ static void check_hung_uninterruptible_tasks(unsigned long timeout)
>  	unsigned long last_break = jiffies;
>  	struct task_struct *g, *t;
>  	unsigned long this_round_count;
> -	int need_warning = hung_task_warnings_printed;
> +	int need_warning;
>  	unsigned long si_mask = hung_task_si_mask;
> 
>  	/*
> @@ -318,6 +318,11 @@ static void check_hung_uninterruptible_tasks(unsigned long timeout)
>  	if (test_taint(TAINT_DIE) || did_panic)
>  		return;
> 
> +	if (atomic_xchg(&reset_hung_task_warnings, 0))
> +		hung_task_warnings_printed =
> +			READ_ONCE(sysctl_hung_task_warnings);
> +	need_warning = hung_task_warnings_printed;
> +
>  	this_round_count = 0;
>  	rcu_read_lock();
>  	for_each_process_thread(g, t) {
> @@ -345,10 +350,15 @@ static void check_hung_uninterruptible_tasks(unsigned long timeout)
>  	rcu_read_unlock();
> 
>  	if (!this_round_count) {
> -		hung_task_warnings_printed = sysctl_hung_task_warnings;
> +		hung_task_warnings_printed =
> +			READ_ONCE(sysctl_hung_task_warnings);
>  		return;
>  	}
> 
> +	if (!hung_task_warnings_printed && !hung_task_call_panic)
> +		pr_info("khungtaskd: %lu hung tasks detected (warning budget exhausted)\n",
> +			this_round_count);
> +
>  	if (need_warning || hung_task_call_panic) {
>  		si_mask |= SYS_INFO_LOCKS;
> 
> @@ -438,13 +448,10 @@ static int proc_dohung_task_warnings(const struct ctl_table *table, int write,
>  	int ret;
> 
>  	ret = proc_dointvec_minmax(table, write, buffer, lenp, ppos);
> +	if (!ret && write)
> +		atomic_set_release(&reset_hung_task_warnings, 1);
> 
> -	if (ret || !write)
> -		return ret;
> -
> -	hung_task_warnings_printed = sysctl_hung_task_warnings;
> -
> -	return 0;
> +	return ret;
>  }
> 
>  /*
> ---
> 

I like this implementation. Also the self-healing: if !this_round_count
triggers an early return, hung_task_warnings_printed is set to
READ_ONCE(sysctl_hung_task_warnings). Even if reset_hung_task_warnings
remained 1 due to a concurrent write during the scan, atomic_xchg() will
simply absorb it on the next round without any ill side effects.

I will incorporate these changes.


Kind regards,
-- 
Aaron Tomlin
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.