blob: 865bb7b25f6002178ba51253c55f1716a16fb39d [file] [log] [blame]
Willy Tarreau2bfefdb2019-05-03 13:52:18 +02001/*
2 * Thread lockup detection
3 *
4 * Copyright 2000-2019 Willy Tarreau <willy@haproxy.org>.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version
9 * 2 of the License, or (at your option) any later version.
10 */
11
12#include <signal.h>
13#include <time.h>
14
Willy Tarreau4c7e4b72020-05-27 12:58:42 +020015#include <haproxy/api.h>
Willy Tarreau55542642021-10-08 09:33:24 +020016#include <haproxy/clock.h>
Willy Tarreau2a83d602020-05-27 16:58:08 +020017#include <haproxy/debug.h>
Willy Tarreau36979d92020-06-05 17:27:29 +020018#include <haproxy/errors.h>
Willy Tarreauf268ee82020-06-04 17:05:57 +020019#include <haproxy/global.h>
Willy Tarreau7f673c22021-05-08 12:27:42 +020020#include <haproxy/signal-t.h>
Willy Tarreau3f567e42020-05-28 15:29:19 +020021#include <haproxy/thread.h>
Willy Tarreau48fbcae2020-06-03 18:09:46 +020022#include <haproxy/tools.h>
Willy Tarreau2bfefdb2019-05-03 13:52:18 +020023
24
25/*
26 * It relies on timer_create() and timer_settime() which are only available in
27 * this case.
28 */
Willy Tarreaub474f432021-10-08 15:23:26 +020029#if defined(USE_RT) && defined(_POSIX_TIMERS) && (_POSIX_TIMERS > 0) && defined(_POSIX_THREAD_CPUTIME)
30
31/* define a dummy value to designate "no timer". Use only 32 bits. */
32#ifndef TIMER_INVALID
33#define TIMER_INVALID ((timer_t)(unsigned long)(0xfffffffful))
34#endif
35
36static timer_t per_thread_wd_timer[MAX_THREADS];
Willy Tarreau2bfefdb2019-05-03 13:52:18 +020037
Willy Tarreau2bfefdb2019-05-03 13:52:18 +020038/* Setup (or ping) the watchdog timer for thread <thr>. Returns non-zero on
39 * success, zero on failure. It interrupts once per second of CPU time. It
40 * happens that timers based on the CPU time are not automatically re-armed
41 * so we only use the value and leave the interval unset.
42 */
43int wdt_ping(int thr)
44{
45 struct itimerspec its;
46
47 its.it_value.tv_sec = 1; its.it_value.tv_nsec = 0;
48 its.it_interval.tv_sec = 0; its.it_interval.tv_nsec = 0;
Willy Tarreaub474f432021-10-08 15:23:26 +020049 return timer_settime(per_thread_wd_timer[thr], 0, &its, NULL) == 0;
Willy Tarreau2bfefdb2019-05-03 13:52:18 +020050}
51
52/* This is the WDTSIG signal handler */
53void wdt_handler(int sig, siginfo_t *si, void *arg)
54{
55 unsigned long long n, p;
Willy Tarreauadc1f522022-06-27 16:20:13 +020056 ulong thr_bit;
Willy Tarreau03f9b352022-06-27 16:02:24 +020057 int thr, tgrp;
Willy Tarreau2bfefdb2019-05-03 13:52:18 +020058
59 switch (si->si_code) {
60 case SI_TIMER:
61 /* A thread's timer fired, the thread ID is in si_int. We have
62 * no guarantee that the thread handling this signal is in any
63 * way related to the one triggering it, so we need to retrieve
64 * the thread number from there. Note: this thread might
65 * continue to execute in parallel.
66 */
Willy Tarreau02255b22019-05-23 08:36:29 +020067 thr = si->si_value.sival_int;
Willy Tarreau2bfefdb2019-05-03 13:52:18 +020068
69 /* cannot happen unless an unknown timer tries to play with our
70 * nerves. Let's die for now if this happens.
71 */
72 if (thr < 0 || thr >= global.nbthread)
73 break;
74
Willy Tarreau03f9b352022-06-27 16:02:24 +020075 tgrp = ha_thread_info[thr].tgid;
Willy Tarreauadc1f522022-06-27 16:20:13 +020076 thr_bit = ha_thread_info[thr].ltid_bit;
Willy Tarreau45c38e22021-09-30 18:28:49 +020077 p = ha_thread_ctx[thr].prev_cpu_time;
Willy Tarreau21694982021-10-08 15:09:17 +020078 n = now_cpu_time_thread(thr);
Willy Tarreau2bfefdb2019-05-03 13:52:18 +020079
William Lallemandae053b32022-05-13 11:03:39 +020080 /* not yet reached the deadline of 1 sec,
81 * or p wasn't initialized yet
82 */
83 if (!p || n - p < 1000000000UL)
Willy Tarreau2bfefdb2019-05-03 13:52:18 +020084 goto update_and_leave;
85
Willy Tarreau5405c9c2023-02-17 14:55:41 +010086 if ((_HA_ATOMIC_LOAD(&ha_thread_ctx[thr].flags) & TH_FL_SLEEPING) ||
Willy Tarreau1229ef32022-07-01 17:26:15 +020087 (_HA_ATOMIC_LOAD(&ha_tgroup_ctx[tgrp-1].threads_harmless) & thr_bit)) {
Willy Tarreau2bfefdb2019-05-03 13:52:18 +020088 /* This thread is currently doing exactly nothing
89 * waiting in the poll loop (unlikely but possible),
90 * waiting for all other threads to join the rendez-vous
91 * point (common), or waiting for another thread to
92 * finish an isolated operation (unlikely but possible).
93 */
94 goto update_and_leave;
95 }
96
97 /* So the thread indeed appears locked up. In order to be
98 * certain that we're not witnessing an exceptional spike of
99 * CPU usage due to a configuration issue (like running tens
100 * of thousands of tasks in a single loop), we'll check if the
Willy Tarreaua0b99532021-09-30 18:48:37 +0200101 * scheduler is still alive by setting the TH_FL_STUCK flag
Willy Tarreau2bfefdb2019-05-03 13:52:18 +0200102 * that the scheduler clears when switching to the next task.
103 * If it's already set, then it's our second call with no
104 * progress and the thread is dead.
105 */
Willy Tarreaubdcd3252022-06-22 09:19:46 +0200106 if (!(_HA_ATOMIC_LOAD(&ha_thread_ctx[thr].flags) & TH_FL_STUCK)) {
Willy Tarreaua0b99532021-09-30 18:48:37 +0200107 _HA_ATOMIC_OR(&ha_thread_ctx[thr].flags, TH_FL_STUCK);
Willy Tarreau2bfefdb2019-05-03 13:52:18 +0200108 goto update_and_leave;
109 }
110
111 /* No doubt now, there's no hop to recover, die loudly! */
112 break;
Willy Tarreau6414e442021-10-08 15:31:04 +0200113
114#if defined(USE_THREAD) && defined(SI_TKILL) /* Linux uses this */
115
Willy Tarreau2bfefdb2019-05-03 13:52:18 +0200116 case SI_TKILL:
117 /* we got a pthread_kill, stop on it */
118 thr = tid;
119 break;
Willy Tarreau6414e442021-10-08 15:31:04 +0200120
121#elif defined(USE_THREAD) && defined(SI_LWP) /* FreeBSD uses this */
122
123 case SI_LWP:
124 /* we got a pthread_kill, stop on it */
125 thr = tid;
126 break;
127
Willy Tarreau06278152020-03-10 09:26:17 +0100128#endif
Willy Tarreau2bfefdb2019-05-03 13:52:18 +0200129 default:
130 /* unhandled other conditions */
131 return;
132 }
133
134 /* By default we terminate. If we're not on the victim thread, better
135 * bounce the signal there so that we produce a cleaner stack trace
136 * with the other thread interrupted exactly where it was running and
137 * the current one not involved in this.
138 */
Willy Tarreaue58114e2020-03-04 10:53:07 +0100139#ifdef USE_THREAD
Willy Tarreau2bfefdb2019-05-03 13:52:18 +0200140 if (thr != tid)
Willy Tarreau19b18ad2021-10-06 21:43:38 +0200141 ha_tkill(thr, sig);
Willy Tarreau2bfefdb2019-05-03 13:52:18 +0200142 else
Willy Tarreaue58114e2020-03-04 10:53:07 +0100143#endif
Willy Tarreau2bfefdb2019-05-03 13:52:18 +0200144 ha_panic();
145 return;
146
147 update_and_leave:
148 wdt_ping(thr);
149}
150
151int init_wdt_per_thread()
152{
Willy Tarreaub474f432021-10-08 15:23:26 +0200153 if (!clock_setup_signal_timer(&per_thread_wd_timer[tid], WDTSIG, tid))
Willy Tarreau2bfefdb2019-05-03 13:52:18 +0200154 goto fail1;
155
156 if (!wdt_ping(tid))
157 goto fail2;
158
159 return 1;
160
161 fail2:
Willy Tarreaub474f432021-10-08 15:23:26 +0200162 timer_delete(per_thread_wd_timer[tid]);
Willy Tarreau2bfefdb2019-05-03 13:52:18 +0200163 fail1:
Willy Tarreaub474f432021-10-08 15:23:26 +0200164 per_thread_wd_timer[tid] = TIMER_INVALID;
Willy Tarreau2bfefdb2019-05-03 13:52:18 +0200165 ha_warning("Failed to setup watchdog timer for thread %u, disabling lockup detection.\n", tid);
Willy Tarreau7259fa22020-03-04 10:46:13 +0100166 return 1;
Willy Tarreau2bfefdb2019-05-03 13:52:18 +0200167}
168
169void deinit_wdt_per_thread()
170{
Willy Tarreaub474f432021-10-08 15:23:26 +0200171 if (per_thread_wd_timer[tid] != TIMER_INVALID)
172 timer_delete(per_thread_wd_timer[tid]);
Willy Tarreau2bfefdb2019-05-03 13:52:18 +0200173}
174
175/* registers the watchdog signal handler and returns 0. This sets up the signal
176 * handler for WDTSIG, so it must be called once per process.
177 */
178int init_wdt()
179{
180 struct sigaction sa;
181
182 sa.sa_handler = NULL;
183 sa.sa_sigaction = wdt_handler;
184 sigemptyset(&sa.sa_mask);
185 sa.sa_flags = SA_SIGINFO;
186 sigaction(WDTSIG, &sa, NULL);
Christopher Fauletfc633b62020-11-06 15:24:23 +0100187 return ERR_NONE;
Willy Tarreau2bfefdb2019-05-03 13:52:18 +0200188}
189
190REGISTER_POST_CHECK(init_wdt);
191REGISTER_PER_THREAD_INIT(init_wdt_per_thread);
192REGISTER_PER_THREAD_DEINIT(deinit_wdt_per_thread);
193#endif