aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/tools/testing/selftests/futex/functional/futex_wait_timeout.c
blob: d183f878360bcd04c72565510c116808b3e82347 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
// SPDX-License-Identifier: GPL-2.0-or-later
/******************************************************************************
 *
 *   Copyright © International Business Machines  Corp., 2009
 *
 * DESCRIPTION
 *      Block on a futex and wait for timeout.
 *
 * AUTHOR
 *      Darren Hart <dvhart@linux.intel.com>
 *
 * HISTORY
 *      2009-Nov-6: Initial version by Darren Hart <dvhart@linux.intel.com>
 *      2021-Apr-26: More test cases by André Almeida <andrealmeid@collabora.com>
 *
 *****************************************************************************/

#include <pthread.h>
#include "futextest.h"
#include "futex2test.h"
#include "logging.h"

#define TEST_NAME "futex-wait-timeout"

static long timeout_ns = 100000;	/* 100us default timeout */
static futex_t futex_pi;
static pthread_barrier_t barrier;

void usage(char *prog)
{
	printf("Usage: %s\n", prog);
	printf("  -c	Use color\n");
	printf("  -h	Display this help message\n");
	printf("  -t N	Timeout in nanoseconds (default: 100,000)\n");
	printf("  -v L	Verbosity level: %d=QUIET %d=CRITICAL %d=INFO\n",
	       VQUIET, VCRITICAL, VINFO);
}

/*
 * Get a PI lock and hold it forever, so the main thread lock_pi will block
 * and we can test the timeout
 */
void *get_pi_lock(void *arg)
{
	int ret;
	volatile futex_t lock = 0;

	ret = futex_lock_pi(&futex_pi, NULL, 0, 0);
	if (ret != 0)
		error("futex_lock_pi failed\n", ret);

	pthread_barrier_wait(&barrier);

	/* Blocks forever */
	ret = futex_wait(&lock, 0, NULL, 0);
	error("futex_wait failed\n", ret);

	return NULL;
}

/*
 * Check if the function returned the expected error
 */
static void test_timeout(int res, int *ret, char *test_name, int err)
{
	if (!res || errno != err) {
		ksft_test_result_fail("%s returned %d\n", test_name,
				      res < 0 ? errno : res);
		*ret = RET_FAIL;
	} else {
		ksft_test_result_pass("%s succeeds\n", test_name);
	}
}

/*
 * Calculate absolute timeout and correct overflow
 */
static int futex_get_abs_timeout(clockid_t clockid, struct timespec *to,
				 long timeout_ns)
{
	if (clock_gettime(clockid, to)) {
		error("clock_gettime failed\n", errno);
		return errno;
	}

	to->tv_nsec += timeout_ns;

	if (to->tv_nsec >= 1000000000) {
		to->tv_sec++;
		to->tv_nsec -= 1000000000;
	}

	return 0;
}

int main(int argc, char *argv[])
{
	futex_t f1 = FUTEX_INITIALIZER;
	int res, ret = RET_PASS;
	struct timespec to;
	pthread_t thread;
	int c;
	struct futex_waitv waitv = {
			.uaddr = (uintptr_t)&f1,
			.val = f1,
			.flags = FUTEX_32,
			.__reserved = 0
		};

	while ((c = getopt(argc, argv, "cht:v:")) != -1) {
		switch (c) {
		case 'c':
			log_color(1);
			break;
		case 'h':
			usage(basename(argv[0]));
			exit(0);
		case 't':
			timeout_ns = atoi(optarg);
			break;
		case 'v':
			log_verbosity(atoi(optarg));
			break;
		default:
			usage(basename(argv[0]));
			exit(1);
		}
	}

	ksft_print_header();
	ksft_set_plan(9);
	ksft_print_msg("%s: Block on a futex and wait for timeout\n",
	       basename(argv[0]));
	ksft_print_msg("\tArguments: timeout=%ldns\n", timeout_ns);

	pthread_barrier_init(&barrier, NULL, 2);
	pthread_create(&thread, NULL, get_pi_lock, NULL);

	/* initialize relative timeout */
	to.tv_sec = 0;
	to.tv_nsec = timeout_ns;

	res = futex_wait(&f1, f1, &to, 0);
	test_timeout(res, &ret, "futex_wait relative", ETIMEDOUT);

	/* FUTEX_WAIT_BITSET with CLOCK_REALTIME */
	if (futex_get_abs_timeout(CLOCK_REALTIME, &to, timeout_ns))
		return RET_FAIL;
	res = futex_wait_bitset(&f1, f1, &to, 1, FUTEX_CLOCK_REALTIME);
	test_timeout(res, &ret, "futex_wait_bitset realtime", ETIMEDOUT);

	/* FUTEX_WAIT_BITSET with CLOCK_MONOTONIC */
	if (futex_get_abs_timeout(CLOCK_MONOTONIC, &to, timeout_ns))
		return RET_FAIL;
	res = futex_wait_bitset(&f1, f1, &to, 1, 0);
	test_timeout(res, &ret, "futex_wait_bitset monotonic", ETIMEDOUT);

	/* FUTEX_WAIT_REQUEUE_PI with CLOCK_REALTIME */
	if (futex_get_abs_timeout(CLOCK_REALTIME, &to, timeout_ns))
		return RET_FAIL;
	res = futex_wait_requeue_pi(&f1, f1, &futex_pi, &to, FUTEX_CLOCK_REALTIME);
	test_timeout(res, &ret, "futex_wait_requeue_pi realtime", ETIMEDOUT);

	/* FUTEX_WAIT_REQUEUE_PI with CLOCK_MONOTONIC */
	if (futex_get_abs_timeout(CLOCK_MONOTONIC, &to, timeout_ns))
		return RET_FAIL;
	res = futex_wait_requeue_pi(&f1, f1, &futex_pi, &to, 0);
	test_timeout(res, &ret, "futex_wait_requeue_pi monotonic", ETIMEDOUT);

	/* Wait until the other thread calls futex_lock_pi() */
	pthread_barrier_wait(&barrier);
	pthread_barrier_destroy(&barrier);
	/*
	 * FUTEX_LOCK_PI with CLOCK_REALTIME
	 * Due to historical reasons, FUTEX_LOCK_PI supports only realtime
	 * clock, but requires the caller to not set CLOCK_REALTIME flag.
	 *
	 * If you call FUTEX_LOCK_PI with a monotonic clock, it'll be
	 * interpreted as a realtime clock, and (unless you mess your machine's
	 * time or your time machine) the monotonic clock value is always
	 * smaller than realtime and the syscall will timeout immediately.
	 */
	if (futex_get_abs_timeout(CLOCK_REALTIME, &to, timeout_ns))
		return RET_FAIL;
	res = futex_lock_pi(&futex_pi, &to, 0, 0);
	test_timeout(res, &ret, "futex_lock_pi realtime", ETIMEDOUT);

	/* Test operations that don't support FUTEX_CLOCK_REALTIME */
	res = futex_lock_pi(&futex_pi, NULL, 0, FUTEX_CLOCK_REALTIME);
	test_timeout(res, &ret, "futex_lock_pi invalid timeout flag", ENOSYS);

	/* futex_waitv with CLOCK_MONOTONIC */
	if (futex_get_abs_timeout(CLOCK_MONOTONIC, &to, timeout_ns))
		return RET_FAIL;
	res = futex_waitv(&waitv, 1, 0, &to, CLOCK_MONOTONIC);
	test_timeout(res, &ret, "futex_waitv monotonic", ETIMEDOUT);

	/* futex_waitv with CLOCK_REALTIME */
	if (futex_get_abs_timeout(CLOCK_REALTIME, &to, timeout_ns))
		return RET_FAIL;
	res = futex_waitv(&waitv, 1, 0, &to, CLOCK_REALTIME);
	test_timeout(res, &ret, "futex_waitv realtime", ETIMEDOUT);

	ksft_print_cnts();
	return ret;
}