workqueue: annotate racy PWQ_STAT_CPU_TIME update in wq_worker_tick()

wq_worker_tick() bumps pwq->stats[PWQ_STAT_CPU_TIME] on every scheduler
tick before pool->lock is taken. For unbound workqueues the
pool_workqueue is shared by all workers of the pool across CPUs, so
concurrent ticks on different CPUs perform an unsynchronized 64-bit
read-modify-write on the same counter. KCSAN reports this as a
data-race:

  BUG: KCSAN: data-race in wq_worker_tick / wq_worker_tick

  read-write to 0xffff0004d6989500 of 8 bytes by interrupt on cpu 29:
   wq_worker_tick+0x70/0x418
   sched_tick+0x248/0x3a0
   update_process_times+0x200/0x260
   tick_nohz_handler+0x230/0x2f8
   __hrtimer_run_queues+0x1ec/0x6c8
   hrtimer_interrupt+0x174/0x4b8
   ...
  read-write to 0xffff0004d6989500 of 8 bytes by interrupt on cpu 24:
   wq_worker_tick+0x70/0x418
   sched_tick+0x248/0x3a0
   ...
  value changed: 0x000000000010a1d0 -> 0x000000000010a9a0

The counter is purely advisory, so an occasional lost update is
harmless, and every other stats[] update already runs under pool->lock.

Annotate the update with data_race().

Signed-off-by: Breno Leitao <leitao@debian.org>
Signed-off-by: Tejun Heo <tj@kernel.org>
This commit is contained in:
Breno Leitao
2026-07-02 07:54:10 -10:00
committed by Tejun Heo
parent e73c290bd7
commit ecf5aad9a4
+5 -1
View File
@@ -1535,7 +1535,11 @@ void wq_worker_tick(struct task_struct *task)
if (!pwq)
return;
pwq->stats[PWQ_STAT_CPU_TIME] += TICK_USEC;
/*
* @pwq is shared across CPUs for unbound wqs and this advisory stat is
* bumped outside pool->lock, so the update is intentionally racy.
*/
data_race(pwq->stats[PWQ_STAT_CPU_TIME] += TICK_USEC);
if (!wq_cpu_intensive_thresh_us)
return;