hrtimers: Force migrate away hrtimers queued after CPUHP_AP_HRTIMERS_DYING
Subsystems (6)
Frederic Weisbecker
maintainerPATCH v4 1/1 hrtimers: Force migrate away hrtimers queued after CPUHP_AP_HRTIMERS_DYING
hrtimers are migrated away from the dying CPU to any online target at the CPUHP_AP_HRTIMERS_DYING stage in order not to delay bandwidth timers handling tasks involved in the CPU hotplug forward progress.
However wake ups can still be performed by the outgoing CPU after CPUHP_AP_HRTIMERS_DYING. Those can result again in bandwidth timers being armed. Depending on several considerations (crystal ball power management based election, earliest timer already enqueued, timer migration enabled or not), the target may eventually be the current CPU even if offline. If that happens, the timer is eventually ignored.
The most notable example is RCU which had to deal with each and every of those wake-ups by deferring them to an online CPU, along with related workarounds:
_ e787644caf76 (rcu: Defer RCU kthreads wakeup when CPU is dying) _ 9139f93209d1 (rcu/nocb: Fix RT throttling hrtimer armed from offline CPU) _ f7345ccc62a4 (rcu/nocb: Fix rcuog wake-up from offline softirq)
The problem isn't confined to RCU though as the stop machine kthread (which runs CPUHP_AP_HRTIMERS_DYING) reports its completion at the end of its work through cpu_stop_signal_done() and performs a wake up that eventually arms the deadline server timer:
WARNING: CPU: 94 PID: 588 at kernel/time/hrtimer.c:1086 hrtimer_start_range_ns+0x289/0x2d0
CPU: 94 UID: 0 PID: 588 Comm: migration/94 Not tainted
Stopper: multi_cpu_stop+0x0/0x120 <- stop_machine_cpuslocked+0x66/0xc0
RIP: 0010:hrtimer_start_range_ns+0x289/0x2d0
Call Trace:
<TASK>
? hrtimer_start_range_ns
start_dl_timer
enqueue_dl_entity
dl_server_start
enqueue_task_fair
enqueue_task
ttwu_do_activate
try_to_wake_up
complete
cpu_stopper_thread
smpboot_thread_fn
kthread
ret_from_fork
ret_from_fork_asm
</TASK>Instead of providing yet another bandaid to work around the situation, fix it from hrtimers infrastructure instead: always migrate away a timer to an online target whenever it is enqueued from an offline CPU.
This will also allow to revert all the above RCU disgraceful hacks.
--- include/linux/hrtimer_defs.h | 1 + kernel/time/hrtimer.c | 92 +++++++++++++++++++++++++++++------- 2 files changed, 75 insertions(+), 18 deletions(-)
include/linux/hrtimer_defs.h +1 -0
@@ -125,6 +125,7 @@ struct hrtimer_cpu_base {ktime_t softirq_expires_next;struct hrtimer *softirq_next_timer;struct hrtimer_clock_base clock_base[HRTIMER_MAX_CLOCK_BASES ]; +call_single_data_t csd; }____cacheline_aligned ;
kernel/time/hrtimer.c +74 -18
167 lines not on this page · raw message
Signature · 1 line
2.46.0
Quoted 50 lines · Frederic Weisbecker
hrtimers are migrated away from the dying CPU to any online target at the
CPUHP_AP_HRTIMERS_DYINGstage in order not to delay bandwidth timers handling tasks involved in the CPU hotplug forward progress.However wake ups can still be performed by the outgoing CPU after
CPUHP_AP_HRTIMERS_DYING. Those can result again in bandwidth timers being armed. Depending on several considerations (crystal ball power management based election, earliest timer already enqueued, timer migration enabled or not), the target may eventually be the current CPU even if offline. If that happens, the timer is eventually ignored.The most notable example is RCU which had to deal with each and every of those wake-ups by deferring them to an online CPU, along with related workarounds:
_
e787644caf76(rcu: Defer RCU kthreads wakeup when CPU is dying) _9139f93209d1(rcu/nocb: Fix RT throttling hrtimer armed from offline CPU) _f7345ccc62a4(rcu/nocb: Fix rcuog wake-up from offline softirq)The problem isn't confined to RCU though as the stop machine kthread (which runs
CPUHP_AP_HRTIMERS_DYING) reports its completion at the end of its work throughcpu_stop_signal_done()and performs a wake up that eventually arms the deadline server timer:WARNING: CPU: 94 PID: 588 at kernel/time/hrtimer.c:1086 hrtimer_start_range_ns+0x289/0x2d0 CPU: 94 UID: 0 PID: 588 Comm: migration/94 Not tainted Stopper: multi_cpu_stop+0x0/0x120 <- stop_machine_cpuslocked+0x66/0xc0 RIP: 0010:hrtimer_start_range_ns+0x289/0x2d0 Call Trace: <TASK> ? hrtimer_start_range_ns start_dl_timer enqueue_dl_entity dl_server_start enqueue_task_fair enqueue_task ttwu_do_activate try_to_wake_up complete cpu_stopper_thread smpboot_thread_fn kthread ret_from_fork ret_from_fork_asm </TASK>Instead of providing yet another bandaid to work around the situation, fix it from hrtimers infrastructure instead: always migrate away a timer to an online target whenever it is enqueued from an offline CPU.
Quoted 8 lines
This will also allow to revert all the above RCU disgraceful hacks.
Reported-by Vlad Poenaru <[email protected]>Reported-by Usama Arif <[email protected]>Fixes 5c0930ccaad5 ("hrtimers: Push pending hrtimers away from outgoing CPU earlier")Closes [email protected]Signed-off-by Frederic Weisbecker <[email protected]>Signed-off-by Paul E. McKenney <[email protected]>
This passes over-holiday testing rcutorture, so, perhaps redundantly:
Quoted 201 lines
--- include/linux/hrtimer_defs.h | 1 + kernel/time/hrtimer.c | 92 +++++++++++++++++++++++++++++------- 2 files changed, 75 insertions(+), 18 deletions(-)include/linux/hrtimer_defs.h +1
-0@@ -125,6 +125,7 @@ struct hrtimer_cpu_base {ktime_t softirq_expires_next;struct hrtimer *softirq_next_timer;struct hrtimer_clock_base clock_base[HRTIMER_MAX_CLOCK_BASES ]; +call_single_data_t csd; }____cacheline_aligned ;kernel/time/hrtimer.c +74
-18@@ -58,6 +58,8 @@#define HRTIMER_ACTIVE_SOFT (HRTIMER_ACTIVE_HARD << MASK_SHIFT)#define HRTIMER_ACTIVE_ALL (HRTIMER_ACTIVE_SOFT | HRTIMER_ACTIVE_HARD) +static void retrigger_next_event (void *arg); +/* * The timer bases: * @@ -111,7 +113,8 @@ DEFINE_PER_CPU(struct hrtimer_cpu_base, hrtimer_bases) = .clockid = CLOCK_TAI, .get_time = &ktime_get_clocktai, }, - } + }, + .csd =CSD_INIT (retrigger_next_event,NULL ) };static const int hrtimer_clock_to_base_table[MAX_CLOCKS ] = {@@ -124,6 +127,14 @@ static const int hrtimer_clock_to_base_table[MAX_CLOCKS] = { [CLOCK_TAI] = HRTIMER_BASE_TAI, }; +static inline bool hrtimer_base_is_online (struct hrtimer_cpu_base *base) +{ +if (!IS_ENABLED (CONFIG_HOTPLUG_CPU )) +return true; +else +return likely (base->online); +} +/* * Functions and macros which are different for UP/SMP systems are kept in a * single place @@ -183,27 +194,58 @@ struct hrtimer_clock_base *lock_hrtimer_base(const struct hrtimer *timer, }/* -* We do not migrate the timer when it is expiring before the next -* event on the target cpu. When high resolution is enabled, we cannot -* reprogram the target cpu hardware and we would cause it to fire -* late. To keep it simple, we handle the high resolution enabled and -* disabled case similar. +* Check if the elected target is suitable considering its next +* event and the hotplug state of the current CPU. +* +* If the elected target is remote and its next event is after the timer +* to queue, then a remote reprogram is necessary. However there is no +* guarantee the IPI handling the operation would arrive in time to meet +* the high resolution deadline. In this case the local CPU becomes a +* preferred target, unless it is offline. +* +* High and low resolution modes are handled the same way for simplicity. * * Called with cpu_base->lock of target cpu held. */ -static int -hrtimer_check_target (struct hrtimer *timer,struct hrtimer_clock_base *new_base) +static bool +hrtimer_suitable_target (struct hrtimer *timer, +struct hrtimer_clock_base *new_base, +struct hrtimer_cpu_base *new_cpu_base, +struct hrtimer_cpu_base *this_cpu_base) {ktime_t expires; +/* +* The local CPU clockevent can be reprogrammed. Also get_target_base() +* guarantees it is online. +*/ +if (new_cpu_base == this_cpu_base) +return true; + +/* +* The offline local CPU can't be the default target if the +* next remote target event is after this timer. Keep the +* elected new base. An IPI will we issued to reprogram +* it as a last resort. +*/ +if (!hrtimer_base_is_online (this_cpu_base)) +return true; + expires =ktime_sub (hrtimer_get_expires (timer), new_base->offset); -return expires < new_base->cpu_base->expires_next; + +return expires >= new_base->cpu_base->expires_next; }static inline struct hrtimer_cpu_base *get_target_base (struct hrtimer_cpu_base *base,int pinned) { +if (!hrtimer_base_is_online (base)) { +int cpu =cpumask_any_and (cpu_online_mask,housekeeping_cpumask (HK_TYPE_TIMER )); + +return &per_cpu (hrtimer_bases, cpu); + } +#if defined(CONFIG_SMP ) && defined(CONFIG_NO_HZ_COMMON )if (static_branch_likely (&timers_migration_enabled) && !pinned)return &per_cpu (hrtimer_bases,get_nohz_timer_target ());@@ -254,8 +296,8 @@ switch_hrtimer_base(struct hrtimer *timer, struct hrtimer_clock_base *base,raw_spin_unlock (&base->cpu_base->lock);raw_spin_lock (&new_base->cpu_base->lock); -if (new_cpu_base != this_cpu_base && -hrtimer_check_target (timer, new_base)) { +if (!hrtimer_suitable_target (timer, new_base, new_cpu_base, + this_cpu_base)) {raw_spin_unlock (&new_base->cpu_base->lock);raw_spin_lock (&base->cpu_base->lock); new_cpu_base = this_cpu_base;@@ -264,8 +306,8 @@ switch_hrtimer_base(struct hrtimer *timer, struct hrtimer_clock_base *base, }WRITE_ONCE (timer->base, new_base); }else { -if (new_cpu_base != this_cpu_base && -hrtimer_check_target (timer, new_base)) { +if (!hrtimer_suitable_target (timer, new_base, new_cpu_base, + this_cpu_base)) { new_cpu_base = this_cpu_base; goto again; }@@ -716,8 +758,6 @@ static inline int hrtimer_is_hres_enabled(void)return hrtimer_hres_enabled; } -static void retrigger_next_event (void *arg); -/* * Switch to high resolution mode */ @@ -1206,6 +1246,7 @@ static int __hrtimer_start_range_ns(struct hrtimer *timer, ktime_t tim, u64 delta_ns, const enum hrtimer_mode mode, struct hrtimer_clock_base *base) { + struct hrtimer_cpu_base *this_cpu_base = this_cpu_ptr(&hrtimer_bases);struct hrtimer_clock_base *new_base;bool force_local, first;@@ -1217,9 +1258,15 @@ static int __hrtimer_start_range_ns(struct hrtimer *timer, ktime_t tim,* and enforce reprogramming after it is queued no matter whether * it is the new first expiring timer again or not. */ - force_local = base->cpu_base ==this_cpu_ptr (&hrtimer_bases); + force_local = base->cpu_base == this_cpu_base; force_local &= base->cpu_base->next_timer == timer; +/* +* Don't force local queuing if this enqueue happens on a unplugged +* CPU after hrtimer_cpu_dying() has been invoked. +*/ + force_local &= this_cpu_base->online; +/* * Remove an active timer from the queue. In case it is not queued * on the current CPU, make sure that remove_hrtimer() updates the @@ -1249,8 +1296,17 @@ static int __hrtimer_start_range_ns(struct hrtimer *timer, ktime_t tim, } first =enqueue_hrtimer (timer, new_base, mode); -if (!force_local) -return first; +if (!force_local) { +if (hrtimer_base_is_online (this_cpu_base)) +return first; + +if (first) { +struct hrtimer_cpu_base *new_cpu_base = new_base->cpu_base; + +smp_call_function_single_async (new_cpu_base->cpu, &new_cpu_base->csd); + } +return 0 ; + }/* * Timer was forced to stay on the current CPU to avoid Signature · 1 line
2.46.0
Quoted 75 lines · Paul E. McKenney
Quoted 66 lines · Frederic Weisbecker
hrtimers are migrated away from the dying CPU to any online target at
theCPUHP_AP_HRTIMERS_DYINGstage in order not to delay bandwidth timers
handling tasks involved in the CPU hotplug forward progress.However wake ups can still be performed by the outgoing CPU after
CPUHP_AP_HRTIMERS_DYING. Those can result again in bandwidth timers being armed. Depending on several considerations (crystal ball power management based election, earliest timer already enqueued, timer
migration enabled or not), the target may eventually be the current CPU even if offline. If that happens, the timer is eventually ignored.The most notable example is RCU which had to deal with each and every of
those wake-ups by deferring them to an online CPU, along with related
workarounds:_
e787644caf76(rcu: Defer RCU kthreads wakeup when CPU is dying) _9139f93209d1(rcu/nocb: Fix RT throttling hrtimer armed from offline CPU)
_f7345ccc62a4(rcu/nocb: Fix rcuog wake-up from offline softirq)The problem isn't confined to RCU though as the stop machine kthread
(which runsCPUHP_AP_HRTIMERS_DYING) reports its completion at the end
of its work throughcpu_stop_signal_done()and performs a wake up that
eventually arms the deadline server timer:WARNING: CPU: 94 PID: 588 at kernel/time/hrtimer.c:1086 hrtimer_start_range_ns+0x289/0x2d0 CPU: 94 UID: 0 PID: 588 Comm: migration/94 Not tainted Stopper: multi_cpu_stop+0x0/0x120 <- stop_machine_cpuslocked+0x66/0xc0 RIP: 0010:hrtimer_start_range_ns+0x289/0x2d0 Call Trace: <TASK>?
hrtimer_start_range_ns
start_dl_timer
enqueue_dl_entity
dl_server_start
enqueue_task_fair
enqueue_task
ttwu_do_activate
try_to_wake_up
complete
cpu_stopper_thread
smpboot_thread_fn
kthread
ret_from_fork
ret_from_fork_asm</TASK>Instead of providing yet another bandaid to work around the situation,
fix it from hrtimers infrastructure instead: always migrate away a timer to an online target whenever it is enqueued from an offline CPU.This will also allow to revert all the above RCU disgraceful hacks.
Quoted 7 lines
Reported-by Vlad Poenaru <[email protected]>Reported-by Usama Arif <[email protected]>Fixes 5c0930ccaad5 ("hrtimers: Push pending hrtimers away fromoutgoing CPU earlier")
Closes [email protected]Signed-off-by Frederic Weisbecker <[email protected]>Signed-off-by Paul E. McKenney <[email protected]>
Quoted 3 lines
This passes over-holiday testing rcutorture, so, perhaps redundantly:
Tested-by Paul E. McKenney <[email protected]>
Hi,
I encountered the same issue even after applying this patch. Below are the details of the warning and call trace.
migration/3: ------------[ cut here ]------------ migration/3: WARNING: CPU: 3 PID: 42 at kernel/time/hrtimer.c:1125 enqueue_hrtimer+0x7c/0xec
migration/3: CPU: 3 UID: 0 PID: 42 Comm: migration/3 Tainted: G
OE 6.12.18-android16-0-g59cb5a849beb-4k #10b440e43fa7b24aaa3b7e6e5d2b938948e0cacdb
migration/3: Stopper: multi_cpu_stop+0x0/0x184 <- stop_machine_cpuslocked+0xc0/0x15c
migration/3: Call trace: migration/3: enqueue_hrtimer+0x7c/0xec migration/3: hrtimer_start_range_ns+0x54c/0x7b4 migration/3: start_dl_timer+0x110/0x188 migration/3: enqueue_dl_entity+0x478/0x80c migration/3: dl_server_start+0xf8/0x194 migration/3: enqueue_task_fair+0x6c4/0x924 migration/3: enqueue_task+0x70/0x3a4 migration/3: do_activate_task+0xcc/0x178 migration/3: ttwu_do_activate+0xac/0x2e0 migration/3: try_to_wake_up+0x73c/0xaf4 migration/3: wake_up_process+0x18/0x28 migration/3: kick_pool+0xc4/0x170 migration/3: __queue_work+0x44c/0x698 migration/3: delayed_work_timer_fn+0x20/0x30 migration/3: call_timer_fn+0x4c/0x1d0 migration/3: __run_timer_base+0x278/0x350 migration/3: run_timer_softirq+0x78/0x9c migration/3: handle_softirqs+0x154/0x42c migration/3: __do_softirq+0x14/0x20 migration/3: ____do_softirq+0x10/0x20 migration/3: call_on_irq_stack+0x3c/0x74 migration/3: do_softirq_own_stack+0x1c/0x2c migration/3: __irq_exit_rcu+0x5c/0xd4 migration/3: irq_exit_rcu+0x10/0x1c migration/3: el1_interrupt+0x38/0x58 migration/3: el1h_64_irq_handler+0x18/0x24 migration/3: el1h_64_irq+0x68/0x6c migration/3: multi_cpu_stop+0x15c/0x184 migration/3: cpu_stopper_thread+0xf8/0x1b0 migration/3: smpboot_thread_fn+0x204/0x304 migration/3: kthread+0x110/0x1a4 migration/3: ret_from_fork+0x10/0x20 migration/3: ---[ end trace 0000000000000000 ]---
kworker/1:1: psci: CPU0 killed (polled 0 ms)
Frederic Weisbecker
maintainerHi Walter Chang,
Quoted 86 lines · Walter Chang (張維哲)
Quoted 75 lines · Paul E. McKenney
Quoted 66 lines · Frederic Weisbecker
hrtimers are migrated away from the dying CPU to any online target at
theCPUHP_AP_HRTIMERS_DYINGstage in order not to delay bandwidth timers
handling tasks involved in the CPU hotplug forward progress.However wake ups can still be performed by the outgoing CPU after
CPUHP_AP_HRTIMERS_DYING. Those can result again in bandwidth timers being armed. Depending on several considerations (crystal ball power management based election, earliest timer already enqueued, timer
migration enabled or not), the target may eventually be the current CPU even if offline. If that happens, the timer is eventually ignored.The most notable example is RCU which had to deal with each and every of
those wake-ups by deferring them to an online CPU, along with related
workarounds:_
e787644caf76(rcu: Defer RCU kthreads wakeup when CPU is dying) _9139f93209d1(rcu/nocb: Fix RT throttling hrtimer armed from offline CPU)
_f7345ccc62a4(rcu/nocb: Fix rcuog wake-up from offline softirq)The problem isn't confined to RCU though as the stop machine kthread
(which runsCPUHP_AP_HRTIMERS_DYING) reports its completion at the end
of its work throughcpu_stop_signal_done()and performs a wake up that
eventually arms the deadline server timer:WARNING: CPU: 94 PID: 588 at kernel/time/hrtimer.c:1086 hrtimer_start_range_ns+0x289/0x2d0 CPU: 94 UID: 0 PID: 588 Comm: migration/94 Not tainted Stopper: multi_cpu_stop+0x0/0x120 <- stop_machine_cpuslocked+0x66/0xc0 RIP: 0010:hrtimer_start_range_ns+0x289/0x2d0 Call Trace: <TASK>?
hrtimer_start_range_ns
start_dl_timer
enqueue_dl_entity
dl_server_start
enqueue_task_fair
enqueue_task
ttwu_do_activate
try_to_wake_up
complete
cpu_stopper_thread
smpboot_thread_fn
kthread
ret_from_fork
ret_from_fork_asm</TASK>Instead of providing yet another bandaid to work around the situation,
fix it from hrtimers infrastructure instead: always migrate away a timer to an online target whenever it is enqueued from an offline CPU.This will also allow to revert all the above RCU disgraceful hacks.
Quoted 7 lines
Reported-by Vlad Poenaru <[email protected]>Reported-by Usama Arif <[email protected]>Fixes 5c0930ccaad5 ("hrtimers: Push pending hrtimers away fromoutgoing CPU earlier")
Closes [email protected]Signed-off-by Frederic Weisbecker <[email protected]>Signed-off-by Paul E. McKenney <[email protected]>Quoted 3 lines
This passes over-holiday testing rcutorture, so, perhaps redundantly:
Tested-by Paul E. McKenney <[email protected]>Hi,
I encountered the same issue even after applying this patch. Below are the details of the warning and call trace.
Quoted 8 lines
migration/3: ------------[ cut here ]------------ migration/3: WARNING: CPU: 3 PID: 42 at
kernel/time/hrtimer.c:1125enqueue_hrtimer+0x7c/0xec
migration/3: CPU: 3 UID: 0 PID: 42 Comm: migration/3 Tainted: G
OE 6.12.18-android16-0-g59cb5a849beb-4k #10b440e43fa7b24aaa3b7e6e5d2b938948e0cacdb
migration/3: Stopper:multi_cpu_stop+0x0/0x184<-stop_machine_cpuslocked+0xc0/0x15c
It's not the first time I get such a report on an out of tree kernel. The problem is I don't know if the tainted modules are involved. But something is probably making an offline CPU visible within the hierarchy on get_nohz_timer_target(). And that new warning made that visible.
Can you try this and tell us if the warning fires?
Thanks.
include/linux/sched/nohz.h +1 -0
@@ -9,6 +9,7 @@#if defined(CONFIG_SMP ) && defined(CONFIG_NO_HZ_COMMON )extern void nohz_balance_enter_idle (int cpu);extern int get_nohz_timer_target (void ); +extern void assert_domain_online (void );#else static inline void nohz_balance_enter_idle (int cpu) { }#endif
kernel/cpu.c +2 -0
@@ -13,6 +13,7 @@#include <linux/sched/isolation.h> #include <linux/sched/task.h> #include <linux/sched/smt.h> +#include <linux/sched/nohz.h> #include <linux/unistd.h> #include <linux/cpu.h> #include <linux/oom.h>
@@ -1277,6 +1278,7 @@ static int take_cpu_down(void *_param)if (err <0 )return err; +assert_domain_online ();/* * Must be called from CPUHP_TEARDOWN_CPU, which means, as we are going * down, that the current state is CPUHP_TEARDOWN_CPU - 1.
kernel/sched/core.c +14 -0
@@ -1163,6 +1163,20 @@ void resched_cpu(int cpu)#ifdef CONFIG_SMP #ifdef CONFIG_NO_HZ_COMMON +void assert_domain_online (void ) +{ +int cpu =smp_processor_id (); +int i; +struct sched_domain *sd; + +guard (rcu)(); + +for_each_domain (cpu, sd) { +for_each_cpu (i,sched_domain_span (sd)) { +WARN_ON_ONCE (cpu_is_offline (i)); + } + } +}/* * In the semi idle case, use the nearest busy CPU for migrating timers * from an idle CPU. This is good for power-savings.
Quoted 69 lines · Frederic Weisbecker
It's not the first time I get such a report on an out of tree kernel. The problem is I don't know if the tainted modules are involved. But something is probably making an offline CPU visible within
the hierarchy onget_nohz_timer_target(). And that new warning made that visible.Can you try this and tell us if the warning fires?
Thanks.
include/linux/sched/nohz.h +1
-0@@ -9,6 +9,7 @@ #if defined(CONFIG_SMP) && defined(CONFIG_NO_HZ_COMMON) extern void nohz_balance_enter_idle(int cpu); extern int get_nohz_timer_target(void); +extern void assert_domain_online (void ); #else static inline void nohz_balance_enter_idle(int cpu) { } #endifkernel/cpu.c +2
-0@@ -13,6 +13,7 @@ #include <linux/sched/isolation.h> #include <linux/sched/task.h> #include <linux/sched/smt.h> +#include <linux/sched/nohz.h> #include <linux/unistd.h> #include <linux/cpu.h> #include <linux/oom.h>@@ -1277,6 +1278,7 @@ static int take_cpu_down(void *_param) if (err < 0) return err; + assert_domain_online(); /** Must be called from CPUHP_TEARDOWN_CPU, which means, as we are going
* down, that the current state isCPUHP_TEARDOWN_CPU- 1.kernel/sched/core.c +14
-0@@ -1163,6 +1163,20 @@ void resched_cpu(int cpu) #ifdef CONFIG_SMP #ifdef CONFIG_NO_HZ_COMMON +void assert_domain_online (void ) +{ + int cpu = smp_processor_id(); + int i; + struct sched_domain *sd; + + guard(rcu)(); + + for_each_domain(cpu, sd) { + for_each_cpu(i, sched_domain_span(sd)) { + WARN_ON_ONCE(cpu_is_offline(i)); + } + } +} /** In the semi idle case, use the nearest busy CPU for migrating
Quoted 2 lines
timers
* from an idle CPU. This is good for power-savings.
Hi Frederic,
Thank you for providing the patch to debug the hrtimer warning issue.
I have applied the patch and conducted stress testing over the weekend. And the warning provided in the patch did not occur during this period.
Additionally, after a thorough review of our internal tainted modules, I can confirm that you are correct in your assessment. The get_nohz_timer_target() with our tainted modules may indeed return a CPU that is offline, leading to the hrtimer warning issue. We are working on fixing this within our tainted modules.
Thanks again for your help in debugging this issue.
Best regards,
Walter Chang
Quoted 117 lines · Frederic Weisbecker
Hi Walter Chang,
Le Wed, Mar 26, 2025 at 05:46:38AM +0000, Walter Chang (張維哲) a écrit :
Quoted 103 lines · Paul E. McKenney
Quoted 91 lines
Quoted 81 lines · Frederic Weisbecker
hrtimers are migrated away from the dying CPU to any online target
at
theCPUHP_AP_HRTIMERS_DYINGstage in order not to delay bandwidth
timers
handling tasks involved in the CPU hotplug forward progress.However wake ups can still be performed by the outgoing CPU after
CPUHP_AP_HRTIMERS_DYING. Those can result again in bandwidth timers
being armed. Depending on several considerations (crystal ball power management based election, earliest timer already enqueued,
timer
migration enabled or not), the target may eventually be the current
CPU even if offline. If that happens, the timer is eventually ignored.The most notable example is RCU which had to deal with each and every of
those wake-ups by deferring them to an online CPU, along with related
workarounds:_
e787644caf76(rcu: Defer RCU kthreads wakeup when CPU is dying)
_9139f93209d1(rcu/nocb: Fix RT throttling hrtimer armed from offline CPU)
_f7345ccc62a4(rcu/nocb: Fix rcuog wake-up from offline softirq)The problem isn't confined to RCU though as the stop machine kthread
(which runsCPUHP_AP_HRTIMERS_DYING) reports its completion at the
end
of its work throughcpu_stop_signal_done()and performs a wake up
that
eventually arms the deadline server timer:WARNING: CPU: 94 PID: 588 at kernel/time/hrtimer.c:1086 hrtimer_start_range_ns+0x289/0x2d0 CPU: 94 UID: 0 PID: 588 Comm: migration/94 Not tainted Stopper: multi_cpu_stop+0x0/0x120 <- stop_machine_cpuslocked+0x66/0xc0 RIP: 0010:hrtimer_start_range_ns+0x289/0x2d0 Call Trace: <TASK>?
hrtimer_start_range_ns
start_dl_timer
enqueue_dl_entity
dl_server_start
enqueue_task_fair
enqueue_task
ttwu_do_activate
try_to_wake_up
complete
cpu_stopper_thread
smpboot_thread_fn
kthread
ret_from_fork
ret_from_fork_asm</TASK>Instead of providing yet another bandaid to work around the situation,
fix it from hrtimers infrastructure instead: always migrate away a
timer to an online target whenever it is enqueued from an offline
CPU.This will also allow to revert all the above RCU disgraceful hacks.
Quoted 7 lines
Reported-by Vlad Poenaru <[email protected]>Reported-by Usama Arif <[email protected]>Fixes 5c0930ccaad5 ("hrtimers: Push pending hrtimers away fromoutgoing CPU earlier")
Closes [email protected]Signed-off-by Frederic Weisbecker <[email protected]>Signed-off-by Paul E. McKenney <[email protected]>Quoted 4 lines
This passes over-holiday testing rcutorture, so, perhaps redundantly:
Tested-by Paul E. McKenney <[email protected]>Hi,
I encountered the same issue even after applying this patch. Below are the details of the warning and call trace.
Quoted 9 lines
migration/3: ------------[ cut here ]------------ migration/3: WARNING: CPU: 3 PID: 42 at
kernel/time/hrtimer.c:1125enqueue_hrtimer+0x7c/0xec
migration/3: CPU: 3 UID: 0 PID: 42 Comm: migration/3 Tainted:
G
OE 6.12.18-android16-0-g59cb5a849beb-4k #10b440e43fa7b24aaa3b7e6e5d2b938948e0cacdb
migration/3: Stopper:multi_cpu_stop+0x0/0x184<-stop_machine_cpuslocked+0xc0/0x15c
Quoted 7 lines
It's not the first time I get such a report on an out of tree kernel. The problem is I don't know if the tainted modules are involved. But something is probably making an offline CPU visible within
the hierarchy onget_nohz_timer_target(). And that new warning made that visible.
Hi,
By review the get_nohz_timer_target(), it's probably making an offline CPU visible at timer candidates, maybe this patch could fix it?
[PATCH] sched/core: Exclude offline CPUs from the timer candidates
The timer target is chosen from the HK_TYPE_KERNEL_NOISE. However,the candidate may be an offline CPU,
so exclude offline CPUs and choose only from online CPUs.
--- kernel/sched/core.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-)
kernel/sched/core.c +1 -1
@@ -1182,7 +1182,7 @@ int get_nohz_timer_target(void)struct sched_domain *sd;const struct cpumask *hk_mask; -if (housekeeping_cpu (cpu,HK_TYPE_KERNEL_NOISE )) { + if (housekeeping_cpu(cpu, HK_TYPE_KERNEL_NOISE) &&
cpu_online(cpu)) {
if (!idle_cpu(cpu))
return cpu;
default_cpu = cpu;
@@ -1197,13 +1197,16 @@ int get_nohz_timer_target(void)
if (cpu == i)
continue;- if (!idle_cpu(i))
+ if (!idle_cpu(i) && cpu_online(i))
return i;
}
}- if (default_cpu == -1)
+ if (default_cpu == -1) {
default_cpu =housekeeping_any_cpu(HK_TYPE_KERNEL_NOISE);
+ if (!cpu_online(default_cpu))
+ default_cpu = cpumask_any(cpu_online_mask);
+ }
return default_cpu;
}
Quoted 63 lines
Can you try this and tell us if the warning fires?
Thanks.
include/linux/sched/nohz.h +1
-0@@ -9,6 +9,7 @@ #if defined(CONFIG_SMP) && defined(CONFIG_NO_HZ_COMMON) extern void nohz_balance_enter_idle(int cpu); extern int get_nohz_timer_target(void); +extern void assert_domain_online (void ); #else static inline void nohz_balance_enter_idle(int cpu) { } #endifkernel/cpu.c +1
-0@@ -13,6 +13,7 @@ #include <linux/sched/isolation.h> #include <linux/sched/task.h> #include <linux/sched/smt.h> +#include <linux/sched/nohz.h> #include <linux/unistd.h> #include <linux/cpu.h> #include <linux/oom.h>@@ -1277,6 +1278,7 @@ static int take_cpu_down(void *_param) if (err < 0) return err;kernel/cpu.c +1
-0+assert_domain_online ();/*
* Must be called fromCPUHP_TEARDOWN_CPU, which means, as we are going
* down, that the current state isCPUHP_TEARDOWN_CPU- 1.kernel/sched/core.c +0
-0@@ -1163,6 +1163,20 @@ void resched_cpu(int cpu)
#ifdefCONFIG_SMP
#ifdefCONFIG_NO_HZ_COMMONkernel/sched/core.c +14
-0+void assert_domain_online (void ) +{ +int cpu =smp_processor_id (); +int i; +struct sched_domain *sd; + +guard (rcu)(); + +for_each_domain (cpu, sd) { +for_each_cpu (i,sched_domain_span (sd)) { +WARN_ON_ONCE (cpu_is_offline (i)); + } + } +}/*
* In the semi idle case, use the nearest busy CPU for migrating timers
* from an idle CPU. This is good for power-savings.
Frederic Weisbecker
maintainerQuoted 30 lines · Kuyo Chang (張建文)
Hi,
By review the
get_nohz_timer_target(), it's probably making an offline CPU visible at timer candidates, maybe this patch could fix it?[PATCH] sched/core: Exclude offline CPUs from the timer candidates
The timer target is chosen from the
HK_TYPE_KERNEL_NOISE. However,the candidate may be an offline CPU,
so exclude offline CPUs and choose only from online CPUs.Signed-off-by kuyo chang <[email protected]>--- kernel/sched/core.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-)kernel/sched/core.c +1
-1@@ -1182,7 +1182,7 @@ int get_nohz_timer_target(void)struct sched_domain *sd;const struct cpumask *hk_mask; -if (housekeeping_cpu (cpu,HK_TYPE_KERNEL_NOISE )) { +if (housekeeping_cpu (cpu,HK_TYPE_KERNEL_NOISE ) &&cpu_online (cpu)) {if (!idle_cpu (cpu))return cpu;
Quoted 1 line
default_cpu = cpu;
It can't choose the current CPU because get_target_base() prevents that:
if (!hrtimer_base_is_online(base)) {
int cpu = cpumask_any_and(cpu_online_mask, housekeeping_cpumask(HK_TYPE_TIMER));
return &per_cpu(hrtimer_bases, cpu);
}kernel/sched/core.c int get_nohz_timer_target(void) · line 1201
Quoted 7 lines
kernel/sched/core.c +1
-1@@ -1197,13 +1197,16 @@ int get_nohz_timer_target(void)if (cpu == i)continue ; -if (!idle_cpu (i)) +if (!idle_cpu (i) &&cpu_online (i))return i;
CPUs within the domain hierarchy are guaranteed to be online. sched_cpu_deactivate() -> cpuset_cpu_inactive(cpu) is supposed to take care of that. Unless there is another bug lurking here, which is my suspicion. But it's hard to know as we are dealing with a kernel with out of tree patches.
kernel/sched/core.c int get_nohz_timer_target(void) · line 1209
Quoted 9 lines
kernel/sched/core.c +3
-1} } -if (default_cpu ==-1 ) +if (default_cpu ==-1 ) { default_cpu =housekeeping_any_cpu (HK_TYPE_KERNEL_NOISE ); +if (!cpu_online (default_cpu)) + default_cpu =cpumask_any (cpu_online_mask);
housekeeping_any_cpu() only returns online CPUs.
Thanks.
The following commit has been merged into the timers/urgent branch of tip:
The following commit has been merged into the timers/urgent branch of tip:
Commit-ID: b7a110336261147ccb373f4100cc88271c54bd91 Gitweb: https://git.kernel.org/tip/b7a110336261147ccb373f4100cc88271c54bd91 Author: Frederic Weisbecker <[email protected]> AuthorDate: Sat, 18 Jan 2025 00:24:33 +01:00 Committer: Thomas Gleixner <[email protected]> CommitterDate: Thu, 23 Jan 2025 11:47:23 +01:00
hrtimers: Force migrate away hrtimers queued after CPUHP_AP_HRTIMERS_DYING
hrtimers are migrated away from the dying CPU to any online target at the CPUHP_AP_HRTIMERS_DYING stage in order not to delay bandwidth timers handling tasks involved in the CPU hotplug forward progress.
However wakeups can still be performed by the outgoing CPU after CPUHP_AP_HRTIMERS_DYING. Those can result again in bandwidth timers being armed. Depending on several considerations (crystal ball power management based election, earliest timer already enqueued, timer migration enabled or not), the target may eventually be the current CPU even if offline. If that happens, the timer is eventually ignored.
The most notable example is RCU which had to deal with each and every of those wake-ups by deferring them to an online CPU, along with related workarounds:
_ e787644caf76 (rcu: Defer RCU kthreads wakeup when CPU is dying) _ 9139f93209d1 (rcu/nocb: Fix RT throttling hrtimer armed from offline CPU) _ f7345ccc62a4 (rcu/nocb: Fix rcuog wake-up from offline softirq)
The problem isn't confined to RCU though as the stop machine kthread (which runs CPUHP_AP_HRTIMERS_DYING) reports its completion at the end of its work through cpu_stop_signal_done() and performs a wake up that eventually arms the deadline server timer:
WARNING: CPU: 94 PID: 588 at kernel/time/hrtimer.c:1086 hrtimer_start_range_ns+0x289/0x2d0
CPU: 94 UID: 0 PID: 588 Comm: migration/94 Not tainted
Stopper: multi_cpu_stop+0x0/0x120 <- stop_machine_cpuslocked+0x66/0xc0
RIP: 0010:hrtimer_start_range_ns+0x289/0x2d0
Call Trace:
<TASK>
start_dl_timer
enqueue_dl_entity
dl_server_start
enqueue_task_fair
enqueue_task
ttwu_do_activate
try_to_wake_up
complete
cpu_stopper_threadInstead of providing yet another bandaid to work around the situation, fix it in the hrtimers infrastructure instead: always migrate away a timer to an online target whenever it is enqueued from an offline CPU.
This will also allow to revert all the above RCU disgraceful hacks.
--- include/linux/hrtimer_defs.h | 1 +- kernel/time/hrtimer.c | 103 +++++++++++++++++++++++++++------- 2 files changed, 83 insertions(+), 21 deletions(-)
include/linux/hrtimer_defs.h +1 -0
@@ -125,6 +125,7 @@ struct hrtimer_cpu_base {ktime_t softirq_expires_next;struct hrtimer *softirq_next_timer;struct hrtimer_clock_base clock_base[HRTIMER_MAX_CLOCK_BASES ]; +call_single_data_t csd; }____cacheline_aligned ;
kernel/time/hrtimer.c +82 -21
176 lines not on this page · raw message
The following commit has been merged into the timers/urgent branch of tip:
The following commit has been merged into the timers/urgent branch of tip:
Commit-ID: 53dac345395c0d2493cbc2f4c85fe38aef5b63f5 Gitweb: https://git.kernel.org/tip/53dac345395c0d2493cbc2f4c85fe38aef5b63f5 Author: Frederic Weisbecker <[email protected]> AuthorDate: Sat, 18 Jan 2025 00:24:33 +01:00 Committer: Thomas Gleixner <[email protected]> CommitterDate: Thu, 23 Jan 2025 20:06:35 +01:00
hrtimers: Force migrate away hrtimers queued after CPUHP_AP_HRTIMERS_DYING
hrtimers are migrated away from the dying CPU to any online target at the CPUHP_AP_HRTIMERS_DYING stage in order not to delay bandwidth timers handling tasks involved in the CPU hotplug forward progress.
However wakeups can still be performed by the outgoing CPU after CPUHP_AP_HRTIMERS_DYING. Those can result again in bandwidth timers being armed. Depending on several considerations (crystal ball power management based election, earliest timer already enqueued, timer migration enabled or not), the target may eventually be the current CPU even if offline. If that happens, the timer is eventually ignored.
The most notable example is RCU which had to deal with each and every of those wake-ups by deferring them to an online CPU, along with related workarounds:
_ e787644caf76 (rcu: Defer RCU kthreads wakeup when CPU is dying) _ 9139f93209d1 (rcu/nocb: Fix RT throttling hrtimer armed from offline CPU) _ f7345ccc62a4 (rcu/nocb: Fix rcuog wake-up from offline softirq)
The problem isn't confined to RCU though as the stop machine kthread (which runs CPUHP_AP_HRTIMERS_DYING) reports its completion at the end of its work through cpu_stop_signal_done() and performs a wake up that eventually arms the deadline server timer:
WARNING: CPU: 94 PID: 588 at kernel/time/hrtimer.c:1086 hrtimer_start_range_ns+0x289/0x2d0
CPU: 94 UID: 0 PID: 588 Comm: migration/94 Not tainted
Stopper: multi_cpu_stop+0x0/0x120 <- stop_machine_cpuslocked+0x66/0xc0
RIP: 0010:hrtimer_start_range_ns+0x289/0x2d0
Call Trace:
<TASK>
start_dl_timer
enqueue_dl_entity
dl_server_start
enqueue_task_fair
enqueue_task
ttwu_do_activate
try_to_wake_up
complete
cpu_stopper_threadInstead of providing yet another bandaid to work around the situation, fix it in the hrtimers infrastructure instead: always migrate away a timer to an online target whenever it is enqueued from an offline CPU.
This will also allow to revert all the above RCU disgraceful hacks.
--- include/linux/hrtimer_defs.h | 1 +- kernel/time/hrtimer.c | 103 +++++++++++++++++++++++++++------- 2 files changed, 83 insertions(+), 21 deletions(-)
include/linux/hrtimer_defs.h +1 -0
@@ -125,6 +125,7 @@ struct hrtimer_cpu_base {ktime_t softirq_expires_next;struct hrtimer *softirq_next_timer;struct hrtimer_clock_base clock_base[HRTIMER_MAX_CLOCK_BASES ]; +call_single_data_t csd; }____cacheline_aligned ;
kernel/time/hrtimer.c +82 -21
176 lines not on this page · raw message