1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Copyright (C) 2015 Cyril Hrubis <chrubis@suse.cz>
4 *
5 * Block several threads on a private mutex, then wake them up.
6 */
7
8 #include <sys/types.h>
9
10 #include "futextest.h"
11 #include "futex_utils.h"
12 #include "tst_safe_pthread.h"
13
14 static futex_t futex = FUTEX_INITIALIZER;
15
16 static volatile int threads_flags[55];
17
18 static struct futex_test_variants variants[] = {
19 #if (__NR_futex != __LTP__NR_INVALID_SYSCALL)
20 { .fntype = FUTEX_FN_FUTEX, .desc = "syscall with old kernel spec"},
21 #endif
22
23 #if (__NR_futex_time64 != __LTP__NR_INVALID_SYSCALL)
24 { .fntype = FUTEX_FN_FUTEX64, .desc = "syscall time64 with kernel spec"},
25 #endif
26 };
27
threads_awake(void)28 static int threads_awake(void)
29 {
30 int ret = 0;
31 unsigned int i;
32
33 for (i = 0; i < ARRAY_SIZE(threads_flags); i++) {
34 if (threads_flags[i])
35 ret++;
36 }
37
38 return ret;
39 }
40
clear_threads_awake(void)41 static void clear_threads_awake(void)
42 {
43 unsigned int i;
44
45 for (i = 0; i < ARRAY_SIZE(threads_flags); i++)
46 threads_flags[i] = 0;
47 }
48
threaded(void * arg)49 static void *threaded(void *arg)
50 {
51 struct futex_test_variants *tv = &variants[tst_variant];
52 long i = (long)arg;
53
54 futex_wait(tv->fntype, &futex, futex, NULL, FUTEX_PRIVATE_FLAG);
55
56 threads_flags[i] = 1;
57
58 return NULL;
59 }
60
do_child(void)61 static void do_child(void)
62 {
63 struct futex_test_variants *tv = &variants[tst_variant];
64 int i, j, awake;
65 pthread_t t[55];
66
67 for (i = 0; i < (int)ARRAY_SIZE(t); i++)
68 SAFE_PTHREAD_CREATE(&t[i], NULL, threaded, (void*)((long)i));
69
70 while (wait_for_threads(ARRAY_SIZE(t)))
71 usleep(100);
72
73 for (i = 1; i <= 10; i++) {
74 clear_threads_awake();
75 TEST(futex_wake(tv->fntype, &futex, i, FUTEX_PRIVATE_FLAG));
76 if (i != TST_RET) {
77 tst_res(TFAIL | TTERRNO,
78 "futex_wake() woken up %li threads, expected %i",
79 TST_RET, i);
80 }
81
82 for (j = 0; j < 100000; j++) {
83 awake = threads_awake();
84 if (awake == i)
85 break;
86
87 usleep(100);
88 }
89
90 if (awake == i) {
91 tst_res(TPASS, "futex_wake() woken up %i threads", i);
92 } else {
93 tst_res(TFAIL, "Woken up %i threads, expected %i",
94 awake, i);
95 }
96 }
97
98 TEST(futex_wake(tv->fntype, &futex, 1, FUTEX_PRIVATE_FLAG));
99 if (TST_RET) {
100 tst_res(TFAIL | TTERRNO, "futex_wake() woken up %li, none were waiting",
101 TST_RET);
102 } else {
103 tst_res(TPASS, "futex_wake() woken up 0 threads");
104 }
105
106 for (i = 0; i < (int)ARRAY_SIZE(t); i++)
107 SAFE_PTHREAD_JOIN(t[i], NULL);
108
109 exit(0);
110 }
111
112 /*
113 * We do the real test in a child because with the test -i parameter the loop
114 * that checks that all threads are sleeping may fail with ENOENT. That is
115 * because some of the threads from previous run may still be there.
116 *
117 * Which is because the userspace part of pthread_join() sleeps in a futex on a
118 * pthread tid which is woken up at the end of the exit_mm(tsk) which is before
119 * the process is removed from the parent thread_group list. So there is a
120 * small race window where the readdir() returns the process tid as a directory
121 * under /proc/$PID/tasks/, but the subsequent open() fails with ENOENT because
122 * the thread was removed meanwhile.
123 */
run(void)124 static void run(void)
125 {
126 if (!SAFE_FORK())
127 do_child();
128 }
129
setup(void)130 static void setup(void)
131 {
132 struct futex_test_variants *tv = &variants[tst_variant];
133
134 tst_res(TINFO, "Testing variant: %s", tv->desc);
135 futex_supported_by_kernel(tv->fntype);
136 }
137
138 static struct tst_test test = {
139 .setup = setup,
140 .test_all = run,
141 .test_variants = ARRAY_SIZE(variants),
142 .forks_child = 1,
143 };
144