xref: /openbsd-src/regress/sys/kern/pipe/test-thundering-herd.c (revision 63f9946001973e137a82241f1619cdf2863ffd41)
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