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
30struct conf;
31static 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. */
43static int still_running;
44
45/* 0 if no scheduling failures, 1 if failures are encountered. */
46static int failed;
47
48static void *
49thread_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
67struct 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
76static void *
77thread_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
108static void
109stop_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. */
150static bool
151early_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

source code of glibc/sysdeps/unix/sysv/linux/tst-skeleton-thread-affinity.c