Extend the fork and exit test with threads. Simultaneously kill
authorbluhm <bluhm@openbsd.org>
Thu, 29 Apr 2021 13:39:22 +0000 (13:39 +0000)
committerbluhm <bluhm@openbsd.org>
Thu, 29 Apr 2021 13:39:22 +0000 (13:39 +0000)
30 processes with 30 threads each.

regress/sys/kern/fork-exit/Makefile
regress/sys/kern/fork-exit/fork-exit.c

index 5104429..4473c43 100644 (file)
@@ -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 <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>
index b66e4a7..3d96beb 100644 (file)
@@ -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 <bluhm@openbsd.org>
@@ -23,6 +23,7 @@
 #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);
 }
 
@@ -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)