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
test_thundering_herd_read_signal(void)57 test_thundering_herd_read_signal(void)
58 {
59
60 return test_thundering_herd(1, 1);
61 }
62
63 int
test_thundering_herd_read_wakeup(void)64 test_thundering_herd_read_wakeup(void)
65 {
66
67 return test_thundering_herd(1, 0);
68 }
69
70 int
test_thundering_herd_write_signal(void)71 test_thundering_herd_write_signal(void)
72 {
73
74 return test_thundering_herd(0, 1);
75 }
76
77 int
test_thundering_herd_write_wakeup(void)78 test_thundering_herd_write_wakeup(void)
79 {
80
81 return test_thundering_herd(0, 0);
82 }
83
84 static int
test_thundering_herd(int doread,int dosignal)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
block_proc(const struct context * ctx)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