From 79f7fade5638288ad654be0a84ae59b0956f4313 Mon Sep 17 00:00:00 2001 From: bluhm Date: Thu, 29 Apr 2021 13:39:22 +0000 Subject: [PATCH] Extend the fork and exit test with threads. Simultaneously kill 30 processes with 30 threads each. --- regress/sys/kern/fork-exit/Makefile | 29 ++++- regress/sys/kern/fork-exit/fork-exit.c | 161 +++++++++++++++++++------ 2 files changed, 144 insertions(+), 46 deletions(-) diff --git a/regress/sys/kern/fork-exit/Makefile b/regress/sys/kern/fork-exit/Makefile index 5104429ffdd..4473c431e69 100644 --- a/regress/sys/kern/fork-exit/Makefile +++ b/regress/sys/kern/fork-exit/Makefile @@ -1,4 +1,4 @@ -# $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 # @@ -17,24 +17,41 @@ # 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 diff --git a/regress/sys/kern/fork-exit/fork-exit.c b/regress/sys/kern/fork-exit/fork-exit.c index b66e4a7dc34..3d96beb85b6 100644 --- a/regress/sys/kern/fork-exit/fork-exit.c +++ b/regress/sys/kern/fork-exit/fork-exit.c @@ -1,4 +1,4 @@ -/* $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 @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -31,48 +32,92 @@ 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); } @@ -107,7 +152,7 @@ main(int argc, char *argv[]) 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; @@ -122,34 +167,49 @@ main(int argc, char *argv[]) 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 */ @@ -160,16 +220,35 @@ main(int argc, char *argv[]) /* 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 */ @@ -177,25 +256,27 @@ main(int argc, char *argv[]) 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 { @@ -204,7 +285,7 @@ main(int argc, char *argv[]) } } } - } while (waiting); + } /* kill all children simultaneously, parent exits in signal handler */ if (signal(SIGTERM, sigexit) == SIG_ERR) -- 2.20.1