1 /* $OpenBSD: test-thundering-herd.c,v 1.2 2019/11/14 21:17:00 anton Exp $ */ 2 3 /* 4 * Copyright (c) 2019 Anton Lindqvist <anton@openbsd.org> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 /* 20 * Verify correctness when multiple threads are waiting on I/O in either 21 * pipe_read(9) or pipe_write(9). 22 * 23 * Multiple threads are put in this waiting state. The parent thread then 24 * performs a read/write operation on the pipe causing all sleeping threads to 25 * be woken up; only to end up sleeping in pipelock(9). The parent thread then 26 * either closes the pipe or sends a signal to all threads, which must cause all 27 * threads to abort its pipe operation. 28 */ 29 30 #include <err.h> 31 #include <errno.h> 32 #include <stdio.h> 33 #include <stdlib.h> 34 #include <unistd.h> 35 36 #include "pipe.h" 37 38 #define NCHILD 4 39 40 struct context { 41 const char *c_ident; 42 int c_id; 43 int c_read; 44 45 int c_pipe[2]; 46 int c_cv[2]; 47 48 char *c_buf; 49 size_t c_bufsiz; 50 }; 51 52 static int test_thundering_herd(int, int); 53 54 static pid_t block_proc(const struct context *); 55 56 int 57 test_thundering_herd_read_signal(void) 58 { 59 60 return test_thundering_herd(1, 1); 61 } 62 63 int 64 test_thundering_herd_read_wakeup(void) 65 { 66 67 return test_thundering_herd(1, 0); 68 } 69 70 int 71 test_thundering_herd_write_signal(void) 72 { 73 74 return test_thundering_herd(0, 1); 75 } 76 77 int 78 test_thundering_herd_write_wakeup(void) 79 { 80 81 return test_thundering_herd(0, 0); 82 } 83 84 static int 85 test_thundering_herd(int doread, int dosignal) 86 { 87 pid_t pids[NCHILD]; 88 struct context ctx; 89 ssize_t n; 90 int i; 91 unsigned char c = 'c'; 92 93 ctx.c_ident = doread ? "read" : "write"; 94 ctx.c_read = doread; 95 if (pipe(ctx.c_pipe) == -1) 96 err(1, "pipe"); 97 if (pipe(ctx.c_cv) == -1) 98 err(1, "pipe"); 99 100 ctx.c_bufsiz = ctx.c_read ? 1 : BIG_PIPE_SIZE; 101 ctx.c_buf = malloc(ctx.c_bufsiz); 102 if (ctx.c_buf == NULL) 103 err(1, NULL); 104 105 for (i = 0; i < NCHILD; i++) { 106 ctx.c_id = i + 1; 107 pids[i] = block_proc(&ctx); 108 } 109 110 /* 111 * Let one child wakeup and force the other children into sleeping in 112 * pipelock(9). 113 */ 114 if (ctx.c_read) 115 n = write(ctx.c_pipe[1], &c, 1); 116 else 117 n = read(ctx.c_pipe[0], &c, 1); 118 if (n == -1) 119 err(1, "%s", ctx.c_ident); 120 if (n != 1) 121 errx(1, "%s: %ld != 1", ctx.c_ident, n); 122 123 /* Wait for signal from woken up child. */ 124 (void)read(ctx.c_cv[0], &c, 1); 125 if (verbose) 126 fprintf(stderr, "[p] got signal from child\n"); 127 128 if (dosignal) { 129 for (i = 0; i < NCHILD; i++) { 130 if (verbose) 131 fprintf(stderr, "[p] kill %d\n", i + 1); 132 if (kill(pids[i], SIGUSR1) == -1) 133 err(1, "kill"); 134 } 135 } else { 136 if (ctx.c_read) 137 close(ctx.c_pipe[1]); 138 else 139 close(ctx.c_pipe[0]); 140 } 141 142 for (i = 0; i < NCHILD; i++) { 143 int ex; 144 145 if (verbose) 146 fprintf(stderr, "[p] wait %d\n", i + 1); 147 ex = xwaitpid(pids[i]); 148 if (ex == 0 || ex == SIGUSR1) 149 continue; 150 errx(1, "waitpid: %d != 0", ex); 151 } 152 153 return 0; 154 } 155 156 static pid_t 157 block_proc(const struct context *ctx) 158 { 159 pid_t pid; 160 161 pid = fork(); 162 if (pid == -1) 163 err(1, "fork"); 164 if (pid == 0) { 165 int rp = ctx->c_pipe[0]; 166 int wp = ctx->c_pipe[1]; 167 168 if (ctx->c_read) 169 close(wp); 170 else 171 close(rp); 172 173 for (;;) { 174 ssize_t n; 175 unsigned char c = 'c'; 176 177 if (ctx->c_read) 178 n = read(rp, ctx->c_buf, ctx->c_bufsiz); 179 else 180 n = write(wp, ctx->c_buf, ctx->c_bufsiz); 181 if (verbose) 182 fprintf(stderr, "[%d] %s = %ld\n", 183 ctx->c_id, ctx->c_ident, n); 184 if (n == -1) { 185 if (errno == EPIPE) 186 break; 187 err(1, "[%d] %s", ctx->c_id, ctx->c_ident); 188 } 189 if (n == 0) 190 break; 191 192 /* Send signal to parent. */ 193 (void)write(ctx->c_cv[1], &c, 1); 194 } 195 196 _exit(0); 197 } 198 199 return pid; 200 } 201