blob: 1cfb2b6bde0acf067e74cd653e44ab2219bbe5d0 [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
15#include <common/config.h>
16#include <common/debug.h>
17#include <common/hathreads.h>
18#include <common/initcall.h>
19#include <common/standard.h>
20#include <types/global.h>
Olivier Houchard75893092020-03-18 13:07:19 +010021#include <types/signal.h>
Willy Tarreau2bfefdb2019-05-03 13:52:18 +020022#include <proto/log.h>
23
24
25/*
26 * It relies on timer_create() and timer_settime() which are only available in
27 * this case.
28 */
Willy Tarreaubc1b8202019-05-23 10:20:55 +020029#if defined(USE_THREAD) && defined(USE_RT) && (_POSIX_TIMERS > 0) && defined(_POSIX_THREAD_CPUTIME)
Willy Tarreau2bfefdb2019-05-03 13:52:18 +020030
Willy Tarreau2bfefdb2019-05-03 13:52:18 +020031/* Setup (or ping) the watchdog timer for thread <thr>. Returns non-zero on
32 * success, zero on failure. It interrupts once per second of CPU time. It
33 * happens that timers based on the CPU time are not automatically re-armed
34 * so we only use the value and leave the interval unset.
35 */
36int wdt_ping(int thr)
37{
38 struct itimerspec its;
39
40 its.it_value.tv_sec = 1; its.it_value.tv_nsec = 0;
41 its.it_interval.tv_sec = 0; its.it_interval.tv_nsec = 0;
42 return timer_settime(thread_info[thr].wd_timer, 0, &its, NULL) == 0;
43}
44
45/* This is the WDTSIG signal handler */
46void wdt_handler(int sig, siginfo_t *si, void *arg)
47{
48 unsigned long long n, p;
49 int thr;
50
51 switch (si->si_code) {
52 case SI_TIMER:
53 /* A thread's timer fired, the thread ID is in si_int. We have
54 * no guarantee that the thread handling this signal is in any
55 * way related to the one triggering it, so we need to retrieve
56 * the thread number from there. Note: this thread might
57 * continue to execute in parallel.
58 */
Willy Tarreau02255b22019-05-23 08:36:29 +020059 thr = si->si_value.sival_int;
Willy Tarreau2bfefdb2019-05-03 13:52:18 +020060
61 /* cannot happen unless an unknown timer tries to play with our
62 * nerves. Let's die for now if this happens.
63 */
64 if (thr < 0 || thr >= global.nbthread)
65 break;
66
67 p = thread_info[thr].prev_cpu_time;
68 n = now_cpu_time_thread(&thread_info[thr]);
69
70 /* not yet reached the deadline of 1 sec */
71 if (n - p < 1000000000UL)
72 goto update_and_leave;
73
Willy Tarreau445b2b72019-07-31 19:20:39 +020074 if ((threads_harmless_mask|sleeping_thread_mask|threads_to_dump) & (1UL << thr)) {
Willy Tarreau2bfefdb2019-05-03 13:52:18 +020075 /* This thread is currently doing exactly nothing
76 * waiting in the poll loop (unlikely but possible),
77 * waiting for all other threads to join the rendez-vous
78 * point (common), or waiting for another thread to
79 * finish an isolated operation (unlikely but possible).
80 */
81 goto update_and_leave;
82 }
83
84 /* So the thread indeed appears locked up. In order to be
85 * certain that we're not witnessing an exceptional spike of
86 * CPU usage due to a configuration issue (like running tens
87 * of thousands of tasks in a single loop), we'll check if the
88 * scheduler is still alive by setting the TI_FL_STUCK flag
89 * that the scheduler clears when switching to the next task.
90 * If it's already set, then it's our second call with no
91 * progress and the thread is dead.
92 */
93 if (!(thread_info[thr].flags & TI_FL_STUCK)) {
94 _HA_ATOMIC_OR(&thread_info[thr].flags, TI_FL_STUCK);
95 goto update_and_leave;
96 }
97
98 /* No doubt now, there's no hop to recover, die loudly! */
99 break;
Willy Tarreau27232152020-03-10 09:26:17 +0100100#ifdef USE_THREAD
Willy Tarreau2bfefdb2019-05-03 13:52:18 +0200101 case SI_TKILL:
102 /* we got a pthread_kill, stop on it */
103 thr = tid;
104 break;
Willy Tarreau27232152020-03-10 09:26:17 +0100105#endif
Willy Tarreau2bfefdb2019-05-03 13:52:18 +0200106 default:
107 /* unhandled other conditions */
108 return;
109 }
110
111 /* By default we terminate. If we're not on the victim thread, better
112 * bounce the signal there so that we produce a cleaner stack trace
113 * with the other thread interrupted exactly where it was running and
114 * the current one not involved in this.
115 */
116 if (thr != tid)
117 pthread_kill(thread_info[thr].pthread, sig);
118 else
119 ha_panic();
120 return;
121
122 update_and_leave:
123 wdt_ping(thr);
124}
125
126int init_wdt_per_thread()
127{
128 struct sigevent sev;
129 sigset_t set;
130
131 /* unblock the WDTSIG signal we intend to use */
132 sigemptyset(&set);
133 sigaddset(&set, WDTSIG);
134 ha_sigmask(SIG_UNBLOCK, &set, NULL);
135
136 /* this timer will signal WDTSIG when it fires, with tid in the si_int
137 * field (important since any thread will receive the signal).
138 */
139 sev.sigev_notify = SIGEV_SIGNAL;
140 sev.sigev_signo = WDTSIG;
141 sev.sigev_value.sival_int = tid;
142 if (timer_create(ti->clock_id, &sev, &ti->wd_timer) == -1)
143 goto fail1;
144
145 if (!wdt_ping(tid))
146 goto fail2;
147
148 return 1;
149
150 fail2:
151 timer_delete(ti->wd_timer);
152 fail1:
153 ti->wd_timer = TIMER_INVALID;
154 ha_warning("Failed to setup watchdog timer for thread %u, disabling lockup detection.\n", tid);
Willy Tarreau6a44bde2020-03-04 10:46:13 +0100155 return 1;
Willy Tarreau2bfefdb2019-05-03 13:52:18 +0200156}
157
158void deinit_wdt_per_thread()
159{
160 if (ti->wd_timer != TIMER_INVALID)
161 timer_delete(ti->wd_timer);
162}
163
164/* registers the watchdog signal handler and returns 0. This sets up the signal
165 * handler for WDTSIG, so it must be called once per process.
166 */
167int init_wdt()
168{
169 struct sigaction sa;
170
171 sa.sa_handler = NULL;
172 sa.sa_sigaction = wdt_handler;
173 sigemptyset(&sa.sa_mask);
174 sa.sa_flags = SA_SIGINFO;
175 sigaction(WDTSIG, &sa, NULL);
176 return 0;
177}
178
179REGISTER_POST_CHECK(init_wdt);
180REGISTER_PER_THREAD_INIT(init_wdt_per_thread);
181REGISTER_PER_THREAD_DEINIT(deinit_wdt_per_thread);
182#endif