-# $OpenBSD: Makefile,v 1.1 2021/04/28 17:59:53 bluhm Exp $
+# $OpenBSD: Makefile,v 1.2 2021/04/29 13:39:22 bluhm Exp $
# Copyright (c) 2021 Alexander Bluhm <bluhm@openbsd.org>
#
# To hunt kernel bugs during exit, terminate processes simultaneously.
# Fork 300 children that sleep. Kill them together as process group.
# Sleeping can optionally be done with individual memory layout by
-# executing sleep(1).
+# executing sleep(1). Optionally each child may create threads.
+# Test killing 30 processes with 30 threads each.
PROG= fork-exit
+LDADD= -lpthread
WARNINGS= yes
REGRESS_TARGETS += run-fork1-exit
-run-fork1-exit:
+run-fork1-exit: ${PROG}
# test forking a single child
- ./fork-exit
+ ulimit -p 500 -n 1000; ./fork-exit
REGRESS_TARGETS += run-fork-exit
-run-fork-exit:
+run-fork-exit: ${PROG}
# fork 300 children and kill them simultaneously as process group
ulimit -p 500 -n 1000; ./fork-exit -p 300
REGRESS_TARGETS += run-fork-exec-exit
-run-fork-exec-exit:
+run-fork-exec-exit: ${PROG}
# fork 300 children, exec sleep programs, and kill process group
ulimit -p 500 -n 1000; ./fork-exit -e -p 300
+REGRESS_TARGETS += run-fork1-thread1
+run-fork1-thread1: ${PROG}
+ # fork a single child and create one thread
+ ulimit -p 500 -n 1000; ./fork-exit -t 1
+
+REGRESS_TARGETS += run-fork1-thread
+run-fork1-thread: ${PROG}
+ # fork a single child and create 1000 threads
+ ulimit -p 500 -n 1000; ./fork-exit -t 1000
+
+REGRESS_TARGETS += run-fork-thread
+run-fork-thread: ${PROG}
+ # fork 30 children each with 30 threads and kill process group
+ ulimit -p 500 -n 1000; ./fork-exit -p 30 -t 30
+
.include <bsd.regress.mk>
-/* $OpenBSD: fork-exit.c,v 1.1 2021/04/28 17:59:53 bluhm Exp $ */
+/* $OpenBSD: fork-exit.c,v 1.2 2021/04/29 13:39:22 bluhm Exp $ */
/*
* Copyright (c) 2021 Alexander Bluhm <bluhm@openbsd.org>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
+#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
int execute = 0;
int daemonize = 0;
int procs = 1;
+int threads = 0;
int timeout = 30;
+pthread_barrier_t thread_barrier;
+char timeoutstr[sizeof("-2147483647")];
+
static void __dead
usage(void)
{
- fprintf(stderr, "fork-exit [-ed] [-p procs] [-t timeout]\n"
+ fprintf(stderr, "fork-exit [-ed] [-p procs] [-t threads] [-T timeout]\n"
" -e child execs sleep(1), default call sleep(3)\n"
- " -d daemonize, if already process group leader\n"
+ " -d daemonize, use if already process group leader\n"
" -p procs number of processes to fork, default 1\n"
- " -t timeout parent and children will exit, default 30 sec\n");
+ " -t threads number of threads to create, default 0\n"
+ " -T timeout parent and children will exit, default 30 sec\n");
exit(2);
}
+static void * __dead
+run_thread(void *arg)
+{
+ int error;
+
+ error = pthread_barrier_wait(&thread_barrier);
+ if (error && error != PTHREAD_BARRIER_SERIAL_THREAD)
+ errc(1, error, "pthread_barrier_wait");
+
+ if (sleep(timeout) != 0)
+ err(1, "sleep %d", timeout);
+
+ /* should not happen */
+ _exit(0);
+}
+
+static void
+create_threads(void)
+{
+ pthread_t *thrs;
+ int i, error;
+
+ error = pthread_barrier_init(&thread_barrier, NULL, threads + 1);
+ if (error)
+ errc(1, errno, "pthread_barrier_init");
+
+ thrs = reallocarray(NULL, threads, sizeof(pthread_t));
+ if (thrs == NULL)
+ err(1, "thrs");
+
+ for (i = 0; i < threads; i++) {
+ error = pthread_create(&thrs[i], NULL, run_thread, NULL);
+ if (error)
+ errc(1, error, "pthread_create");
+ }
+
+ error = pthread_barrier_wait(&thread_barrier);
+ if (error && error != PTHREAD_BARRIER_SERIAL_THREAD)
+ errc(1, error, "pthread_barrier_wait");
+
+ /* return to close child's pipe and sleep */
+}
+
static void __dead
exec_sleep(void)
{
- execl("/bin/sleep", "sleep", "30", NULL);
+ execl("/bin/sleep", "sleep", timeoutstr, NULL);
err(1, "exec sleep");
}
-static void
-fork_sleep(int fd)
+static void __dead
+run_child(int fd)
{
- switch (fork()) {
- case -1:
- err(1, "fork");
- case 0:
- break;
- default:
- return;
- }
/* close pipe to parent and sleep until killed */
if (execute) {
if (fcntl(fd, F_SETFD, FD_CLOEXEC))
err(1, "fcntl FD_CLOEXEC");
exec_sleep();
} else {
+ if (threads)
+ create_threads();
if (close(fd) == -1)
- err(1, "close write");
+ err(1, "close child");
if (sleep(timeout) != 0)
err(1, "sleep %d", timeout);
}
+
+ /* should not happen */
_exit(0);
}
pid_t pgrp;
struct timeval tv;
- while ((ch = getopt(argc, argv, "edp:t:")) != -1) {
+ while ((ch = getopt(argc, argv, "edp:T:t:")) != -1) {
switch (ch) {
case 'e':
execute = 1;
optarg);
break;
case 't':
+ threads = strtonum(optarg, 0, INT_MAX, &errstr);
+ if (errstr != NULL)
+ errx(1, "number of threads is %s: %s", errstr,
+ optarg);
+ break;
+ case 'T':
timeout = strtonum(optarg, 0, INT_MAX, &errstr);
if (errstr != NULL)
errx(1, "timeout is %s: %s", errstr, optarg);
+ break;
default:
usage();
}
}
+ if (execute) {
+ int ret;
+
+ if (threads > 0)
+ errx(1, "execute sleep cannot be used with threads");
+
+ ret = snprintf(timeoutstr, sizeof(timeoutstr), "%d", timeout);
+ if (ret < 0 || (size_t)ret >= sizeof(timeoutstr))
+ err(1, "snprintf");
+ }
/* become process group leader */
+ if (daemonize) {
+ /* get rid of process leadership */
+ switch (fork()) {
+ case -1:
+ err(1, "fork parent");
+ case 0:
+ break;
+ default:
+ /* parent leaves orphan behind to do the work */
+ _exit(0);
+ }
+ }
pgrp = setsid();
if (pgrp == -1) {
- if (errno == EPERM && daemonize) {
- /* get rid of leadership */
- switch (fork()) {
- case -1:
- err(1, "fork parent");
- case 0:
- /* try again */
- pgrp = setsid();
- break;
- default:
- _exit(0);
- }
- }
if (!daemonize)
warnx("try -d to become process group leader");
- if (pgrp == -1)
- err(1, "setsid");
+ err(1, "setsid");
}
/* create pipes to keep in contact with children */
/* fork child processes and pass writing end of pipe */
for (i = 0; i < procs; i++) {
- int pipefds[2];
+ int pipefds[2], error;
if (pipe(pipefds) == -1)
err(1, "pipe");
if (fdmax < pipefds[0])
fdmax = pipefds[0];
rfds[i] = pipefds[0];
- fork_sleep(pipefds[1]);
- if (close(pipefds[1]) == -1)
- err(1, "close parent");
+
+ switch (fork()) {
+ case -1:
+ /* resource temporarily unavailable may happen */
+ error = errno;
+ /* reap children, but not parent */
+ signal(SIGTERM, SIG_IGN);
+ kill(-pgrp, SIGTERM);
+ errc(1, error, "fork child");
+ case 0:
+ /* child closes reading end, read is for the parent */
+ if (close(pipefds[0]) == -1)
+ err(1, "close read");
+ run_child(pipefds[1]);
+ /* cannot happen */
+ _exit(0);
+ default:
+ /* parent closes writing end, write is for the child */
+ if (close(pipefds[1]) == -1)
+ err(1, "close write");
+ break;
+ }
}
/* create select mask with all reading ends of child pipes */
fdset = calloc(fdlen, sizeof(fd_mask));
if (fdset == NULL)
err(1, "fdset");
+ waiting = 0;
for (i = 0; i < procs; i++) {
FD_SET(rfds[i], fdset);
+ waiting = 1;
}
/* wait until all child processes are waiting */
- do {
- waiting = 0;
+ while (waiting) {
tv.tv_sec = timeout;
tv.tv_usec = 0;
errno = ETIMEDOUT;
if (select(fdmax + 1, fdset, NULL, NULL, &tv) <= 0)
err(1, "select");
+ waiting = 0;
/* remove fd of children that closed their end */
for (i = 0; i < procs; i++) {
if (rfds[i] >= 0) {
if (FD_ISSET(rfds[i], fdset)) {
if (close(rfds[i]) == -1)
- err(1, "close read");
+ err(1, "close parent");
FD_CLR(rfds[i], fdset);
rfds[i] = -1;
} else {
}
}
}
- } while (waiting);
+ }
/* kill all children simultaneously, parent exits in signal handler */
if (signal(SIGTERM, sigexit) == SIG_ERR)