xref: /freebsd/sys/contrib/openzfs/cmd/zstream/zstream_backtrace.c (revision 22649d4dba730d46244fd2dff4fd174903c8379f)
1*22649d4dSMartin Matuska // SPDX-License-Identifier: CDDL-1.0
2*22649d4dSMartin Matuska /*
3*22649d4dSMartin Matuska  * This file and its contents are supplied under the terms of the
4*22649d4dSMartin Matuska  * Common Development and Distribution License ("CDDL"), version 1.0.
5*22649d4dSMartin Matuska  * You may only use this file in accordance with the terms of version
6*22649d4dSMartin Matuska  * 1.0 of the CDDL.
7*22649d4dSMartin Matuska  *
8*22649d4dSMartin Matuska  * A full copy of the text of the CDDL should have accompanied this
9*22649d4dSMartin Matuska  * source.  A copy of the CDDL is also available via the Internet at
10*22649d4dSMartin Matuska  * https://opensource.org/license/CDDL-1.0.
11*22649d4dSMartin Matuska  */
12*22649d4dSMartin Matuska 
13*22649d4dSMartin Matuska /*
14*22649d4dSMartin Matuska  * Copyright (c) 2026 by Garth Snyder. All rights reserved.
15*22649d4dSMartin Matuska  */
16*22649d4dSMartin Matuska 
17*22649d4dSMartin Matuska /*
18*22649d4dSMartin Matuska  * This is a watchdog timer and multithread backtrace dumper that's used by
19*22649d4dSMartin Matuska  * zstream selftest. libspl_backtrace() does all the real work, but it can
20*22649d4dSMartin Matuska  * only dump the current thread's stack. We need to get every thread to call
21*22649d4dSMartin Matuska  * libspl_backtrace() in an organized sequence. However, there's no "give me
22*22649d4dSMartin Matuska  * a list of all pthreads" function in the POSIX API.
23*22649d4dSMartin Matuska  *
24*22649d4dSMartin Matuska  * Rather than constructing an ad-hoc thread registry, we can approach the
25*22649d4dSMartin Matuska  * problem by unblocking THREAD_BACKTRACE_SIGNAL (an arbitrary choice) when
26*22649d4dSMartin Matuska  * zstream first starts. All created threads then inherit this signal mask.
27*22649d4dSMartin Matuska  *
28*22649d4dSMartin Matuska  * In selftest mode, we add the backtrace dumper as a handler for
29*22649d4dSMartin Matuska  * THREAD_BACKTRACE_SIGNAL. The handler calls libspl_backtrace(), which is
30*22649d4dSMartin Matuska  * signal-handler safe. It then signals a semaphore to indicate that the
31*22649d4dSMartin Matuska  * current thread has finished its dump and suspends itself.
32*22649d4dSMartin Matuska  *
33*22649d4dSMartin Matuska  * The watchdog supervisor is a separate thread that sigwait()s for SIGALRM.
34*22649d4dSMartin Matuska  * It blocks THREAD_BACKTRACE_SIGNAL and runs its own libspl_backtrace(). It
35*22649d4dSMartin Matuska  * then enters a loop in which it sends THREAD_BACKTRACE_SIGNAL to the
36*22649d4dSMartin Matuska  * process as a whole and runs sem_timedwait() to see if any thread woke up
37*22649d4dSMartin Matuska  * and dumped its backtrace. If that call times out, either the receiving
38*22649d4dSMartin Matuska  * thread wedged while trying to backtrace or there were no more threads to
39*22649d4dSMartin Matuska  * backtrace.
40*22649d4dSMartin Matuska  *
41*22649d4dSMartin Matuska  * These two scenarios can be distinguished by calling sigpending(). If
42*22649d4dSMartin Matuska  * THREAD_BACKTRACE_SIGNAL still shows as being pending on the process, then
43*22649d4dSMartin Matuska  * there was no thread to receive it and we are done. If there's no pending
44*22649d4dSMartin Matuska  * signal, then some thread did receive the signal but failed to post to the
45*22649d4dSMartin Matuska  * semaphore; we print a "thread wedged while backtracing" message and
46*22649d4dSMartin Matuska  * continue the loop.
47*22649d4dSMartin Matuska  */
48*22649d4dSMartin Matuska 
49*22649d4dSMartin Matuska #include <err.h>
50*22649d4dSMartin Matuska #include <errno.h>
51*22649d4dSMartin Matuska #include <semaphore.h>
52*22649d4dSMartin Matuska #include <signal.h>
53*22649d4dSMartin Matuska #include <stdio.h>
54*22649d4dSMartin Matuska #include <stdlib.h>
55*22649d4dSMartin Matuska #include <sys/backtrace.h>
56*22649d4dSMartin Matuska #include <sys/debug.h>
57*22649d4dSMartin Matuska #include <sys/stdtypes.h>
58*22649d4dSMartin Matuska #include <sys/types.h>
59*22649d4dSMartin Matuska #include <time.h>
60*22649d4dSMartin Matuska #include <unistd.h>
61*22649d4dSMartin Matuska 
62*22649d4dSMartin Matuska #include "zstream.h"
63*22649d4dSMartin Matuska #include "zstream_backtrace.h"
64*22649d4dSMartin Matuska #include "zstream_util.h"
65*22649d4dSMartin Matuska 
66*22649d4dSMartin Matuska /*
67*22649d4dSMartin Matuska  * Watchdog timeout in seconds. A test that hits this limit is almost
68*22649d4dSMartin Matuska  * certainly deadlocked, and the watchdog converts the hang into a test
69*22649d4dSMartin Matuska  * failure instead of a stuck test run.
70*22649d4dSMartin Matuska  */
71*22649d4dSMartin Matuska #define	WATCHDOG_TIMEOUT_SECS	120
72*22649d4dSMartin Matuska #define	MAX_SECS_FOR_BACKTRACE	2
73*22649d4dSMartin Matuska 
74*22649d4dSMartin Matuska static sem_t sem_thread_bt_complete;	/* thread -> watchdog */
75*22649d4dSMartin Matuska 
76*22649d4dSMartin Matuska /*
77*22649d4dSMartin Matuska  * Signal handler for THREAD_BACKTRACE_SIGNAL, run by all threads except the
78*22649d4dSMartin Matuska  * watchdog thread
79*22649d4dSMartin Matuska  */
80*22649d4dSMartin Matuska static void
backtrace_self(int signal)81*22649d4dSMartin Matuska backtrace_self(int signal)
82*22649d4dSMartin Matuska {
83*22649d4dSMartin Matuska 	(void) signal;
84*22649d4dSMartin Matuska 	ssize_t dummy __maybe_unused = write(STDERR_FILENO, "\n", 1);
85*22649d4dSMartin Matuska 	libspl_backtrace(STDERR_FILENO);
86*22649d4dSMartin Matuska 	sem_post(&sem_thread_bt_complete);
87*22649d4dSMartin Matuska 
88*22649d4dSMartin Matuska 	sigset_t mask;
89*22649d4dSMartin Matuska 	sigfillset(&mask);
90*22649d4dSMartin Matuska 	sigsuspend(&mask);
91*22649d4dSMartin Matuska }
92*22649d4dSMartin Matuska 
93*22649d4dSMartin Matuska static void
backtrace_all_threads(void)94*22649d4dSMartin Matuska backtrace_all_threads(void)
95*22649d4dSMartin Matuska {
96*22649d4dSMartin Matuska 	while (B_TRUE) {
97*22649d4dSMartin Matuska 		if (kill(getpid(), THREAD_BACKTRACE_SIGNAL) != 0)
98*22649d4dSMartin Matuska 			err(1, "failed to send thread backtrace signal");
99*22649d4dSMartin Matuska 		struct timespec deadline;
100*22649d4dSMartin Matuska 		clock_gettime(CLOCK_REALTIME, &deadline);
101*22649d4dSMartin Matuska 		deadline.tv_sec += MAX_SECS_FOR_BACKTRACE;
102*22649d4dSMartin Matuska 		if (sem_timedwait(&sem_thread_bt_complete, &deadline) != 0) {
103*22649d4dSMartin Matuska 			sigset_t pending;
104*22649d4dSMartin Matuska 			if (sigpending(&pending) != 0)
105*22649d4dSMartin Matuska 				err(1, "sigpending failed");
106*22649d4dSMartin Matuska 			if (sigismember(&pending, THREAD_BACKTRACE_SIGNAL)) {
107*22649d4dSMartin Matuska 				return;
108*22649d4dSMartin Matuska 			} else {
109*22649d4dSMartin Matuska 				warnx("a thread failed to generate a backtrace,"
110*22649d4dSMartin Matuska 				    " continuing...");
111*22649d4dSMartin Matuska 			}
112*22649d4dSMartin Matuska 		}
113*22649d4dSMartin Matuska 	}
114*22649d4dSMartin Matuska }
115*22649d4dSMartin Matuska 
116*22649d4dSMartin Matuska /*
117*22649d4dSMartin Matuska  * Body of the watchdog thread
118*22649d4dSMartin Matuska  */
119*22649d4dSMartin Matuska static void *
watchdog(void * nope)120*22649d4dSMartin Matuska watchdog(void *nope)
121*22649d4dSMartin Matuska {
122*22649d4dSMartin Matuska 	(void) nope;
123*22649d4dSMartin Matuska 	int signal;
124*22649d4dSMartin Matuska 	sigset_t bt_mask, dog_mask;
125*22649d4dSMartin Matuska 
126*22649d4dSMartin Matuska 	/*
127*22649d4dSMartin Matuska 	 * This thread does its own backtrace, so we block the
128*22649d4dSMartin Matuska 	 * backtrace signal.
129*22649d4dSMartin Matuska 	 */
130*22649d4dSMartin Matuska 	sigemptyset(&bt_mask);
131*22649d4dSMartin Matuska 	sigaddset(&bt_mask, THREAD_BACKTRACE_SIGNAL);
132*22649d4dSMartin Matuska 	if (pthread_sigmask(SIG_BLOCK, &bt_mask, NULL) != 0)
133*22649d4dSMartin Matuska 		err(1, "pthread_sigmask failed");
134*22649d4dSMartin Matuska 
135*22649d4dSMartin Matuska 	sigemptyset(&dog_mask);
136*22649d4dSMartin Matuska 	sigaddset(&dog_mask, WATCHDOG_SIGNAL);
137*22649d4dSMartin Matuska 
138*22649d4dSMartin Matuska 	int rc = sigwait(&dog_mask, &signal);
139*22649d4dSMartin Matuska 	if (rc != 0) {
140*22649d4dSMartin Matuska 		errno = rc;
141*22649d4dSMartin Matuska 		err(1, "watchdog sigwait failed");
142*22649d4dSMartin Matuska 	} else if (signal != WATCHDOG_SIGNAL) {
143*22649d4dSMartin Matuska 		errx(1, "unexpected signal %d received by watchdog", signal);
144*22649d4dSMartin Matuska 	}
145*22649d4dSMartin Matuska 
146*22649d4dSMartin Matuska 	fprintf(stderr, "\n\nWATCHDOG TIMER EXPIRED\n"
147*22649d4dSMartin Matuska 	    "Dumping backtrace for all threads...\n\n");
148*22649d4dSMartin Matuska 	fflush(stderr);
149*22649d4dSMartin Matuska 	libspl_backtrace(STDERR_FILENO);
150*22649d4dSMartin Matuska 	backtrace_all_threads();
151*22649d4dSMartin Matuska 	fprintf(stderr, "\nAll threads backtraced, exiting.\n");
152*22649d4dSMartin Matuska 	exit(1);
153*22649d4dSMartin Matuska }
154*22649d4dSMartin Matuska 
155*22649d4dSMartin Matuska void
watchdog_init(void)156*22649d4dSMartin Matuska watchdog_init(void)
157*22649d4dSMartin Matuska {
158*22649d4dSMartin Matuska 	if (sem_init(&sem_thread_bt_complete, 0, 0) != 0)
159*22649d4dSMartin Matuska 		err(1, "watchdog sem_init failed");
160*22649d4dSMartin Matuska 
161*22649d4dSMartin Matuska 	safe_create_thread(watchdog, NULL, "watchdog", B_TRUE);
162*22649d4dSMartin Matuska 
163*22649d4dSMartin Matuska 	struct sigaction sa = {
164*22649d4dSMartin Matuska 		.sa_handler = backtrace_self,
165*22649d4dSMartin Matuska 		.sa_flags = SA_RESTART
166*22649d4dSMartin Matuska 	};
167*22649d4dSMartin Matuska 	if (sigaction(THREAD_BACKTRACE_SIGNAL, &sa, NULL) != 0)
168*22649d4dSMartin Matuska 		err(1, "backtrace sigaction failed");
169*22649d4dSMartin Matuska }
170*22649d4dSMartin Matuska 
171*22649d4dSMartin Matuska void
watchdog_arm(void)172*22649d4dSMartin Matuska watchdog_arm(void)
173*22649d4dSMartin Matuska {
174*22649d4dSMartin Matuska 	(void) alarm(WATCHDOG_TIMEOUT_SECS);
175*22649d4dSMartin Matuska }
176*22649d4dSMartin Matuska 
177*22649d4dSMartin Matuska void
watchdog_disarm(void)178*22649d4dSMartin Matuska watchdog_disarm(void)
179*22649d4dSMartin Matuska {
180*22649d4dSMartin Matuska 	(void) alarm(0);
181*22649d4dSMartin Matuska }
182