mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH] taskstats: Avoid counting an exiting thread twice in TGID queries
@ 2026-10-31  5:24 Baek Kyu, Kim
  2026-10-31 15:23 ` Bradley Morgan
  2026-11-01 14:26 ` Oleg Nesterov
  0 siblings, 2 replies; 4+ messages in thread
From: Baek Kyu, Kim @ 2026-10-31  5:24 UTC (permalink / raw)
  To: Balbir Singh; +Cc: Baek Kyu, Kim, Andrew Morton, Oleg Nesterov, linux-kernel

fill_tgid_exit() adds a thread's contribution to signal->stats before
exit_notify() sets exit_state. A TGID query in that interval copies the
cached contribution, then scans the same thread because exit_state is
still clear. The thread's contribution is counted twice.

Mark a task when its contribution reaches the cache, under the same
siglock used by the TGID query. Exclude marked tasks from the live scan.
Leave a task eligible for the scan if no cache exists, and clear the
marker when a new task is copied during fork.

Fixes: ad4ecbcba728 ("[PATCH] delay accounting taskstats interface send tgid once")
Signed-off-by: Baek Kyu, Kim <pocj8ur4in@gmail.com>
---
In an arm64 QEMU guest, holding the interval after cache accounting but
before exit_state changed made a 100 ms worker appear twice in a TGID CPU
query. The baseline reported 208000 us for 8000 us of the querying thread
plus 100000 us of the worker; the change reported 112000 us for 12000 us
plus 100000 us. Natural race frequency and other architectures were not
measured; KASAN and KCSAN were not enabled.

 include/linux/sched.h | 5 +++++
 kernel/fork.c         | 3 +++
 kernel/taskstats.c    | 7 ++++---
 3 files changed, 12 insertions(+), 3 deletions(-)

diff --git a/include/linux/sched.h b/include/linux/sched.h
index d35ae49a991f..359ee982323c 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -1457,6 +1457,11 @@ struct task_struct {
 	struct task_delay_info		*delays;
 #endif
 
+#ifdef CONFIG_TASKSTATS
+	/* Protected by sighand->siglock. */
+	bool				taskstats_tgid_accounted;
+#endif
+
 #ifdef CONFIG_FAULT_INJECTION
 	int				make_it_fail;
 	unsigned int			fail_nth;
diff --git a/kernel/fork.c b/kernel/fork.c
index 33760ece3bb3..76d9abcc5ca8 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -2202,6 +2202,9 @@ __latent_entropy struct task_struct *copy_process(
 		goto bad_fork_cleanup_count;
 
 	delayacct_tsk_init(p);	/* Must remain after dup_task_struct() */
+#ifdef CONFIG_TASKSTATS
+	p->taskstats_tgid_accounted = false;
+#endif
 	p->flags &= ~(PF_SUPERPRIV | PF_WQ_WORKER | PF_IDLE | PF_NO_SETAFFINITY);
 	p->flags |= PF_FORKNOEXEC;
 	INIT_LIST_HEAD(&p->children);
diff --git a/kernel/taskstats.c b/kernel/taskstats.c
index f31df72f0e9d..dd9054de761f 100644
--- a/kernel/taskstats.c
+++ b/kernel/taskstats.c
@@ -245,8 +245,8 @@ static int fill_stats_for_tgid(pid_t tgid, struct taskstats *stats)
 	u64 now_ns;
 
 	/*
-	 * Add additional stats from live tasks except zombie thread group
-	 * leaders who are already counted with the dead tasks
+	 * Add stats from tasks not yet included in the dead-task cache.
+	 * Accounting can finish before exit_state is set.
 	 */
 	rcu_read_lock();
 	first = find_task_by_vpid(tgid);
@@ -261,7 +261,7 @@ static int fill_stats_for_tgid(pid_t tgid, struct taskstats *stats)
 
 	now_ns = ktime_get_ns();
 	for_each_thread(first, tsk) {
-		if (tsk->exit_state)
+		if (tsk->exit_state || tsk->taskstats_tgid_accounted)
 			continue;
 
 		tgid_stats_add_task(stats, tsk, now_ns);
@@ -291,6 +291,7 @@ static void fill_tgid_exit(struct task_struct *tsk)
 
 	now_ns = ktime_get_ns();
 	tgid_stats_add_task(tsk->signal->stats, tsk, now_ns);
+	tsk->taskstats_tgid_accounted = true;
 ret:
 	spin_unlock_irqrestore(&tsk->sighand->siglock, flags);
 	return;
-- 
2.55.0

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH] taskstats: Avoid counting an exiting thread twice in TGID queries
  2026-10-31  5:24 [PATCH] taskstats: Avoid counting an exiting thread twice in TGID queries Baek Kyu, Kim
@ 2026-10-31 15:23 ` Bradley Morgan
  2026-11-01 14:26 ` Oleg Nesterov
  1 sibling, 0 replies; 4+ messages in thread
From: Bradley Morgan @ 2026-10-31 15:23 UTC (permalink / raw)
  To: pocj8ur4in; +Cc: akpm, bsingharora, linux-kernel, oleg

On 31 October 2026 05:24:51 GMT, "Baek Kyu, Kim" <pocj8ur4in@gmail.com>
wrote:
>fill_tgid_exit() adds a thread's contribution to signal->stats before
>exit_notify() sets exit_state. A TGID query in that interval copies the
>cached contribution, then scans the same thread because exit_state is
>still clear. The thread's contribution is counted twice.
>
>Mark a task when its contribution reaches the cache, under the same
>siglock used by the TGID query. Exclude marked tasks from the live scan.
>Leave a task eligible for the scan if no cache exists, and clear the
>marker when a new task is copied during fork.

This part of the changelog would need to go, see below.

>
>Fixes: ad4ecbcba728 ("[PATCH] delay accounting taskstats interface send tgid once")

Thx, Oleg already suggested this kinda thing

Link: https://lore.kernel.org/all/asaKCo5lwB3nzWNM@redhat.com/ would be good. Or closes?

>Signed-off-by: Baek Kyu, Kim <pocj8ur4in@gmail.com>
>---
>In an arm64 QEMU guest, holding the interval after cache accounting but
>before exit_state changed made a 100 ms worker appear twice in a TGID CPU
>query. The baseline reported 208000 us for 8000 us of the querying thread
>plus 100000 us of the worker; the change reported 112000 us for 12000 us
>plus 100000 us. Natural race frequency and other architectures were not
>measured; KASAN and KCSAN were not enabled.
>
> include/linux/sched.h | 5 +++++
> kernel/fork.c         | 3 +++
> kernel/taskstats.c    | 7 ++++---
> 3 files changed, 12 insertions(+), 3 deletions(-)
>
>diff --git a/include/linux/sched.h b/include/linux/sched.h
>index d35ae49a991f..359ee982323c 100644
>--- a/include/linux/sched.h
>+++ b/include/linux/sched.h
>@@ -1457,6 +1457,11 @@ struct task_struct {
> 	struct task_delay_info		*delays;
> #endif
> 
>+#ifdef CONFIG_TASKSTATS
>+	/* Protected by sighand->siglock. */
>+	bool				taskstats_tgid_accounted;
>+#endif
>+
> #ifdef CONFIG_FAULT_INJECTION
> 	int				make_it_fail;
> 	unsigned int			fail_nth;
>diff --git a/kernel/fork.c b/kernel/fork.c
>index 33760ece3bb3..76d9abcc5ca8 100644
>--- a/kernel/fork.c
>+++ b/kernel/fork.c
>@@ -2202,6 +2202,9 @@ __latent_entropy struct task_struct *copy_process(
> 		goto bad_fork_cleanup_count;
> 
> 	delayacct_tsk_init(p);	/* Must remain after dup_task_struct() */
>+#ifdef CONFIG_TASKSTATS
>+	p->taskstats_tgid_accounted = false;
>+#endif

Id like to pray that this is dead, and I'm not just being a idiot.

The flag is set in fill_tgid_exit() and the task that already is past
taskstats_exit() in do_exit, it can't call fork() again, dup_task_struct()
copies from current, so I'd reckon that this is useless.

> 	p->flags &= ~(PF_SUPERPRIV | PF_WQ_WORKER | PF_IDLE | PF_NO_SETAFFINITY);
> 	p->flags |= PF_FORKNOEXEC;
> 	INIT_LIST_HEAD(&p->children);
>diff --git a/kernel/taskstats.c b/kernel/taskstats.c
>index f31df72f0e9d..dd9054de761f 100644
>--- a/kernel/taskstats.c
>+++ b/kernel/taskstats.c
>@@ -245,8 +245,8 @@ static int fill_stats_for_tgid(pid_t tgid, struct taskstats *stats)
> 	u64 now_ns;
> 
> 	/*
>-	 * Add additional stats from live tasks except zombie thread group
>-	 * leaders who are already counted with the dead tasks
>+	 * Add stats from tasks not yet included in the dead-task cache.
>+	 * Accounting can finish before exit_state is set.
> 	 */
> 	rcu_read_lock();
> 	first = find_task_by_vpid(tgid);
>@@ -261,7 +261,7 @@ static int fill_stats_for_tgid(pid_t tgid, struct taskstats *stats)
> 
> 	now_ns = ktime_get_ns();
> 	for_each_thread(first, tsk) {
>-		if (tsk->exit_state)
>+		if (tsk->exit_state || tsk->taskstats_tgid_accounted)
> 			continue;
> 
> 		tgid_stats_add_task(stats, tsk, now_ns);
>@@ -291,6 +291,7 @@ static void fill_tgid_exit(struct task_struct *tsk)
> 
> 	now_ns = ktime_get_ns();
> 	tgid_stats_add_task(tsk->signal->stats, tsk, now_ns);
>+	tsk->taskstats_tgid_accounted = true;
> ret:
> 	spin_unlock_irqrestore(&tsk->sighand->siglock, flags);
> 	return;
>


Btw: you've sent 8 patches today, please stop spamming please.

--- Thanks!

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH] taskstats: Avoid counting an exiting thread twice in TGID queries
  2026-10-31  5:24 [PATCH] taskstats: Avoid counting an exiting thread twice in TGID queries Baek Kyu, Kim
  2026-10-31 15:23 ` Bradley Morgan
@ 2026-11-01 14:26 ` Oleg Nesterov
  2026-11-01 14:53   ` Bradley Morgan
  1 sibling, 1 reply; 4+ messages in thread
From: Oleg Nesterov @ 2026-11-01 14:26 UTC (permalink / raw)
  To: Baek Kyu, Kim; +Cc: Balbir Singh, Andrew Morton, linux-kernel, Bradley Morgan

On 10/31, Baek Kyu, Kim wrote:
>
> fill_tgid_exit() adds a thread's contribution to signal->stats before
> exit_notify() sets exit_state. A TGID query in that interval copies the
> cached contribution, then scans the same thread because exit_state is
> still clear. The thread's contribution is counted twice.

I forgot everything about this code, quite possibly I am wrong but let
me add some comments anyway...

Yes, known problem. Plus afaics fill_stats_for_tgid() can also miss an
exiting task, see below.

Another problem is that taskstats_exit(group_exit => true) can miss other
threads which have not called taskstats_exit() yet and report TGID/AGROUP.
Afaics this patch doesn't try to fix this.

> --- a/kernel/fork.c
> +++ b/kernel/fork.c
> @@ -2202,6 +2202,9 @@ __latent_entropy struct task_struct *copy_process(
>  		goto bad_fork_cleanup_count;
>
>  	delayacct_tsk_init(p);	/* Must remain after dup_task_struct() */
> +#ifdef CONFIG_TASKSTATS
> +	p->taskstats_tgid_accounted = false;
> +#endif

I agree with Bradley, this looks unnecessary.

> @@ -261,7 +261,7 @@ static int fill_stats_for_tgid(pid_t tgid, struct taskstats *stats)
>
>  	now_ns = ktime_get_ns();
>  	for_each_thread(first, tsk) {
> -		if (tsk->exit_state)
> +		if (tsk->exit_state || tsk->taskstats_tgid_accounted)
>  			continue;

If we add taskstats_tgid_accounted, why do we need to check exit_state?

In the multithreaded case, if taskstats_tgid_accounted is true, then
exit_state should be true too (ignoring the kmem_cache_zalloc() failures).

In the single threaded case this looks simply wrong. The exiting single
threaded task will not call fill_tgid_exit(). If cmd_attr_tgid() is called
after exit_notify(), it will miss that zombie.


And... Perhaps it would be better to use ->atomic_flags rather than add
yet another member to task_struct?

Oleg.


^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH] taskstats: Avoid counting an exiting thread twice in TGID queries
  2026-11-01 14:26 ` Oleg Nesterov
@ 2026-11-01 14:53   ` Bradley Morgan
  0 siblings, 0 replies; 4+ messages in thread
From: Bradley Morgan @ 2026-11-01 14:53 UTC (permalink / raw)
  To: Oleg Nesterov, Baek Kyu, Kim; +Cc: Balbir Singh, Andrew Morton, linux-kernel

On 1 November 2026 14:26:51 GMT, Oleg Nesterov <oleg@redhat.com> wrote:
>On 10/31, Baek Kyu, Kim wrote:
>>
>> fill_tgid_exit() adds a thread's contribution to signal->stats before
>> exit_notify() sets exit_state. A TGID query in that interval copies the
>> cached contribution, then scans the same thread because exit_state is
>> still clear. The thread's contribution is counted twice.
>
>I forgot everything about this code, quite possibly I am wrong but let
>me add some comments anyway...
>
>Yes, known problem. Plus afaics fill_stats_for_tgid() can also miss an
>exiting task, see below.
>
>Another problem is that taskstats_exit(group_exit => true) can miss other
>threads which have not called taskstats_exit() yet and report TGID/AGROUP.
>Afaics this patch doesn't try to fix this.
>
>> --- a/kernel/fork.c
>> +++ b/kernel/fork.c
>> @@ -2202,6 +2202,9 @@ __latent_entropy struct task_struct *copy_process(
>>  		goto bad_fork_cleanup_count;
>>
>>  	delayacct_tsk_init(p);	/* Must remain after dup_task_struct() */
>> +#ifdef CONFIG_TASKSTATS
>> +	p->taskstats_tgid_accounted = false;
>> +#endif
>
>I agree with Bradley, this looks unnecessary.
>

I checked, it is dead code, it IS unnecessary (impossible to reach)

>> @@ -261,7 +261,7 @@ static int fill_stats_for_tgid(pid_t tgid, struct
>taskstats *stats)
>>
>>  	now_ns = ktime_get_ns();
>>  	for_each_thread(first, tsk) {
>> -		if (tsk->exit_state)
>> +		if (tsk->exit_state || tsk->taskstats_tgid_accounted)
>>  			continue;
>
>If we add taskstats_tgid_accounted, why do we need to check exit_state?
>
>In the multithreaded case, if taskstats_tgid_accounted is true, then
>exit_state should be true too (ignoring the kmem_cache_zalloc() failures).
>
>In the single threaded case this looks simply wrong. The exiting single
>threaded task will not call fill_tgid_exit(). If cmd_attr_tgid() is called
>after exit_notify(), it will miss that zombie.
>
>
>And... Perhaps it would be better to use ->atomic_flags rather than add
>yet another member to task_struct?
>
>Oleg.
>

--- Thanks!

^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2026-11-01 14:53 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-31  5:24 [PATCH] taskstats: Avoid counting an exiting thread twice in TGID queries Baek Kyu, Kim
2026-10-31 15:23 ` Bradley Morgan
2026-11-01 14:26 ` Oleg Nesterov
2026-11-01 14:53   ` Bradley Morgan

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®