Home | History | Annotate | Download | only in functional
      1 /******************************************************************************
      2  *
      3  *   Copyright  International Business Machines  Corp., 2006-2008
      4  *
      5  *   This program is free software;  you can redistribute it and/or modify
      6  *   it under the terms of the GNU General Public License as published by
      7  *   the Free Software Foundation; either version 2 of the License, or
      8  *   (at your option) any later version.
      9  *
     10  * DESCRIPTION
     11  *      This test excercises the futex syscall op codes needed for requeuing
     12  *      priority inheritance aware POSIX condition variables and mutexes.
     13  *
     14  * AUTHORS
     15  *      Sripathi Kodi <sripathik (at) in.ibm.com>
     16  *      Darren Hart <dvhart (at) linux.intel.com>
     17  *
     18  * HISTORY
     19  *      2008-Jan-13: Initial version by Sripathi Kodi <sripathik (at) in.ibm.com>
     20  *      2009-Nov-6: futex test adaptation by Darren Hart <dvhart (at) linux.intel.com>
     21  *
     22  *****************************************************************************/
     23 
     24 #include <errno.h>
     25 #include <limits.h>
     26 #include <pthread.h>
     27 #include <stdio.h>
     28 #include <stdlib.h>
     29 #include <signal.h>
     30 #include <string.h>
     31 #include "atomic.h"
     32 #include "futextest.h"
     33 #include "logging.h"
     34 
     35 #define TEST_NAME "futex-requeue-pi"
     36 #define MAX_WAKE_ITERS 1000
     37 #define THREAD_MAX 10
     38 #define SIGNAL_PERIOD_US 100
     39 
     40 atomic_t waiters_blocked = ATOMIC_INITIALIZER;
     41 atomic_t waiters_woken = ATOMIC_INITIALIZER;
     42 
     43 futex_t f1 = FUTEX_INITIALIZER;
     44 futex_t f2 = FUTEX_INITIALIZER;
     45 futex_t wake_complete = FUTEX_INITIALIZER;
     46 
     47 /* Test option defaults */
     48 static long timeout_ns;
     49 static int broadcast;
     50 static int owner;
     51 static int locked;
     52 
     53 struct thread_arg {
     54 	long id;
     55 	struct timespec *timeout;
     56 	int lock;
     57 	int ret;
     58 };
     59 #define THREAD_ARG_INITIALIZER { 0, NULL, 0, 0 }
     60 
     61 void usage(char *prog)
     62 {
     63 	printf("Usage: %s\n", prog);
     64 	printf("  -b	Broadcast wakeup (all waiters)\n");
     65 	printf("  -c	Use color\n");
     66 	printf("  -h	Display this help message\n");
     67 	printf("  -l	Lock the pi futex across requeue\n");
     68 	printf("  -o	Use a third party pi futex owner during requeue (cancels -l)\n");
     69 	printf("  -t N	Timeout in nanoseconds (default: 0)\n");
     70 	printf("  -v L	Verbosity level: %d=QUIET %d=CRITICAL %d=INFO\n",
     71 	       VQUIET, VCRITICAL, VINFO);
     72 }
     73 
     74 int create_rt_thread(pthread_t *pth, void*(*func)(void *), void *arg,
     75 		     int policy, int prio)
     76 {
     77 	int ret;
     78 	struct sched_param schedp;
     79 	pthread_attr_t attr;
     80 
     81 	pthread_attr_init(&attr);
     82 	memset(&schedp, 0, sizeof(schedp));
     83 
     84 	ret = pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);
     85 	if (ret) {
     86 		error("pthread_attr_setinheritsched\n", ret);
     87 		return -1;
     88 	}
     89 
     90 	ret = pthread_attr_setschedpolicy(&attr, policy);
     91 	if (ret) {
     92 		error("pthread_attr_setschedpolicy\n", ret);
     93 		return -1;
     94 	}
     95 
     96 	schedp.sched_priority = prio;
     97 	ret = pthread_attr_setschedparam(&attr, &schedp);
     98 	if (ret) {
     99 		error("pthread_attr_setschedparam\n", ret);
    100 		return -1;
    101 	}
    102 
    103 	ret = pthread_create(pth, &attr, func, arg);
    104 	if (ret) {
    105 		error("pthread_create\n", ret);
    106 		return -1;
    107 	}
    108 	return 0;
    109 }
    110 
    111 
    112 void *waiterfn(void *arg)
    113 {
    114 	struct thread_arg *args = (struct thread_arg *)arg;
    115 	futex_t old_val;
    116 
    117 	info("Waiter %ld: running\n", args->id);
    118 	/* Each thread sleeps for a different amount of time
    119 	 * This is to avoid races, because we don't lock the
    120 	 * external mutex here */
    121 	usleep(1000 * (long)args->id);
    122 
    123 	old_val = f1;
    124 	atomic_inc(&waiters_blocked);
    125 	info("Calling futex_wait_requeue_pi: %p (%u) -> %p\n",
    126 	     &f1, f1, &f2);
    127 	args->ret = futex_wait_requeue_pi(&f1, old_val, &f2, args->timeout,
    128 					  FUTEX_PRIVATE_FLAG);
    129 
    130 	info("waiter %ld woke with %d %s\n", args->id, args->ret,
    131 	     args->ret < 0 ? strerror(errno) : "");
    132 	atomic_inc(&waiters_woken);
    133 	if (args->ret < 0) {
    134 		if (args->timeout && errno == ETIMEDOUT)
    135 			args->ret = 0;
    136 		else {
    137 			args->ret = RET_ERROR;
    138 			error("futex_wait_requeue_pi\n", errno);
    139 		}
    140 		futex_lock_pi(&f2, NULL, 0, FUTEX_PRIVATE_FLAG);
    141 	}
    142 	futex_unlock_pi(&f2, FUTEX_PRIVATE_FLAG);
    143 
    144 	info("Waiter %ld: exiting with %d\n", args->id, args->ret);
    145 	pthread_exit((void *)&args->ret);
    146 }
    147 
    148 void *broadcast_wakerfn(void *arg)
    149 {
    150 	struct thread_arg *args = (struct thread_arg *)arg;
    151 	int nr_requeue = INT_MAX;
    152 	int task_count = 0;
    153 	futex_t old_val;
    154 	int nr_wake = 1;
    155 	int i = 0;
    156 
    157 	info("Waker: waiting for waiters to block\n");
    158 	while (waiters_blocked.val < THREAD_MAX)
    159 		usleep(1000);
    160 	usleep(1000);
    161 
    162 	info("Waker: Calling broadcast\n");
    163 	if (args->lock) {
    164 		info("Calling FUTEX_LOCK_PI on mutex=%x @ %p\n", f2, &f2);
    165 		futex_lock_pi(&f2, NULL, 0, FUTEX_PRIVATE_FLAG);
    166 	}
    167  continue_requeue:
    168 	old_val = f1;
    169 	args->ret = futex_cmp_requeue_pi(&f1, old_val, &f2, nr_wake, nr_requeue,
    170 				   FUTEX_PRIVATE_FLAG);
    171 	if (args->ret < 0) {
    172 		args->ret = RET_ERROR;
    173 		error("FUTEX_CMP_REQUEUE_PI failed\n", errno);
    174 	} else if (++i < MAX_WAKE_ITERS) {
    175 		task_count += args->ret;
    176 		if (task_count < THREAD_MAX - waiters_woken.val)
    177 			goto continue_requeue;
    178 	} else {
    179 		error("max broadcast iterations (%d) reached with %d/%d tasks woken or requeued\n",
    180 		       0, MAX_WAKE_ITERS, task_count, THREAD_MAX);
    181 		args->ret = RET_ERROR;
    182 	}
    183 
    184 	futex_wake(&wake_complete, 1, FUTEX_PRIVATE_FLAG);
    185 
    186 	if (args->lock)
    187 		futex_unlock_pi(&f2, FUTEX_PRIVATE_FLAG);
    188 
    189 	if (args->ret > 0)
    190 		args->ret = task_count;
    191 
    192 	info("Waker: exiting with %d\n", args->ret);
    193 	pthread_exit((void *)&args->ret);
    194 }
    195 
    196 void *signal_wakerfn(void *arg)
    197 {
    198 	struct thread_arg *args = (struct thread_arg *)arg;
    199 	unsigned int old_val;
    200 	int nr_requeue = 0;
    201 	int task_count = 0;
    202 	int nr_wake = 1;
    203 	int i = 0;
    204 
    205 	info("Waker: waiting for waiters to block\n");
    206 	while (waiters_blocked.val < THREAD_MAX)
    207 		usleep(1000);
    208 	usleep(1000);
    209 
    210 	while (task_count < THREAD_MAX && waiters_woken.val < THREAD_MAX) {
    211 		info("task_count: %d, waiters_woken: %d\n",
    212 		     task_count, waiters_woken.val);
    213 		if (args->lock) {
    214 			info("Calling FUTEX_LOCK_PI on mutex=%x @ %p\n",
    215 			     f2, &f2);
    216 			futex_lock_pi(&f2, NULL, 0, FUTEX_PRIVATE_FLAG);
    217 		}
    218 		info("Waker: Calling signal\n");
    219 		/* cond_signal */
    220 		old_val = f1;
    221 		args->ret = futex_cmp_requeue_pi(&f1, old_val, &f2,
    222 						 nr_wake, nr_requeue,
    223 						 FUTEX_PRIVATE_FLAG);
    224 		if (args->ret < 0)
    225 			args->ret = -errno;
    226 		info("futex: %x\n", f2);
    227 		if (args->lock) {
    228 			info("Calling FUTEX_UNLOCK_PI on mutex=%x @ %p\n",
    229 			     f2, &f2);
    230 			futex_unlock_pi(&f2, FUTEX_PRIVATE_FLAG);
    231 		}
    232 		info("futex: %x\n", f2);
    233 		if (args->ret < 0) {
    234 			error("FUTEX_CMP_REQUEUE_PI failed\n", errno);
    235 			args->ret = RET_ERROR;
    236 			break;
    237 		}
    238 
    239 		task_count += args->ret;
    240 		usleep(SIGNAL_PERIOD_US);
    241 		i++;
    242 		/* we have to loop at least THREAD_MAX times */
    243 		if (i > MAX_WAKE_ITERS + THREAD_MAX) {
    244 			error("max signaling iterations (%d) reached, giving up on pending waiters.\n",
    245 			      0, MAX_WAKE_ITERS + THREAD_MAX);
    246 			args->ret = RET_ERROR;
    247 			break;
    248 		}
    249 	}
    250 
    251 	futex_wake(&wake_complete, 1, FUTEX_PRIVATE_FLAG);
    252 
    253 	if (args->ret >= 0)
    254 		args->ret = task_count;
    255 
    256 	info("Waker: exiting with %d\n", args->ret);
    257 	info("Waker: waiters_woken: %d\n", waiters_woken.val);
    258 	pthread_exit((void *)&args->ret);
    259 }
    260 
    261 void *third_party_blocker(void *arg)
    262 {
    263 	struct thread_arg *args = (struct thread_arg *)arg;
    264 	int ret2 = 0;
    265 
    266 	args->ret = futex_lock_pi(&f2, NULL, 0, FUTEX_PRIVATE_FLAG);
    267 	if (args->ret)
    268 		goto out;
    269 	args->ret = futex_wait(&wake_complete, wake_complete, NULL,
    270 			       FUTEX_PRIVATE_FLAG);
    271 	ret2 = futex_unlock_pi(&f2, FUTEX_PRIVATE_FLAG);
    272 
    273  out:
    274 	if (args->ret || ret2) {
    275 		error("third_party_blocker() futex error", 0);
    276 		args->ret = RET_ERROR;
    277 	}
    278 
    279 	pthread_exit((void *)&args->ret);
    280 }
    281 
    282 int unit_test(int broadcast, long lock, int third_party_owner, long timeout_ns)
    283 {
    284 	void *(*wakerfn)(void *) = signal_wakerfn;
    285 	struct thread_arg blocker_arg = THREAD_ARG_INITIALIZER;
    286 	struct thread_arg waker_arg = THREAD_ARG_INITIALIZER;
    287 	pthread_t waiter[THREAD_MAX], waker, blocker;
    288 	struct timespec ts, *tsp = NULL;
    289 	struct thread_arg args[THREAD_MAX];
    290 	int *waiter_ret;
    291 	int i, ret = RET_PASS;
    292 
    293 	if (timeout_ns) {
    294 		time_t secs;
    295 
    296 		info("timeout_ns = %ld\n", timeout_ns);
    297 		ret = clock_gettime(CLOCK_MONOTONIC, &ts);
    298 		secs = (ts.tv_nsec + timeout_ns) / 1000000000;
    299 		ts.tv_nsec = ((int64_t)ts.tv_nsec + timeout_ns) % 1000000000;
    300 		ts.tv_sec += secs;
    301 		info("ts.tv_sec  = %ld\n", ts.tv_sec);
    302 		info("ts.tv_nsec = %ld\n", ts.tv_nsec);
    303 		tsp = &ts;
    304 	}
    305 
    306 	if (broadcast)
    307 		wakerfn = broadcast_wakerfn;
    308 
    309 	if (third_party_owner) {
    310 		if (create_rt_thread(&blocker, third_party_blocker,
    311 				     (void *)&blocker_arg, SCHED_FIFO, 1)) {
    312 			error("Creating third party blocker thread failed\n",
    313 			      errno);
    314 			ret = RET_ERROR;
    315 			goto out;
    316 		}
    317 	}
    318 
    319 	atomic_set(&waiters_woken, 0);
    320 	for (i = 0; i < THREAD_MAX; i++) {
    321 		args[i].id = i;
    322 		args[i].timeout = tsp;
    323 		info("Starting thread %d\n", i);
    324 		if (create_rt_thread(&waiter[i], waiterfn, (void *)&args[i],
    325 				     SCHED_FIFO, 1)) {
    326 			error("Creating waiting thread failed\n", errno);
    327 			ret = RET_ERROR;
    328 			goto out;
    329 		}
    330 	}
    331 	waker_arg.lock = lock;
    332 	if (create_rt_thread(&waker, wakerfn, (void *)&waker_arg,
    333 			     SCHED_FIFO, 1)) {
    334 		error("Creating waker thread failed\n", errno);
    335 		ret = RET_ERROR;
    336 		goto out;
    337 	}
    338 
    339 	/* Wait for threads to finish */
    340 	/* Store the first error or failure encountered in waiter_ret */
    341 	waiter_ret = &args[0].ret;
    342 	for (i = 0; i < THREAD_MAX; i++)
    343 		pthread_join(waiter[i],
    344 			     *waiter_ret ? NULL : (void **)&waiter_ret);
    345 
    346 	if (third_party_owner)
    347 		pthread_join(blocker, NULL);
    348 	pthread_join(waker, NULL);
    349 
    350 out:
    351 	if (!ret) {
    352 		if (*waiter_ret)
    353 			ret = *waiter_ret;
    354 		else if (waker_arg.ret < 0)
    355 			ret = waker_arg.ret;
    356 		else if (blocker_arg.ret)
    357 			ret = blocker_arg.ret;
    358 	}
    359 
    360 	return ret;
    361 }
    362 
    363 int main(int argc, char *argv[])
    364 {
    365 	int c, ret;
    366 
    367 	while ((c = getopt(argc, argv, "bchlot:v:")) != -1) {
    368 		switch (c) {
    369 		case 'b':
    370 			broadcast = 1;
    371 			break;
    372 		case 'c':
    373 			log_color(1);
    374 			break;
    375 		case 'h':
    376 			usage(basename(argv[0]));
    377 			exit(0);
    378 		case 'l':
    379 			locked = 1;
    380 			break;
    381 		case 'o':
    382 			owner = 1;
    383 			locked = 0;
    384 			break;
    385 		case 't':
    386 			timeout_ns = atoi(optarg);
    387 			break;
    388 		case 'v':
    389 			log_verbosity(atoi(optarg));
    390 			break;
    391 		default:
    392 			usage(basename(argv[0]));
    393 			exit(1);
    394 		}
    395 	}
    396 
    397 	ksft_print_header();
    398 	ksft_print_msg("%s: Test requeue functionality\n", basename(argv[0]));
    399 	ksft_print_msg(
    400 		"\tArguments: broadcast=%d locked=%d owner=%d timeout=%ldns\n",
    401 		broadcast, locked, owner, timeout_ns);
    402 
    403 	/*
    404 	 * FIXME: unit_test is obsolete now that we parse options and the
    405 	 * various style of runs are done by run.sh - simplify the code and move
    406 	 * unit_test into main()
    407 	 */
    408 	ret = unit_test(broadcast, locked, owner, timeout_ns);
    409 
    410 	print_result(TEST_NAME, ret);
    411 	return ret;
    412 }
    413