Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions cmd/zstream/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ CPPCHECKTARGETS += zstream
zstream_SOURCES = \
%D%/zstream.c \
%D%/zstream.h \
%D%/zstream_backtrace.c \
%D%/zstream_backtrace.h \
%D%/zstream_byteswap.c \
%D%/zstream_byteswap.h \
%D%/zstream_chain.c \
Expand All @@ -24,6 +26,9 @@ zstream_SOURCES = \
%D%/zstream_recompress.c \
%D%/zstream_recompress.h \
%D%/zstream_redup.c \
%D%/zstream_selftest.c \
%D%/zstream_selftest.h \
%D%/zstream_selftest_queue.c \
%D%/zstream_token.c \
%D%/zstream_queue.c \
%D%/zstream_queue.h \
Expand Down
24 changes: 24 additions & 0 deletions cmd/zstream/zstream.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,14 @@
* Copyright (c) 2020 by Datto Inc. All rights reserved.
*/

#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "zstream.h"
#include "zstream_util.h"

void
zstream_usage(void)
Expand All @@ -50,9 +53,27 @@ zstream_usage(void)
exit(1);
}

/*
* Set the signal mask to allow THREAD_BACKTRACE_SIGNAL. WATCHDOG_SIGNAL
* must be blocked in all threads so that its intended recipient can listen
* for it with sigwait(), which detects only pending signals.
*/
static void
set_signal_mask(void)
{
sigset_t mask;

safe_pthread_sigmask(SIG_SETMASK, NULL, &mask);
sigaddset(&mask, WATCHDOG_SIGNAL);
sigdelset(&mask, THREAD_BACKTRACE_SIGNAL);
safe_pthread_sigmask(SIG_SETMASK, &mask, NULL);
}

int
main(int argc, char *argv[])
{
set_signal_mask();

char *basename = strrchr(argv[0], '/');
basename = basename ? (basename + 1) : argv[0];
if (argc >= 1 && strcmp(basename, "zstreamdump") == 0)
Expand All @@ -77,6 +98,9 @@ main(int argc, char *argv[])
return (zstream_do_token(argc - 1, argv + 1));
} else if (strcmp(subcommand, "redup") == 0) {
return (zstream_do_redup(argc - 1, argv + 1));
} else if (strcmp(subcommand, "selftest") == 0) {
/* Undocumented; used by the ZFS test suite */
return (zstream_do_selftest(argc - 1, argv + 1));
} else {
zstream_usage();
}
Expand Down
17 changes: 17 additions & 0 deletions cmd/zstream/zstream.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,34 @@
#ifndef _ZSTREAM_H
#define _ZSTREAM_H

#include <signal.h>

#ifdef __cplusplus
extern "C" {
#endif

/*
* Signals used by the watchdog timer and zstream_queue. The signal mask
* must be set properly before any threads are spawned.
*/
#define WATCHDOG_SIGNAL SIGALRM
#define THREAD_BACKTRACE_SIGNAL (SIGRTMIN)

/*
* Establishes the process-wide signal mask. Must be called before any
* thread is created so that every thread inherits the mask without having
* to make any calls of its own.
*/
extern void zstream_signal_init(void);

extern int zstream_do_redup(int, char *[]);
extern int zstream_do_dump(int, char *[]);
extern int zstream_do_decompress(int argc, char *argv[]);
extern int zstream_do_drop_record(int argc, char *argv[]);
extern int zstream_do_recompress(int argc, char *argv[]);
extern int zstream_do_token(int, char *[]);
extern int zstream_do_raw(int, char *[]);
extern int zstream_do_selftest(int, char *[]);
extern void zstream_usage(void) __attribute__((noreturn));

#ifdef __cplusplus
Expand Down
193 changes: 193 additions & 0 deletions cmd/zstream/zstream_backtrace.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
// SPDX-License-Identifier: CDDL-1.0
/*
* CDDL HEADER START
*
* This file and its contents are supplied under the terms of the Common
* Development and Distribution License ("CDDL"), version 1.0. You may only use
* this file in accordance with the terms of version 1.0 of the CDDL.
*
* A full copy of the text of the CDDL should have accompanied this source. A
* copy of the CDDL is also available via the Internet at
* http://www.illumos.org/license/CDDL.
*
* CDDL HEADER END
*/

/*
* Copyright (c) 2026 by Garth Snyder. All rights reserved.
*/

/*
* This is a watchdog timer and multithread backtrace dumper that's used by
* zstream selftest. libspl_backtrace() does all the real work, but it can
* only dump the current thread's stack. We need to get every thread to call
* libspl_backtrace() in an organized sequence. However, there's no "give me
* a list of all pthreads" function in the POSIX API.
*
* Rather than constructing an ad-hoc thread registry, we can approach the
* problem by unblocking SIGRTMIN (an arbitrary choice) and designating a
* handler for it when zstream first starts. All created threads then
* inherit this signal mask and handler.
*
* The SIGRTMIN handler calls libspl_backtrace(), which is signal-handler
* safe. It then signals a semaphore to indicate that it's finished and
* enters pause().
*
* The watchdog itself is a separate thread that sigwait()s for SIGALRM. It
* blocks SIGRTMIN at the thread level and runs libspl_backtrace(). It then
* enters a loop in which it sends SIGRTMIN to the process as a whole and
* runs sem_timedwait() to see if any thread woke up and dumped its
* backtrace. If that call times out, either the receiving thread wedged
* while trying to backtrace or there were no more threads to backtrace.
*
* These two scenarios can be distinguished by calling sigpending(). If a
* SIGRTMIN still shows as being pending on the process, then there was no
* thread to receive it and we are done. If there's no pending signal, then
* some thread did receive the signal but failed to post to the semaphore;
* we print a "thread wedged while backtracing" message and continue the
* loop.
*/

#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <semaphore.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/backtrace.h>
#include <sys/debug.h>
#include <sys/stdtypes.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>

#include "zstream.h"
#include "zstream_backtrace.h"

/*
* Watchdog timeout in seconds. A test that hits this limit is almost
* certainly deadlocked, and the watchdog converts the hang into a test
* failure instead of a stuck test run.
*/
#define WATCHDOG_TIMEOUT_SECS 120
#define MAX_SECS_FOR_BACKTRACE 2

static sem_t sem_thread_bt_complete; /* From thread to watchdog */

/*
* Signal handler for THREAD_BACKTRACE_SIGNAL, run by all threads except the
* watchdog thread
*/
static void
backtrace_self(int signal)
{
(void) signal;
ssize_t dummy __maybe_unused = write(STDERR_FILENO, "\n", 1);
libspl_backtrace(STDERR_FILENO);
sem_post(&sem_thread_bt_complete);

sigset_t mask;
sigfillset(&mask);
sigsuspend(&mask);
}

static void
backtrace_all_threads(void)
{
while (B_TRUE) {
if (kill(getpid(), THREAD_BACKTRACE_SIGNAL) != 0)
err(1, "failed to send thread backtrace signal");
struct timespec deadline;
clock_gettime(CLOCK_REALTIME, &deadline);
deadline.tv_sec += MAX_SECS_FOR_BACKTRACE;
if (sem_timedwait(&sem_thread_bt_complete, &deadline) != 0) {
sigset_t pending;
if (sigpending(&pending) != 0)
err(1, "sigpending failed");
if (sigismember(&pending, THREAD_BACKTRACE_SIGNAL)) {
return;
} else {
warnx("a thread failed to generate a backtrace,"
" continuing...");
}
}
}
}

/*
* Body of the watchdog thread
*/
static void *
watchdog(void *nope)
{
(void) nope;
int signal;
sigset_t bt_mask, dog_mask;

/*
* This thread does its own backtrace, so we block the
* backtrace signal.
*/
sigemptyset(&bt_mask);
sigaddset(&bt_mask, THREAD_BACKTRACE_SIGNAL);
if (pthread_sigmask(SIG_BLOCK, &bt_mask, NULL) != 0)
err(1, "pthread_sigmask failed");

sigemptyset(&dog_mask);
sigaddset(&dog_mask, WATCHDOG_SIGNAL);

int rc = sigwait(&dog_mask, &signal);
if (rc != 0) {
errno = rc;
err(1, "watchdog sigwait failed");
} else if (signal != WATCHDOG_SIGNAL) {
errx(1, "unexpected signal %d received by watchdog", signal);
}

fprintf(stderr, "\n\nWATCHDOG TIMER EXPIRED\n"
"Dumping backtrace for all threads...\n\n");
fflush(stderr);
libspl_backtrace(STDERR_FILENO);
backtrace_all_threads();
fprintf(stderr, "\nAll threads backtraced, exiting.\n");
exit(1);
}

/*
* This function must be called before any (extra) threads are created.
*/
void
watchdog_init(void)
{
if (sem_init(&sem_thread_bt_complete, 0, 0) != 0)
err(1, "watchdog sem_init failed");

pthread_t tid;
if (pthread_create(&tid, NULL, watchdog, NULL) != 0)
err(1, "pthread_create for watchdog failed");
if (pthread_setname_np(tid, "watchdog") != 0)
err(1, "could not set name of watchdog thread");
if (pthread_detach(tid) != 0)
err(1, "failed to detach watchdog thread");

struct sigaction sa = {
.sa_handler = backtrace_self,
.sa_flags = SA_RESTART
};
if (sigaction(THREAD_BACKTRACE_SIGNAL, &sa, NULL) != 0)
err(1, "backtrace sigaction failed");
}

void
watchdog_arm(void)
{
(void) alarm(WATCHDOG_TIMEOUT_SECS);
}

void
watchdog_disarm(void)
{
(void) alarm(0);
}
40 changes: 40 additions & 0 deletions cmd/zstream/zstream_backtrace.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// SPDX-License-Identifier: CDDL-1.0
/*
* CDDL HEADER START
*
* This file and its contents are supplied under the terms of the Common
* Development and Distribution License ("CDDL"), version 1.0. You may only use
* this file in accordance with the terms of version 1.0 of the CDDL.
*
* A full copy of the text of the CDDL should have accompanied this source. A
* copy of the CDDL is also available via the Internet at
* http://www.illumos.org/license/CDDL.
*
* CDDL HEADER END
*/

/*
* Copyright (c) 2026 by Garth Snyder. All rights reserved.
*/

#ifndef _ZSTREAM_BACKTRACE_H
#define _ZSTREAM_BACKTRACE_H

#ifdef __cplusplus
extern "C" {
#endif

extern void
watchdog_init(void);

void
watchdog_arm(void);

void
watchdog_disarm(void);

#ifdef __cplusplus
}
#endif

#endif /* _ZSTREAM_BACKTRACE_H */
2 changes: 2 additions & 0 deletions cmd/zstream/zstream_chain.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@

#include "zstream_chain.h"
#include "zstream_queue.h"
#include "zstream_selftest.h"
#include "zstream_util.h"

#define MAX_CHAIN_LENGTH 32

Expand Down
Loading
Loading