poll_test.c revision d583b39bfb4e2571d3e41097c5c357ffe353ad45
/*
* This file and its contents are supplied under the terms of the
* Common Development and Distribution License ("CDDL"), version 1.0.
* You may only use this file in accordance with the terms of version
* 1.0 of the CDDL.
*
* A full copy of the text of the CDDL should have accompanied this
* source. A copy of the CDDL is also available via the Internet at
*/
/*
* Copyright (c) 2012 by Delphix. All rights reserved.
*/
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <fcntl.h>
#include <pthread.h>
#include <assert.h>
#include <errno.h>
#include <ctype.h>
#include <unistd.h>
#include <poll.h>
/*
* poll_test.c --
*
* This file implements some simple tests to verify the behavior of the
*
* Background:
*
* Several customers recently ran into an issue where threads in grizzly
* (java http server implementation) would randomly wake up from a java
* call to select against a java.nio.channels.Selector with no events ready
* but well before the specified timeout expired. The
* java.nio.channels.Selector select logic is implemented via /dev/poll.
* select on to the file descritpor, and then issues a DP_POLL ioctl to
* wait for events to be ready.
*
* The DP_POLL ioctl arguments include a relative timeout in milliseconds,
* according to man poll.7d the ioctl should block until events are ready,
* the timeout expires, or a signal was received. In this case we noticed
* that DP_POLL was returning before the timeout expired despite no events
* being ready and no signal being delivered.
*
* Using dtrace we discovered that DP_POLL was returning in cases where the
* system time was changed and the thread calling DP_POLL was woken up as
* a result of the process forking. The DP_POLL logic was in a loop
* checking if events were ready and then calling cv_waituntil_sig to
* block. cv_waituntil_sig will return -1 if the system time has changed,
* causing the DP_POLL to complete prematurely.
*
* Looking at the code it turns out the same problem exists in
* the implementation for poll.2 as well.
*
* Fix:
*
* The fix changes dpioctl and poll_common to use cv_relwaituntil_sig
* rather then cv_waituntil_sig. cv_reltimedwait_sig expects a
* relative timeout rather then an absolute timeout, so we avoid the
* problem.
*
* Test:
*
* The test verifies that changing the date does not wake up threads
* blocked processing a poll request or a DP_POLL ioctl. The test spawns
* one thread that changes the date and forks (to force the threads to
* wakeup from cv_reltimedwait_sig) every two seconds. The test spawns
* a second thread that issues poll / DP_POLL on an fd set that will
* never have events ready and verifies that it does not return until
* the specified timeout expires.
*/
/*
* The maximum amount of skew in seconds allowed between the
* expected an actual time that a test takes.
*/
#define TIME_DRIFT 1
static int terminated = 0;
/*
* Set via -d to enable debug logging
*/
static int debug = 0;
static void
{
if (!debug) {
return;
}
(void) printf("DEBUG: ");
}
static void
{
}
static void
{
(void) exit(-1);
}
static void
test_passed(const char *testName)
{
}
static int
{
/*
* We may take slightly more or less time then expected,
* we allow for a small fudge factor if things completed
* before we expect them to.
*/
}
static int
{
int ret;
debug_log("POLL end: (0x%p, %d, %d) returns %d (elapse=%d)\n",
return (ret);
}
static int
{
int ret;
debug_log("DP_POLL end: (0x%p, %d, %d) returns %d (elapse=%d)\n",
return (ret);
}
static void
{
int ret;
if (ret < 0) {
}
}
}
/*
* TEST: poll with no FDs set, verify we wait the appropriate amount of time.
*/
static void
poll_no_fd_test(void)
{
int timeout = 10;
int ret;
if (ret != 0) {
}
}
}
/*
* TEST: POLL with a valid FD set, verify that we wait the appropriate amount
* of time.
*/
static void
poll_with_fds_test(int testfd)
{
int timeout = 10;
int ret;
if (ret != 0) {
}
}
}
/*
* TEST: DP_POLL with no FDs set, verify we wait the appropriate
* amount of time.
*/
static void
{
int timeout = 10;
int ret;
if (ret != 0) {
}
}
}
/*
* TEST: DP_POLL with a valid FD set, verify that we wait
* the appropriate amount of time.
*/
static void
{
int timeout = 10;
int ret;
/*
* Clear the FD in case it's already in the cached set
*/
/*
* Add the FD with POLLIN
*/
if (ret < 0) {
}
}
if (ret != 0) {
}
}
}
/* ARGSUSED */
static void *
poll_thread(void *data)
{
int pollfd;
int testfd;
if (pollfd < 0) {
exit(-1);
}
/*
* Create a dummy FD that will never have POLLIN set
*/
pthread_exit(0);
return (NULL);
}
/*
* This function causes any threads blocked in cv_timedwait_sig_hires
* to wakeup, which allows us to test how dpioctl handles spurious
* wakeups.
*/
static void
trigger_wakeup(void)
{
/*
* Forking will force all of the threads to be woken up so
* they can be moved to a well known state.
*/
if (child == -1) {
perror("Fork failed: ");
exit(-1);
} else if (child == 0) {
_exit(0);
} else {
int status;
do {
(void) printf("Waitpid for %ld failed: %s\n",
exit(-1);
}
if (status != 0) {
(void) printf("Child pid %ld failed: %d\n",
exit(-1);
}
}
}
/*
* This function changes the system time, which has the side
* effect of updating timechanged in the kernel.
*/
static void
change_date(void)
{
int ret;
}
/*
* The helper thread runs in a loop changing the time and
* forcing wakeups every 2 seconds.
*/
/* ARGSUSED */
static void *
helper_thread(void *data)
{
int exit;
debug_log("Helper thread started ...\n");
/* CONSTCOND */
while (1) {
(void) pthread_mutex_lock(&exitLock);
exit = terminated;
(void) pthread_mutex_unlock(&exitLock);
if (exit) {
break;
}
change_date();
debug_log("Time changed and force wakeup issued\n");
}
debug_log("Helper thread exiting ...\n");
pthread_exit(0);
return (NULL);
}
static void
stop_threads(void)
{
(void) pthread_mutex_lock(&exitLock);
terminated = 1;
(void) pthread_cond_broadcast(&exitCond);
(void) pthread_mutex_unlock(&exitLock);
}
static void
run_tests(void)
{
int ret;
if (ret != 0) {
(void) printf("Failed to create date thread: %s",
exit(-1);
}
if (ret != 0) {
(void) printf("Failed to create poll thread: %s",
exit(-1);
}
stop_threads();
}
int
{
int c;
switch (c) {
case 'd':
debug = 1;
break;
default:
break;
}
}
/*
* We need to be root to change the system time
*/
exit(-1);
}
run_tests();
exit(0);
}