xref: /linux/tools/testing/selftests/futex/functional/futex_priv_hash.c (revision 4f22ba7eee3e1ed67095edd49fb99db4032343ab)
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 	ASSERT_EQ(ret, 0)
147 		TH_LOG("futex_hash_slots_get() failed: %d, %s", ret, strerror(errno));
148 
149 	ret = pthread_create(&threads[0], NULL, thread_return_fn, NULL);
150 	ASSERT_EQ(ret, 0)
151 		TH_LOG("pthread_create() failed: %d, %s", ret, strerror(errno));
152 
153 	ret = pthread_join(threads[0], NULL);
154 	ASSERT_EQ(ret, 0)
155 		TH_LOG("pthread_join() failed: %d, %s", ret, strerror(errno));
156 
157 	/* First thread, has to initialize private hash */
158 	futex_slots1 = futex_hash_slots_get();
159 	EXPECT_GT(futex_slots1, 0)
160 		TH_LOG("Current hash buckets: %d. %s", futex_slots1, test_msg_auto_create);
161 
162 	online_cpus = sysconf(_SC_NPROCESSORS_ONLN);
163 	ret = pthread_barrier_init(&barrier_main, NULL, MAX_THREADS + 1);
164 	ASSERT_EQ(ret, 0)
165 		TH_LOG("pthread_barrier_init failed: %s", strerror(errno));
166 
167 	ret = pthread_mutex_lock(&global_lock);
168 	ASSERT_EQ(ret, 0)
169 		TH_LOG("pthread_mutex_lock failed: %s", strerror(errno));
170 
171 	counter = 0;
172 	create_max_threads(_metadata, thread_lock_fn);
173 	pthread_barrier_wait(&barrier_main);
174 
175 	/*
176 	 * The current default size of hash buckets is 16. The auto increase
177 	 * works only if more than 16 CPUs are available.
178 	 */
179 	TH_LOG("Online CPUs: %d", online_cpus);
180 	if (online_cpus > 16) {
181 retry_getslots:
182 		futex_slotsn = futex_hash_slots_get();
183 		if (futex_slotsn < 0 || futex_slots1 == futex_slotsn) {
184 			retry--;
185 			/*
186 			 * Auto scaling on thread creation can be slightly delayed
187 			 * because it waits for a RCU grace period twice. The new
188 			 * private hash is assigned upon the first futex operation
189 			 * after grace period.
190 			 * To cover all this for testing purposes the function
191 			 * below will acquire a lock and acquire it again with a
192 			 * 100ms timeout which must timeout. This ensures we
193 			 * sleep for 100ms and issue a futex operation.
194 			 */
195 			if (retry > 0) {
196 				futex_dummy_op(_metadata);
197 				goto retry_getslots;
198 			}
199 			EXPECT_NE(futex_slots1, futex_slotsn) {
200 				TH_LOG("Expected increase of hash buckets but got: %d -> %d. %s",
201 				       futex_slots1, futex_slotsn, test_msg_auto_inc);
202 			}
203 		}
204 	} else {
205 		SKIP(return, "Automatic increase with more than 16 CPUs (only %d online)", online_cpus);
206 	}
207 	ret = pthread_mutex_unlock(&global_lock);
208 
209 	/* Once the user changes it, it has to be what is set */
210 	futex_hash_slots_set_verify(_metadata, 2);
211 	futex_hash_slots_set_verify(_metadata, 4);
212 	futex_hash_slots_set_verify(_metadata, 8);
213 	futex_hash_slots_set_verify(_metadata, 32);
214 	futex_hash_slots_set_verify(_metadata, 16);
215 
216 	ret = futex_hash_slots_set(15);
217 	EXPECT_LT(ret, 0)
218 		TH_LOG("Use 15 slots should fail but succeeded");
219 
220 	futex_hash_slots_set_verify(_metadata, 2);
221 	join_max_threads(_metadata);
222 
223 	EXPECT_EQ(counter, MAX_THREADS)
224 		TH_LOG("Created and waited for %d of %d threads", counter, MAX_THREADS);
225 
226 	counter = 0;
227 	/* Once the user set something, auto resize must be disabled */
228 	ret = pthread_barrier_init(&barrier_main, NULL, MAX_THREADS);
229 	ASSERT_EQ(ret, 0)
230 		TH_LOG("pthread_barrier_init failed: %s", strerror(errno));
231 
232 	create_max_threads(_metadata, thread_lock_fn);
233 	join_max_threads(_metadata);
234 
235 	ret = futex_hash_slots_get();
236 	EXPECT_EQ(ret, 2)
237 		TH_LOG("No more auto-resize after manual setting, got %d", ret);
238 
239 	futex_hash_slots_set_must_fail(_metadata, 1 << 29);
240 	futex_hash_slots_set_verify(_metadata, 4);
241 
242 	/*
243 	 * Once the global hash has been requested, then this requested can not
244 	 * be undone.
245 	 */
246 	ret = futex_hash_slots_set(0);
247 	ASSERT_EQ(ret, 0)
248 		TH_LOG("Global hash request failed: %s", strerror(errno));
249 
250 	futex_hash_slots_set_must_fail(_metadata, 4);
251 	futex_hash_slots_set_must_fail(_metadata, 8);
252 	futex_hash_slots_set_must_fail(_metadata, 8);
253 	futex_hash_slots_set_must_fail(_metadata, 0);
254 	futex_hash_slots_set_must_fail(_metadata, 6);
255 
256 	ret = pthread_barrier_init(&barrier_main, NULL, MAX_THREADS);
257 	ASSERT_EQ(ret, 0)
258 		TH_LOG("pthread_barrier_init failed: %s", strerror(errno));
259 
260 	create_max_threads(_metadata, thread_lock_fn);
261 	join_max_threads(_metadata);
262 
263 	ret = futex_hash_slots_get();
264 	EXPECT_EQ(ret, 0)
265 		TH_LOG("Continue to use global hash failed");
266 }
267 
268 TEST_HARNESS_MAIN
269