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