1 | /* Generic test for CPU affinity functions, multi-threaded variant. |
2 | Copyright (C) 2015-2022 Free Software Foundation, Inc. |
3 | This file is part of the GNU C Library. |
4 | |
5 | The GNU C Library is free software; you can redistribute it and/or |
6 | modify it under the terms of the GNU Lesser General Public |
7 | License as published by the Free Software Foundation; either |
8 | version 2.1 of the License, or (at your option) any later version. |
9 | |
10 | The GNU C Library is distributed in the hope that it will be useful, |
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
13 | Lesser General Public License for more details. |
14 | |
15 | You should have received a copy of the GNU Lesser General Public |
16 | License along with the GNU C Library; if not, see |
17 | <https://www.gnu.org/licenses/>. */ |
18 | |
19 | /* Before including this file, a test has to declare the helper |
20 | getaffinity and setaffinity functions described in |
21 | tst-skeleton-affinity.c, which is included below. */ |
22 | |
23 | #include <errno.h> |
24 | #include <pthread.h> |
25 | #include <stdbool.h> |
26 | #include <stdlib.h> |
27 | #include <support/xthread.h> |
28 | #include <sys/time.h> |
29 | |
30 | struct conf; |
31 | static bool early_test (struct conf *); |
32 | |
33 | /* Arbitrary run time for each pass. */ |
34 | #define PASS_TIMEOUT 2 |
35 | |
36 | /* There are two passes (one with sched_yield, one without), and we |
37 | double the timeout to be on the safe side. */ |
38 | #define TIMEOUT (2 * PASS_TIMEOUT * 2) |
39 | |
40 | #include "tst-skeleton-affinity.c" |
41 | |
42 | /* 0 if still running, 1 of stopping requested. */ |
43 | static int still_running; |
44 | |
45 | /* 0 if no scheduling failures, 1 if failures are encountered. */ |
46 | static int failed; |
47 | |
48 | static void * |
49 | thread_burn_one_cpu (void *closure) |
50 | { |
51 | int cpu = (uintptr_t) closure; |
52 | while (__atomic_load_n (&still_running, __ATOMIC_RELAXED) == 0) |
53 | { |
54 | int current = sched_getcpu (); |
55 | if (sched_getcpu () != cpu) |
56 | { |
57 | printf (format: "error: Pinned thread %d ran on impossible cpu %d\n" , |
58 | cpu, current); |
59 | __atomic_store_n (&failed, 1, __ATOMIC_RELAXED); |
60 | /* Terminate early. */ |
61 | __atomic_store_n (&still_running, 1, __ATOMIC_RELAXED); |
62 | } |
63 | } |
64 | return NULL; |
65 | } |
66 | |
67 | struct burn_thread |
68 | { |
69 | pthread_t self; |
70 | struct conf *conf; |
71 | cpu_set_t *initial_set; |
72 | cpu_set_t *seen_set; |
73 | int thread; |
74 | }; |
75 | |
76 | static void * |
77 | thread_burn_any_cpu (void *closure) |
78 | { |
79 | struct burn_thread *param = closure; |
80 | |
81 | /* Schedule this thread around a bit to see if it lands on another |
82 | CPU. Run this for 2 seconds, once with sched_yield, once |
83 | without. */ |
84 | for (int pass = 1; pass <= 2; ++pass) |
85 | { |
86 | time_t start = time (NULL); |
87 | while (time (NULL) - start <= PASS_TIMEOUT) |
88 | { |
89 | int cpu = sched_getcpu (); |
90 | if (cpu > param->conf->last_cpu |
91 | || !CPU_ISSET_S (cpu, CPU_ALLOC_SIZE (param->conf->set_size), |
92 | param->initial_set)) |
93 | { |
94 | printf (format: "error: Unpinned thread %d ran on impossible CPU %d\n" , |
95 | param->thread, cpu); |
96 | __atomic_store_n (&failed, 1, __ATOMIC_RELAXED); |
97 | return NULL; |
98 | } |
99 | CPU_SET_S (cpu, CPU_ALLOC_SIZE (param->conf->set_size), |
100 | param->seen_set); |
101 | if (pass == 1) |
102 | sched_yield (); |
103 | } |
104 | } |
105 | return NULL; |
106 | } |
107 | |
108 | static void |
109 | stop_and_join_threads (struct conf *conf, cpu_set_t *set, |
110 | pthread_t *pinned_first, pthread_t *pinned_last, |
111 | struct burn_thread *other_first, |
112 | struct burn_thread *other_last) |
113 | { |
114 | __atomic_store_n (&still_running, 1, __ATOMIC_RELAXED); |
115 | for (pthread_t *p = pinned_first; p < pinned_last; ++p) |
116 | { |
117 | int cpu = p - pinned_first; |
118 | if (!CPU_ISSET_S (cpu, CPU_ALLOC_SIZE (conf->set_size), set)) |
119 | continue; |
120 | |
121 | int ret = pthread_join (th: *p, NULL); |
122 | if (ret != 0) |
123 | { |
124 | printf (format: "error: Failed to join thread %d: %s\n" , cpu, strerror (errnum: ret)); |
125 | fflush (stdout); |
126 | /* Cannot shut down cleanly with threads still running. */ |
127 | abort (); |
128 | } |
129 | } |
130 | |
131 | for (struct burn_thread *p = other_first; p < other_last; ++p) |
132 | { |
133 | int cpu = p - other_first; |
134 | if (!CPU_ISSET_S (cpu, CPU_ALLOC_SIZE (conf->set_size), set)) |
135 | continue; |
136 | |
137 | int ret = pthread_join (th: p->self, NULL); |
138 | if (ret != 0) |
139 | { |
140 | printf (format: "error: Failed to join thread %d: %s\n" , cpu, strerror (errnum: ret)); |
141 | fflush (stdout); |
142 | /* Cannot shut down cleanly with threads still running. */ |
143 | abort (); |
144 | } |
145 | } |
146 | } |
147 | |
148 | /* Tries to check that the initial set of CPUs is complete and that |
149 | the main thread will not run on any other threads. */ |
150 | static bool |
151 | early_test (struct conf *conf) |
152 | { |
153 | pthread_t *pinned_threads |
154 | = calloc (nmemb: conf->last_cpu + 1, size: sizeof (*pinned_threads)); |
155 | struct burn_thread *other_threads |
156 | = calloc (nmemb: conf->last_cpu + 1, size: sizeof (*other_threads)); |
157 | cpu_set_t *initial_set = CPU_ALLOC (conf->set_size); |
158 | cpu_set_t *scratch_set = CPU_ALLOC (conf->set_size); |
159 | |
160 | if (pinned_threads == NULL || other_threads == NULL |
161 | || initial_set == NULL || scratch_set == NULL) |
162 | { |
163 | puts (s: "error: Memory allocation failure" ); |
164 | return false; |
165 | } |
166 | if (getaffinity (CPU_ALLOC_SIZE (conf->set_size), set: initial_set) < 0) |
167 | { |
168 | printf (format: "error: pthread_getaffinity_np failed: %m\n" ); |
169 | return false; |
170 | } |
171 | for (int cpu = 0; cpu <= conf->last_cpu; ++cpu) |
172 | { |
173 | if (!CPU_ISSET_S (cpu, CPU_ALLOC_SIZE (conf->set_size), initial_set)) |
174 | continue; |
175 | other_threads[cpu].conf = conf; |
176 | other_threads[cpu].initial_set = initial_set; |
177 | other_threads[cpu].thread = cpu; |
178 | other_threads[cpu].seen_set = CPU_ALLOC (conf->set_size); |
179 | if (other_threads[cpu].seen_set == NULL) |
180 | { |
181 | puts (s: "error: Memory allocation failure" ); |
182 | return false; |
183 | } |
184 | CPU_ZERO_S (CPU_ALLOC_SIZE (conf->set_size), |
185 | other_threads[cpu].seen_set); |
186 | } |
187 | |
188 | pthread_attr_t attr; |
189 | int ret = pthread_attr_init (attr: &attr); |
190 | if (ret != 0) |
191 | { |
192 | printf (format: "error: pthread_attr_init failed: %s\n" , strerror (errnum: ret)); |
193 | return false; |
194 | } |
195 | support_set_small_thread_stack_size (attr: &attr); |
196 | |
197 | /* Spawn a thread pinned to each available CPU. */ |
198 | for (int cpu = 0; cpu <= conf->last_cpu; ++cpu) |
199 | { |
200 | if (!CPU_ISSET_S (cpu, CPU_ALLOC_SIZE (conf->set_size), initial_set)) |
201 | continue; |
202 | CPU_ZERO_S (CPU_ALLOC_SIZE (conf->set_size), scratch_set); |
203 | CPU_SET_S (cpu, CPU_ALLOC_SIZE (conf->set_size), scratch_set); |
204 | ret = pthread_attr_setaffinity_np |
205 | (attr: &attr, CPU_ALLOC_SIZE (conf->set_size), cpuset: scratch_set); |
206 | if (ret != 0) |
207 | { |
208 | printf (format: "error: pthread_attr_setaffinity_np for CPU %d failed: %s\n" , |
209 | cpu, strerror (errnum: ret)); |
210 | stop_and_join_threads (conf, set: initial_set, |
211 | pinned_first: pinned_threads, pinned_last: pinned_threads + cpu, |
212 | NULL, NULL); |
213 | return false; |
214 | } |
215 | ret = pthread_create (newthread: pinned_threads + cpu, attr: &attr, |
216 | start_routine: thread_burn_one_cpu, arg: (void *) (uintptr_t) cpu); |
217 | if (ret != 0) |
218 | { |
219 | printf (format: "error: pthread_create for CPU %d failed: %s\n" , |
220 | cpu, strerror (errnum: ret)); |
221 | stop_and_join_threads (conf, set: initial_set, |
222 | pinned_first: pinned_threads, pinned_last: pinned_threads + cpu, |
223 | NULL, NULL); |
224 | return false; |
225 | } |
226 | } |
227 | |
228 | /* Spawn another set of threads running on all CPUs. */ |
229 | for (int cpu = 0; cpu <= conf->last_cpu; ++cpu) |
230 | { |
231 | if (!CPU_ISSET_S (cpu, CPU_ALLOC_SIZE (conf->set_size), initial_set)) |
232 | continue; |
233 | ret = pthread_create (newthread: &other_threads[cpu].self, |
234 | attr: support_small_stack_thread_attribute (), |
235 | start_routine: thread_burn_any_cpu, arg: other_threads + cpu); |
236 | if (ret != 0) |
237 | { |
238 | printf (format: "error: pthread_create for thread %d failed: %s\n" , |
239 | cpu, strerror (errnum: ret)); |
240 | stop_and_join_threads (conf, set: initial_set, |
241 | pinned_first: pinned_threads, |
242 | pinned_last: pinned_threads + conf->last_cpu + 1, |
243 | other_first: other_threads, other_last: other_threads + cpu); |
244 | return false; |
245 | } |
246 | } |
247 | |
248 | /* Main thread. */ |
249 | struct burn_thread main_thread; |
250 | main_thread.conf = conf; |
251 | main_thread.initial_set = initial_set; |
252 | main_thread.seen_set = scratch_set; |
253 | main_thread.thread = -1; |
254 | CPU_ZERO_S (CPU_ALLOC_SIZE (conf->set_size), main_thread.seen_set); |
255 | thread_burn_any_cpu (closure: &main_thread); |
256 | stop_and_join_threads (conf, set: initial_set, |
257 | pinned_first: pinned_threads, |
258 | pinned_last: pinned_threads + conf->last_cpu + 1, |
259 | other_first: other_threads, other_last: other_threads + conf->last_cpu + 1); |
260 | |
261 | printf (format: "info: Main thread ran on %d CPU(s) of %d available CPU(s)\n" , |
262 | CPU_COUNT_S (CPU_ALLOC_SIZE (conf->set_size), scratch_set), |
263 | CPU_COUNT_S (CPU_ALLOC_SIZE (conf->set_size), initial_set)); |
264 | CPU_ZERO_S (CPU_ALLOC_SIZE (conf->set_size), scratch_set); |
265 | for (int cpu = 0; cpu <= conf->last_cpu; ++cpu) |
266 | { |
267 | if (!CPU_ISSET_S (cpu, CPU_ALLOC_SIZE (conf->set_size), initial_set)) |
268 | continue; |
269 | CPU_OR_S (CPU_ALLOC_SIZE (conf->set_size), |
270 | scratch_set, scratch_set, other_threads[cpu].seen_set); |
271 | CPU_FREE (other_threads[cpu].seen_set); |
272 | } |
273 | printf (format: "info: Other threads ran on %d CPU(s)\n" , |
274 | CPU_COUNT_S (CPU_ALLOC_SIZE (conf->set_size), scratch_set));; |
275 | |
276 | |
277 | pthread_attr_destroy (attr: &attr); |
278 | CPU_FREE (scratch_set); |
279 | CPU_FREE (initial_set); |
280 | free (ptr: pinned_threads); |
281 | free (ptr: other_threads); |
282 | return failed == 0; |
283 | } |
284 | |