• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Copyright (C) 2019 Xiao Yang <ice_yangxiao@163.com>
4  *
5  * Description:
6  * Testcase to check the basic functionality of futex(FUTEX_CMP_REQUEUE).
7  * futex(FUTEX_CMP_REQUEUE) can wake up the number of waiters specified
8  * by val argument and then requeue the number of waiters limited by val2
9  * argument(i.e. move some remaining waiters from uaddr to uaddr2 address).
10  */
11 
12 #include <errno.h>
13 #include <sys/wait.h>
14 #include <stdlib.h>
15 #include <linux/futex.h>
16 #include <sys/time.h>
17 
18 #include "tst_timer_test.h"
19 #include "tst_test.h"
20 #include "futextest.h"
21 
22 struct shared_data {
23 	futex_t futexes[2];
24 	int spurious;
25 	int test_done;
26 };
27 
28 static struct shared_data *sd;
29 static int max_sleep_ms;
30 
31 static struct tcase {
32 	int num_waiters;
33 	int set_wakes;
34 	int set_requeues;
35 } tcases[] = {
36 	{10, 3, 7},
37 	{10, 0, 10},
38 	{10, 2, 6},
39 	{100, 50, 50},
40 	{100, 0, 70},
41 	{1000, 100, 900},
42 	{1000, 300, 500},
43 };
44 
do_child(void)45 static void do_child(void)
46 {
47 	int slept_for_ms = 0;
48 	struct timespec usec = tst_ms_to_timespec(max_sleep_ms);
49 	int pid = getpid();
50 	int ret = 0;
51 
52 	if (futex_wait(&sd->futexes[0], sd->futexes[0], &usec, 0) == -1) {
53 		if (errno == EAGAIN) {
54 			/* spurious wakeup or signal */
55 			tst_atomic_inc(&sd->spurious);
56 		} else {
57 			tst_res(TFAIL | TERRNO, "process %d wasn't woken up",
58 				pid);
59 			ret = 1;
60 		}
61 	}
62 
63 	/* make sure TST_PROCESS_STATE_WAIT() can always succeed */
64 	while (!tst_atomic_load(&sd->test_done)
65 		&& (slept_for_ms < max_sleep_ms)) {
66 		usleep(50000);
67 		slept_for_ms += 50;
68 	}
69 
70 	exit(ret);
71 }
72 
verify_futex_cmp_requeue(unsigned int n)73 static void verify_futex_cmp_requeue(unsigned int n)
74 {
75 	int num_requeues = 0, num_waits = 0, num_total = 0;
76 	int i, status, spurious, woken_up;
77 	struct tcase *tc = &tcases[n];
78 	int pid[tc->num_waiters];
79 	int exp_ret = tc->set_wakes + tc->set_requeues;
80 
81 	tst_atomic_store(0, &sd->spurious);
82 	tst_atomic_store(0, &sd->test_done);
83 	for (i = 0; i < tc->num_waiters; i++) {
84 		pid[i] = SAFE_FORK();
85 		if (!pid[i])
86 			do_child();
87 	}
88 
89 	for (i = 0; i < tc->num_waiters; i++)
90 		TST_PROCESS_STATE_WAIT(pid[i], 'S');
91 
92 	tst_res(TINFO, "Test %d: waiters: %d, wakes: %d, requeues: %d",
93 		n, tc->num_waiters, tc->set_wakes, tc->set_requeues);
94 
95 	/*
96 	 * change futex value, so any spurious wakeups or signals after
97 	 * this point get bounced back to userspace.
98 	 */
99 	sd->futexes[0]++;
100 	sd->futexes[1]++;
101 
102 	/*
103 	 * Wakes up a maximum of tc->set_wakes waiters. tc->set_requeues
104 	 * specifies an upper limit on the number of waiters that are requeued.
105 	 * Returns the total number of waiters that were woken up or requeued.
106 	 */
107 	TEST(futex_cmp_requeue(&sd->futexes[0], sd->futexes[0], &sd->futexes[1],
108 		tc->set_wakes, tc->set_requeues, 0));
109 
110 	/* Fail if more than requested wakes + requeues were returned */
111 	if (TST_RET > exp_ret) {
112 		tst_res(TFAIL, "futex_cmp_requeue() returned %ld, expected <= %d",
113 			TST_RET, exp_ret);
114 	} else {
115 		tst_res(TINFO, "futex_cmp_requeue() returned %ld", TST_RET);
116 	}
117 
118 	num_requeues = futex_wake(&sd->futexes[1], tc->num_waiters, 0);
119 	num_waits = futex_wake(&sd->futexes[0], tc->num_waiters, 0);
120 
121 	tst_atomic_store(1, &sd->test_done);
122 	for (i = 0; i < tc->num_waiters; i++) {
123 		SAFE_WAITPID(pid[i], &status, 0);
124 		if (WIFEXITED(status) && !WEXITSTATUS(status))
125 			num_total++;
126 	}
127 
128 	spurious = tst_atomic_load(&sd->spurious);
129 	tst_res(TINFO, "children woken, futex0: %d, futex1: %d, "
130 		"spurious wakeups: %d",
131 		num_waits, num_requeues, spurious);
132 
133 	/* Fail if any waiter timed out */
134 	if (num_total != tc->num_waiters) {
135 		tst_res(TFAIL, "%d waiters were not woken up normally",
136 			tc->num_waiters - num_total);
137 		return;
138 	}
139 
140 	/*
141 	 * num_requeues should be in range:
142 	 *     (tc->set_requeues - spurious, tc->set_requeues)
143 	 *
144 	 * Fewer processes than requested can be requeued at futex1
145 	 * if some woke up spuriously. Finding more processes than
146 	 * requested at futex1 is always a failure.
147 	 */
148 	if ((num_requeues < tc->set_requeues - spurious)
149 		|| (num_requeues > tc->set_requeues)) {
150 		tst_res(TFAIL,
151 			"requeued %d waiters, expected range: (%d, %d)",
152 			num_requeues, tc->set_requeues - spurious,
153 			tc->set_requeues);
154 		return;
155 	}
156 
157 	/*
158 	 * woken_up = (TST_RET - num_requeues) should be in range:
159 	 *     (tc->set_wakes - spurious, tc->set_wakes + spurious)
160 	 *
161 	 * Fewer processes than requested can be woken up, if some of
162 	 * them woke up spuriously before requeue. More processes than
163 	 * requested may appear to be woken up, if some woke up
164 	 * spuriously after requeue.
165 	 */
166 	woken_up = TST_RET - num_requeues;
167 	if ((woken_up < tc->set_wakes - spurious)
168 		|| (woken_up > tc->set_wakes + spurious)) {
169 		tst_res(TFAIL,
170 			"woken up %d, expected range (%d, %d)",
171 			woken_up, tc->set_wakes - spurious,
172 			tc->set_wakes + spurious);
173 		return;
174 	}
175 
176 	tst_res(TPASS, "futex_cmp_requeue()");
177 }
178 
setup(void)179 static void setup(void)
180 {
181 	max_sleep_ms = tst_multiply_timeout(5000);
182 
183 	sd = SAFE_MMAP(NULL, sizeof(*sd), PROT_READ | PROT_WRITE,
184 			    MAP_ANONYMOUS | MAP_SHARED, -1, 0);
185 
186 	sd->futexes[0] = FUTEX_INITIALIZER;
187 	sd->futexes[1] = FUTEX_INITIALIZER + 1000;
188 }
189 
cleanup(void)190 static void cleanup(void)
191 {
192 	if (sd)
193 		SAFE_MUNMAP((void *)sd, sizeof(*sd));
194 }
195 
196 static struct tst_test test = {
197 	.setup = setup,
198 	.cleanup = cleanup,
199 	.tcnt = ARRAY_SIZE(tcases),
200 	.test = verify_futex_cmp_requeue,
201 	.forks_child = 1,
202 };
203