@@ -75,6 +75,9 @@ constexpr uint32_t kDefaultRingCapacity = 64;
7575constexpr size_t kMinAltStackSize = 128 * 1024 ;
7676constexpr size_t kAltStackSigstkszMultiplier = 4 ;
7777constexpr size_t kMaxSlotBytes = 256 * 1024 * 1024 ;
78+ constexpr uint64_t kHealthWindowEvents = 128 ;
79+ constexpr uint64_t kHealthBadRatioPercent = 90 ;
80+ constexpr uint32_t kHealthBadWindowsLimit = 3 ;
7881
7982int g_cookie;
8083
@@ -194,6 +197,7 @@ struct CaptureState
194197 bool timer_deleted = false ;
195198 bool retired = false ;
196199
200+ std::atomic<uint64_t > published_count{ 0 };
197201 std::atomic<uint64_t > dropped_count{ 0 };
198202 std::atomic<uint64_t > dropped_cpu_ns{ 0 };
199203 std::atomic<uint64_t > capture_failed_count{ 0 };
@@ -202,6 +206,14 @@ struct CaptureState
202206 std::atomic<uint64_t > timer_overrun_total{ 0 };
203207 std::atomic<uint64_t > coalesced_signal_count{ 0 };
204208
209+ uint64_t health_seen_published_count = 0 ;
210+ uint64_t health_seen_dropped_count = 0 ;
211+ uint64_t health_seen_capture_failed_count = 0 ;
212+ uint64_t health_window_published_count = 0 ;
213+ uint64_t health_window_dropped_count = 0 ;
214+ uint64_t health_window_capture_failed_count = 0 ;
215+ uint32_t health_bad_window_count = 0 ;
216+
205217 CaptureState (uint64_t thread_id, uint64_t tid, const char * thread_name)
206218 : python_thread_id(thread_id)
207219 , native_tid(tid)
@@ -248,7 +260,7 @@ struct EngineState
248260 std::atomic<uint64_t > timer_syscall_failures{ 0 };
249261 std::atomic<uint64_t > accepted_signal_oob_tid_count{ 0 };
250262 std::atomic<uint64_t > handler_hijack_disable_count{ 0 };
251- std::atomic<uint64_t > fast_copy_conflict_count { 0 };
263+ std::atomic<uint64_t > health_disable_count { 0 };
252264 std::atomic<uint64_t > stage2_invalid_frame_count{ 0 };
253265};
254266
@@ -703,6 +715,49 @@ drain_state(EchionSampler& echion, CaptureState& state)
703715 }
704716}
705717
718+ uint64_t
719+ counter_delta (uint64_t current, uint64_t & seen)
720+ {
721+ const uint64_t delta = current >= seen ? current - seen : 0 ;
722+ seen = current;
723+ return delta;
724+ }
725+
726+ bool
727+ capture_state_health_should_disable (CaptureState& state)
728+ {
729+ // AIDEV-NOTE: Health is evaluated on the sampler thread, never in the SIGPROF handler.
730+ // The handler only increments lock-free counters. Timer deletion and state retirement stay
731+ // on the normal control path so pathological capture failures cannot turn into signal-time
732+ // locking or allocation.
733+ const uint64_t published = state.published_count .load (std::memory_order_relaxed);
734+ const uint64_t dropped = state.dropped_count .load (std::memory_order_relaxed);
735+ const uint64_t failed = state.capture_failed_count .load (std::memory_order_relaxed);
736+
737+ state.health_window_published_count += counter_delta (published, state.health_seen_published_count );
738+ state.health_window_dropped_count += counter_delta (dropped, state.health_seen_dropped_count );
739+ state.health_window_capture_failed_count += counter_delta (failed, state.health_seen_capture_failed_count );
740+
741+ const uint64_t bad_events = state.health_window_dropped_count + state.health_window_capture_failed_count ;
742+ const uint64_t total_events = bad_events + state.health_window_published_count ;
743+ if (total_events < kHealthWindowEvents ) {
744+ return false ;
745+ }
746+
747+ const bool bad_window = bad_events * 100 >= total_events * kHealthBadRatioPercent ;
748+ state.health_window_published_count = 0 ;
749+ state.health_window_dropped_count = 0 ;
750+ state.health_window_capture_failed_count = 0 ;
751+
752+ if (!bad_window) {
753+ state.health_bad_window_count = 0 ;
754+ return false ;
755+ }
756+
757+ state.health_bad_window_count ++;
758+ return state.health_bad_window_count >= kHealthBadWindowsLimit ;
759+ }
760+
706761void
707762disable_all_timers_locked ()
708763{
@@ -729,6 +784,19 @@ disable_all_timers_for_hijack()
729784 disable_all_timers_locked ();
730785}
731786
787+ void
788+ disable_all_timers_for_health_locked ()
789+ {
790+ if (!g_state.active .load (std::memory_order_acquire)) {
791+ return ;
792+ }
793+ g_state.health_disable_count .fetch_add (1 , std::memory_order_relaxed);
794+ g_state.active .store (false , std::memory_order_release);
795+ g_state.replacing_wall_cpu .store (false , std::memory_order_release);
796+ g_state.permanently_disabled .store (true , std::memory_order_release);
797+ disable_all_timers_locked ();
798+ }
799+
732800void
733801disable_all_timers_for_blocked_signal ()
734802{
@@ -915,6 +983,7 @@ cpu_timer_signal_handler(int signo, siginfo_t* si, void* ucontext)
915983 }
916984
917985 state->ring .publish_for_producer ();
986+ state->published_count .fetch_add (1 , std::memory_order_relaxed);
918987 g_state.handler_active [tid].store (false , std::memory_order_seq_cst);
919988 errno = saved_errno;
920989}
@@ -1258,6 +1327,22 @@ Engine::drain(EchionSampler& echion)
12581327 for (auto & state : ready_to_free) {
12591328 drain_state (echion, *state);
12601329 }
1330+
1331+ {
1332+ std::lock_guard<std::mutex> lock (g_state.registry_lock );
1333+ bool should_disable = false ;
1334+ if (g_state.active .load (std::memory_order_acquire)) {
1335+ for (auto & item : g_state.live_by_thread_id ) {
1336+ if (capture_state_health_should_disable (*item.second )) {
1337+ should_disable = true ;
1338+ break ;
1339+ }
1340+ }
1341+ }
1342+ if (should_disable) {
1343+ disable_all_timers_for_health_locked ();
1344+ }
1345+ }
12611346#else
12621347 (void )echion;
12631348#endif
@@ -1313,7 +1398,7 @@ Engine::debug_stats() const
13131398 stats.timer_syscall_failures = g_state.timer_syscall_failures .load (std::memory_order_relaxed);
13141399 stats.accepted_signal_oob_tid_count = g_state.accepted_signal_oob_tid_count .load (std::memory_order_relaxed);
13151400 stats.handler_hijack_disable_count = g_state.handler_hijack_disable_count .load (std::memory_order_relaxed);
1316- stats.fast_copy_conflict_count = g_state.fast_copy_conflict_count .load (std::memory_order_relaxed);
1401+ stats.health_disable_count = g_state.health_disable_count .load (std::memory_order_relaxed);
13171402 stats.stage2_invalid_frame_count = g_state.stage2_invalid_frame_count .load (std::memory_order_relaxed);
13181403
13191404 std::lock_guard<std::mutex> lock (g_state.registry_lock );
0 commit comments