xref: /illumos-gate/usr/src/test/os-tests/tests/poll/poll_test.c (revision 8c69cc8fbe729fa7b091e901c4b50508ccc6bb33)
1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright (c) 2012 by Delphix. All rights reserved.
14  */
15 
16 #include <stdlib.h>
17 #include <stdio.h>
18 #include <stdarg.h>
19 #include <string.h>
20 #include <fcntl.h>
21 #include <pthread.h>
22 #include <assert.h>
23 #include <errno.h>
24 #include <ctype.h>
25 #include <unistd.h>
26 #include <poll.h>
27 #include <sys/types.h>
28 #include <sys/wait.h>
29 #include <sys/stat.h>
30 #include <sys/socket.h>
31 #include <sys/devpoll.h>
32 
33 /*
34  * poll_test.c --
35  *
36  *      This file implements some simple tests to verify the behavior of the
37  *      poll system call and the DP_POLL ioctl on /dev/poll.
38  *
39  * Background:
40  *
41  *      Several customers recently ran into an issue where threads in grizzly
42  *      (java http server implementation) would randomly wake up from a java
43  *      call to select against a java.nio.channels.Selector with no events ready
44  *      but well before the specified timeout expired. The
45  *      java.nio.channels.Selector select logic is implemented via /dev/poll.
46  *      The selector opens /dev/poll, writes the file descriptors it wants to
47  *      select on to the file descritpor, and then issues a DP_POLL ioctl to
48  *      wait for events to be ready.
49  *
50  *      The DP_POLL ioctl arguments include a relative timeout in milliseconds,
51  *      according to man poll.7d the ioctl should block until events are ready,
52  *      the timeout expires, or a signal was received. In this case we noticed
53  *      that DP_POLL was returning before the timeout expired despite no events
54  *      being ready and no signal being delivered.
55  *
56  *      Using dtrace we discovered that DP_POLL was returning in cases where the
57  *      system time was changed and the thread calling DP_POLL was woken up as
58  *      a result of the process forking. The DP_POLL logic was in a loop
59  *      checking if events were ready and then calling cv_waituntil_sig to
60  *      block. cv_waituntil_sig will return -1 if the system time has changed,
61  *      causing the DP_POLL to complete prematurely.
62  *
63  *      Looking at the code it turns out the same problem exists in
64  *      the implementation for poll.2 as well.
65  *
66  * Fix:
67  *
68  *      The fix changes dpioctl and poll_common to use cv_relwaituntil_sig
69  *      rather then cv_waituntil_sig. cv_reltimedwait_sig expects a
70  *      relative timeout rather then an absolute timeout, so we avoid the
71  *      problem.
72  *
73  * Test:
74  *
75  *      The test verifies that changing the date does not wake up threads
76  *      blocked processing a poll request or a DP_POLL ioctl. The test spawns
77  *      one thread that changes the date and forks (to force the threads to
78  *      wakeup from cv_reltimedwait_sig) every two seconds. The test spawns
79  *      a second thread that issues poll / DP_POLL on an fd set that will
80  *      never have events ready and verifies that it does not return until
81  *      the specified timeout expires.
82  */
83 
84 /*
85  * The maximum amount of skew in seconds allowed between the
86  * expected an actual time that a test takes.
87  */
88 #define	TIME_DRIFT	1
89 
90 static pthread_mutex_t	exitLock = PTHREAD_MUTEX_INITIALIZER;
91 static pthread_cond_t	exitCond = PTHREAD_COND_INITIALIZER;
92 static int		terminated = 0;
93 
94 /*
95  * Set via -d to enable debug logging
96  */
97 static int debug = 0;
98 
99 static void
100 debug_log(const char *format, ...)
101 {
102 	va_list	args;
103 
104 	if (!debug) {
105 		return;
106 	}
107 
108 	(void) printf("DEBUG: ");
109 
110 	va_start(args, format);
111 	(void) vprintf(format, args);
112 	va_end(args);
113 }
114 
115 static void
116 test_start(const char *testName, const char *format, ...)
117 {
118 	va_list	args;
119 
120 	(void) printf("TEST STARTING %s: ", testName);
121 
122 	va_start(args, format);
123 	(void) vprintf(format, args);
124 	va_end(args);
125 	(void) fflush(stdout);
126 }
127 
128 static void
129 test_failed(const char *testName, const char *format, ...)
130 {
131 	va_list	args;
132 
133 	(void) printf("TEST FAILED %s: ", testName);
134 
135 	va_start(args, format);
136 	(void) vprintf(format, args);
137 	va_end(args);
138 
139 	(void) exit(-1);
140 }
141 
142 static void
143 test_passed(const char *testName)
144 {
145 	(void) printf("TEST PASS: %s\n", testName);
146 	(void) fflush(stdout);
147 }
148 
149 static int
150 check_time(time_t elapsed, time_t expected)
151 {
152 	time_t	diff = expected - elapsed;
153 
154 	/*
155 	 * We may take slightly more or less time then expected,
156 	 * we allow for a small fudge factor if things completed
157 	 * before we expect them to.
158 	 */
159 	return (elapsed >= expected || diff <= TIME_DRIFT);
160 }
161 
162 static int
163 poll_wrapper(pollfd_t *fds, nfds_t nfds, int timeout, time_t *elapsed)
164 {
165 	int		ret;
166 	time_t		start = time(NULL);
167 
168 	debug_log("POLL start: (0x%p, %d, %d)\n", fds, nfds, timeout);
169 
170 	ret = poll(fds, nfds, timeout);
171 
172 	*elapsed = time(NULL) - start;
173 
174 	debug_log("POLL end: (0x%p, %d, %d) returns %d (elapse=%d)\n",
175 	    fds, nfds, timeout, ret, (*elapsed));
176 
177 	return (ret);
178 }
179 
180 static int
181 dppoll(int pollfd, pollfd_t *fds, nfds_t nfds, int timeout, time_t *elapsed)
182 {
183 	struct dvpoll	arg;
184 	int		ret;
185 	time_t		start = time(NULL);
186 
187 	arg.dp_fds = fds;
188 	arg.dp_nfds = nfds;
189 	arg.dp_timeout = timeout;
190 
191 	debug_log("DP_POLL start: (0x%p, %d, %d)\n", fds, nfds, timeout);
192 
193 	ret = ioctl(pollfd, DP_POLL, &arg);
194 
195 	*elapsed = time(NULL) - start;
196 
197 	debug_log("DP_POLL end: (0x%p, %d, %d) returns %d (elapse=%d)\n",
198 	    fds, arg.dp_nfds, arg.dp_timeout, ret, (*elapsed));
199 
200 	return (ret);
201 }
202 
203 static void
204 clear_fd(const char *testName, int pollfd, int testfd)
205 {
206 	int		ret;
207 	pollfd_t	fd;
208 
209 	fd.fd = testfd;
210 	fd.events = POLLREMOVE;
211 	fd.revents = 0;
212 
213 	ret = write(pollfd, &fd, sizeof (pollfd_t));
214 
215 	if (ret != sizeof (pollfd_t)) {
216 		if (ret < 0) {
217 			test_failed(testName, "Failed to clear fd %d: %s",
218 			    testfd, strerror(ret));
219 		}
220 
221 
222 		test_failed(testName, "Failed to clear fd %d: %d", testfd, ret);
223 	}
224 }
225 
226 /*
227  * TEST: poll with no FDs set, verify we wait the appropriate amount of time.
228  */
229 static void
230 poll_no_fd_test(void)
231 {
232 	const char	*testName = __func__;
233 	time_t		elapsed;
234 	int		timeout = 10;
235 	int		ret;
236 
237 	test_start(testName, "poll for %d sec with no fds\n", timeout);
238 
239 	ret = poll_wrapper(NULL, 0, timeout * 1000, &elapsed);
240 
241 	if (ret != 0) {
242 		test_failed(testName, "POLL returns %d (expected 0)\n", ret);
243 	}
244 
245 	if (!check_time(elapsed, timeout)) {
246 		test_failed(testName, "took %d (expected %d)\n",
247 		    elapsed, timeout);
248 	}
249 
250 	test_passed(testName);
251 }
252 
253 /*
254  * TEST: POLL with a valid FD set, verify that we wait the appropriate amount
255  * of time.
256  */
257 static void
258 poll_with_fds_test(int testfd)
259 {
260 	const char	*testName = __func__;
261 	time_t		elapsed;
262 	int		timeout = 10;
263 	int		ret;
264 	pollfd_t	fd;
265 
266 	fd.fd = testfd;
267 	fd.events = 0;
268 	fd.revents = 0;
269 
270 	test_start(testName, "poll for %d sec with fds\n", timeout);
271 
272 	ret = poll_wrapper(&fd, 1, timeout * 1000, &elapsed);
273 
274 	if (ret != 0) {
275 		test_failed(testName, "POLL returns %d (expected 0)\n", ret);
276 	}
277 
278 	if (!check_time(elapsed, timeout)) {
279 		test_failed(testName, "took %d (expected %d)\n",
280 		    elapsed, timeout);
281 	}
282 
283 	test_passed(testName);
284 }
285 
286 /*
287  * TEST: DP_POLL with no FDs set, verify we wait the appropriate
288  * amount of time.
289  */
290 static void
291 dev_poll_no_fd_test(int pollfd)
292 {
293 	const char	*testName = __func__;
294 	time_t		elapsed;
295 	int		timeout = 10;
296 	int		ret;
297 
298 	test_start(testName, "poll for %d sec with no fds\n", timeout);
299 
300 	ret = dppoll(pollfd, NULL, 0, timeout * 1000, &elapsed);
301 
302 	if (ret != 0) {
303 		test_failed(testName, "DP_POLL returns %d (expected 0)\n", ret);
304 	}
305 
306 	if (!check_time(elapsed, timeout)) {
307 		test_failed(testName, "took %d (expected %d)\n",
308 		    elapsed, timeout);
309 	}
310 
311 	test_passed(testName);
312 }
313 
314 /*
315  * TEST: DP_POLL with a valid FD set, verify that we wait
316  * the appropriate amount of time.
317  */
318 static void
319 dev_poll_with_fds_test(int pollfd, int testfd)
320 {
321 	const char	*testName = __func__;
322 	time_t		elapsed;
323 	int		timeout = 10;
324 	int		ret;
325 	pollfd_t	fds[5];
326 
327 	test_start(testName, "poll for %d sec with fds\n", timeout);
328 
329 	/*
330 	 * Clear the FD in case it's already in the cached set
331 	 */
332 	clear_fd(testName, pollfd, testfd);
333 
334 	/*
335 	 * Add the FD with POLLIN
336 	 */
337 	fds[0].fd = testfd;
338 	fds[0].events = POLLIN;
339 	fds[0].revents = 0;
340 
341 	ret = write(pollfd, fds, sizeof (pollfd_t));
342 
343 	if (ret != sizeof (pollfd_t)) {
344 		if (ret < 0) {
345 			test_failed(testName, "Failed to set fds: %s",
346 			    strerror(ret));
347 		}
348 
349 		test_failed(testName, "Failed to set fds: %d", ret);
350 	}
351 
352 	ret = dppoll(pollfd, fds, 5, timeout * 1000, &elapsed);
353 
354 	if (ret != 0) {
355 		test_failed(testName, "DP_POLL returns %d (expected 0)\n", ret);
356 	}
357 
358 	if (!check_time(elapsed, timeout)) {
359 		test_failed(testName, "took %d (expected %d)\n",
360 		    elapsed, timeout);
361 	}
362 
363 	clear_fd(testName, pollfd, testfd);
364 
365 	test_passed(testName);
366 }
367 
368 /* ARGSUSED */
369 static void *
370 poll_thread(void *data)
371 {
372 	int	pollfd;
373 	int	testfd;
374 
375 	pollfd = open("/dev/poll", O_RDWR);
376 
377 	if (pollfd <  0) {
378 		perror("Failed to open /dev/poll: ");
379 		exit(-1);
380 	}
381 
382 	/*
383 	 * Create a dummy FD that will never have POLLIN set
384 	 */
385 	testfd = socket(PF_INET, SOCK_STREAM, 0);
386 
387 	poll_no_fd_test();
388 	poll_with_fds_test(testfd);
389 
390 	dev_poll_no_fd_test(pollfd);
391 	dev_poll_with_fds_test(pollfd, testfd);
392 
393 	(void) close(testfd);
394 	(void) close(pollfd);
395 
396 	pthread_exit(0);
397 	return (NULL);
398 }
399 
400 /*
401  * This function causes any threads blocked in cv_timedwait_sig_hires
402  * to wakeup, which allows us to test how dpioctl handles spurious
403  * wakeups.
404  */
405 static void
406 trigger_wakeup(void)
407 {
408 	pid_t   child;
409 
410 	/*
411 	 * Forking will force all of the threads to be woken up so
412 	 * they can be moved to a well known state.
413 	 */
414 	child = vfork();
415 
416 	if (child == -1) {
417 		perror("Fork failed: ");
418 		exit(-1);
419 	} else if (child == 0) {
420 		_exit(0);
421 	} else {
422 		pid_t   result = -1;
423 		int	status;
424 
425 		do {
426 			result = waitpid(child, &status, 0);
427 
428 			if (result == -1 && errno != EINTR) {
429 				(void) printf("Waitpid for %ld failed: %s\n",
430 				    child, strerror(errno));
431 				exit(-1);
432 			}
433 		} while (result != child);
434 
435 		if (status != 0) {
436 			(void) printf("Child pid %ld failed: %d\n",
437 			    child, status);
438 			exit(-1);
439 		}
440 	}
441 }
442 
443 /*
444  * This function changes the system time, which has the side
445  * effect of updating timechanged in the kernel.
446  */
447 static void
448 change_date(void)
449 {
450 	int	ret;
451 	struct timeval  tp;
452 
453 	ret = gettimeofday(&tp, NULL);
454 	assert(ret == 0);
455 
456 	tp.tv_usec++;
457 	ret = settimeofday(&tp, NULL);
458 	assert(ret == 0);
459 }
460 
461 /*
462  * The helper thread runs in a loop changing the time and
463  * forcing wakeups every 2 seconds.
464  */
465 /* ARGSUSED */
466 static void *
467 helper_thread(void *data)
468 {
469 	int	exit;
470 	struct	timespec ts = {2, 0};
471 
472 	debug_log("Helper thread started ...\n");
473 
474 	/* CONSTCOND */
475 	while (1) {
476 		(void) pthread_mutex_lock(&exitLock);
477 		(void) pthread_cond_reltimedwait_np(&exitCond, &exitLock, &ts);
478 		exit = terminated;
479 		(void) pthread_mutex_unlock(&exitLock);
480 
481 		if (exit) {
482 			break;
483 		}
484 
485 		change_date();
486 		trigger_wakeup();
487 		debug_log("Time changed and force wakeup issued\n");
488 	}
489 
490 	debug_log("Helper thread exiting ...\n");
491 
492 	pthread_exit(0);
493 	return (NULL);
494 }
495 
496 static void
497 stop_threads(void)
498 {
499 	(void) pthread_mutex_lock(&exitLock);
500 	terminated = 1;
501 	(void) pthread_cond_broadcast(&exitCond);
502 	(void) pthread_mutex_unlock(&exitLock);
503 }
504 
505 static void
506 run_tests(void)
507 {
508 	pthread_t	pollThread;
509 	pthread_t	helperThread;
510 	int		ret;
511 
512 	ret = pthread_create(&helperThread, NULL, helper_thread, NULL);
513 
514 	if (ret != 0) {
515 		(void) printf("Failed to create date thread: %s",
516 		    strerror(ret));
517 		exit(-1);
518 	}
519 
520 	ret = pthread_create(&pollThread, NULL, poll_thread, NULL);
521 
522 	if (ret != 0) {
523 		(void) printf("Failed to create poll thread: %s",
524 		    strerror(ret));
525 		exit(-1);
526 	}
527 
528 	(void) pthread_join(pollThread, NULL);
529 	stop_threads();
530 	(void) pthread_join(helperThread, NULL);
531 }
532 
533 int
534 main(int argc, char * const argv[])
535 {
536 	int	c;
537 
538 	while ((c = getopt(argc, argv, "d")) != -1) {
539 		switch (c) {
540 		case 'd':
541 			debug = 1;
542 			break;
543 		default:
544 			break;
545 		}
546 	}
547 
548 	/*
549 	 * We need to be root to change the system time
550 	 */
551 	if (getuid() != 0 && geteuid() != 0) {
552 		(void) printf("%s must be run as root\n", argv[0]);
553 		exit(-1);
554 	}
555 
556 	run_tests();
557 
558 	exit(0);
559 }
560