xref: /linux/tools/testing/selftests/futex/functional/futex_priv_hash.c (revision 67f8bc848ee31831336bd478e57d2f993551902e)
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Copyright (C) 2025 Sebastian Andrzej Siewior <bigeasy@linutronix.de>
4  */
5 
6 #define _GNU_SOURCE
7 
8 #include <errno.h>
9 #include <pthread.h>
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13 #include <unistd.h>
14 
15 #include <linux/prctl.h>
16 #include <sys/prctl.h>
17 
18 #include "kselftest_harness.h"
19 
20 #define MAX_THREADS	64
21 
22 static pthread_barrier_t barrier_main;
23 static pthread_mutex_t global_lock;
24 static pthread_t threads[MAX_THREADS];
25 static int counter;
26 
27 #ifndef PR_FUTEX_HASH
28 #define PR_FUTEX_HASH			78
29 # define PR_FUTEX_HASH_SET_SLOTS	1
30 # define PR_FUTEX_HASH_GET_SLOTS	2
31 #endif
32 
33 static int futex_hash_slots_set(unsigned int slots)
34 {
35 	return prctl(PR_FUTEX_HASH, PR_FUTEX_HASH_SET_SLOTS, slots, 0);
36 }
37 
38 static int futex_hash_slots_get(void)
39 {
40 	return prctl(PR_FUTEX_HASH, PR_FUTEX_HASH_GET_SLOTS);
41 }
42 
43 static void futex_hash_slots_set_verify(struct __test_metadata *_metadata, int slots)
44 {
45 	int ret;
46 
47 	ret = futex_hash_slots_set(slots);
48 	ASSERT_EQ(ret, 0)
49 		TH_LOG("Failed to set slots to %d: %s", slots, strerror(errno));
50 
51 	ret = futex_hash_slots_get();
52 	ASSERT_EQ(ret, slots) {
53 		TH_LOG("Set %d slots but PR_FUTEX_HASH_GET_SLOTS returns: %d, %s",
54 		       slots, ret, strerror(errno));
55 	}
56 }
57 
58 static void futex_hash_slots_set_must_fail(struct __test_metadata *_metadata, int slots)
59 {
60 	int ret;
61 
62 	ret = futex_hash_slots_set(slots);
63 	EXPECT_LT(ret, 0)
64 		TH_LOG("futex_hash_slots_set(%d) should fail but succeeded", slots);
65 }
66 
67 static void *thread_return_fn(void *arg)
68 {
69 	return NULL;
70 }
71 
72 static void *thread_lock_fn(void *arg)
73 {
74 	pthread_barrier_wait(&barrier_main);
75 
76 	pthread_mutex_lock(&global_lock);
77 	counter++;
78 	usleep(20);
79 	pthread_mutex_unlock(&global_lock);
80 	return NULL;
81 }
82 
83 static void create_max_threads(struct __test_metadata *_metadata, void *(*thread_fn)(void *))
84 {
85 	int i, ret;
86 
87 	for (i = 0; i < MAX_THREADS; i++) {
88 		ret = pthread_create(&threads[i], NULL, thread_fn, NULL);
89 		ASSERT_EQ(ret, 0)
90 			TH_LOG("pthread_create failed: %s", strerror(errno));
91 	}
92 }
93 
94 static void join_max_threads(struct __test_metadata *_metadata)
95 {
96 	int i, ret;
97 
98 	for (i = 0; i < MAX_THREADS; i++) {
99 		ret = pthread_join(threads[i], NULL);
100 		ASSERT_EQ(ret, 0)
101 			TH_LOG("pthread_join failed for thread %d: %s", i, strerror(errno));
102 	}
103 }
104 
105 #define SEC_IN_NSEC	1000000000
106 #define MSEC_IN_NSEC	1000000
107 
108 static void futex_dummy_op(struct __test_metadata *_metadata)
109 {
110 	pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
111 	struct timespec timeout;
112 	int ret;
113 
114 	pthread_mutex_lock(&lock);
115 	clock_gettime(CLOCK_REALTIME, &timeout);
116 	timeout.tv_nsec += 100 * MSEC_IN_NSEC;
117 	if (timeout.tv_nsec >=  SEC_IN_NSEC) {
118 		timeout.tv_nsec -= SEC_IN_NSEC;
119 		timeout.tv_sec++;
120 	}
121 	ret = pthread_mutex_timedlock(&lock, &timeout);
122 	ASSERT_NE(ret, 0)
123 		TH_LOG("Successfully locked an already locked mutex");
124 
125 	ASSERT_EQ(ret, ETIMEDOUT)
126 		TH_LOG("pthread_mutex_timedlock() did not timeout: %d", ret);
127 }
128 
129 static const char *test_msg_auto_create = "Automatic hash bucket init on thread creation.\n";
130 static const char *test_msg_auto_inc = "Automatic increase with more than 16 CPUs\n";
131 
132 TEST(priv_hash)
133 {
134 	int futex_slots1, futex_slotsn, online_cpus;
135 	pthread_mutexattr_t mutex_attr_pi;
136 	int ret, retry = 20;
137 
138 	ret = pthread_mutexattr_init(&mutex_attr_pi);
139 	ret |= pthread_mutexattr_setprotocol(&mutex_attr_pi, PTHREAD_PRIO_INHERIT);
140 	ret |= pthread_mutex_init(&global_lock, &mutex_attr_pi);
141 	ASSERT_EQ(ret, 0)
142 		TH_LOG("Failed to initialize pthread mutex");
143 
144 	/* First thread, expect to be 0, not yet initialized */
145 	ret = futex_hash_slots_get();
146 	if (ret < 0 && errno == EINVAL)
147 		SKIP(return, "PR_FUTEX_HASH not supported by kernel");
148 
149 	ASSERT_EQ(ret, 0)
150 		TH_LOG("futex_hash_slots_get() failed: %d, %s", ret, strerror(errno));
151 
152 	ret = pthread_create(&threads[0], NULL, thread_return_fn, NULL);
153 	ASSERT_EQ(ret, 0)
154 		TH_LOG("pthread_create() failed: %d, %s", ret, strerror(errno));
155 
156 	ret = pthread_join(threads[0], NULL);
157 	ASSERT_EQ(ret, 0)
158 		TH_LOG("pthread_join() failed: %d, %s", ret, strerror(errno));
159 
160 	/* First thread, has to initialize private hash */
161 	futex_slots1 = futex_hash_slots_get();
162 	EXPECT_GT(futex_slots1, 0)
163 		TH_LOG("Current hash buckets: %d. %s", futex_slots1, test_msg_auto_create);
164 
165 	online_cpus = sysconf(_SC_NPROCESSORS_ONLN);
166 	ret = pthread_barrier_init(&barrier_main, NULL, MAX_THREADS + 1);
167 	ASSERT_EQ(ret, 0)
168 		TH_LOG("pthread_barrier_init failed: %s", strerror(errno));
169 
170 	ret = pthread_mutex_lock(&global_lock);
171 	ASSERT_EQ(ret, 0)
172 		TH_LOG("pthread_mutex_lock failed: %s", strerror(errno));
173 
174 	counter = 0;
175 	create_max_threads(_metadata, thread_lock_fn);
176 	pthread_barrier_wait(&barrier_main);
177 
178 	/*
179 	 * The current default size of hash buckets is 16. The auto increase
180 	 * works only if more than 16 CPUs are available.
181 	 */
182 	TH_LOG("Online CPUs: %d", online_cpus);
183 	if (online_cpus > 16) {
184 retry_getslots:
185 		futex_slotsn = futex_hash_slots_get();
186 		if (futex_slotsn < 0 || futex_slots1 == futex_slotsn) {
187 			retry--;
188 			/*
189 			 * Auto scaling on thread creation can be slightly delayed
190 			 * because it waits for a RCU grace period twice. The new
191 			 * private hash is assigned upon the first futex operation
192 			 * after grace period.
193 			 * To cover all this for testing purposes the function
194 			 * below will acquire a lock and acquire it again with a
195 			 * 100ms timeout which must timeout. This ensures we
196 			 * sleep for 100ms and issue a futex operation.
197 			 */
198 			if (retry > 0) {
199 				futex_dummy_op(_metadata);
200 				goto retry_getslots;
201 			}
202 			EXPECT_NE(futex_slots1, futex_slotsn) {
203 				TH_LOG("Expected increase of hash buckets but got: %d -> %d. %s",
204 				       futex_slots1, futex_slotsn, test_msg_auto_inc);
205 			}
206 		}
207 	} else {
208 		SKIP(return, "Automatic increase with more than 16 CPUs (only %d online)", online_cpus);
209 	}
210 	ret = pthread_mutex_unlock(&global_lock);
211 
212 	/* Once the user changes it, it has to be what is set */
213 	futex_hash_slots_set_verify(_metadata, 2);
214 	futex_hash_slots_set_verify(_metadata, 4);
215 	futex_hash_slots_set_verify(_metadata, 8);
216 	futex_hash_slots_set_verify(_metadata, 32);
217 	futex_hash_slots_set_verify(_metadata, 16);
218 
219 	ret = futex_hash_slots_set(15);
220 	EXPECT_LT(ret, 0)
221 		TH_LOG("Use 15 slots should fail but succeeded");
222 
223 	futex_hash_slots_set_verify(_metadata, 2);
224 	join_max_threads(_metadata);
225 
226 	EXPECT_EQ(counter, MAX_THREADS)
227 		TH_LOG("Created and waited for %d of %d threads", counter, MAX_THREADS);
228 
229 	counter = 0;
230 	/* Once the user set something, auto resize must be disabled */
231 	ret = pthread_barrier_init(&barrier_main, NULL, MAX_THREADS);
232 	ASSERT_EQ(ret, 0)
233 		TH_LOG("pthread_barrier_init failed: %s", strerror(errno));
234 
235 	create_max_threads(_metadata, thread_lock_fn);
236 	join_max_threads(_metadata);
237 
238 	ret = futex_hash_slots_get();
239 	EXPECT_EQ(ret, 2)
240 		TH_LOG("No more auto-resize after manual setting, got %d", ret);
241 
242 	futex_hash_slots_set_must_fail(_metadata, 1 << 29);
243 	futex_hash_slots_set_verify(_metadata, 4);
244 
245 	/*
246 	 * Once the global hash has been requested, then this requested can not
247 	 * be undone.
248 	 */
249 	ret = futex_hash_slots_set(0);
250 	ASSERT_EQ(ret, 0)
251 		TH_LOG("Global hash request failed: %s", strerror(errno));
252 
253 	futex_hash_slots_set_must_fail(_metadata, 4);
254 	futex_hash_slots_set_must_fail(_metadata, 8);
255 	futex_hash_slots_set_must_fail(_metadata, 8);
256 	futex_hash_slots_set_must_fail(_metadata, 0);
257 	futex_hash_slots_set_must_fail(_metadata, 6);
258 
259 	ret = pthread_barrier_init(&barrier_main, NULL, MAX_THREADS);
260 	ASSERT_EQ(ret, 0)
261 		TH_LOG("pthread_barrier_init failed: %s", strerror(errno));
262 
263 	create_max_threads(_metadata, thread_lock_fn);
264 	join_max_threads(_metadata);
265 
266 	ret = futex_hash_slots_get();
267 	EXPECT_EQ(ret, 0)
268 		TH_LOG("Continue to use global hash failed");
269 }
270 
271 TEST_HARNESS_MAIN
272