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
14static futex_t futex = FUTEX_INITIALIZER;
15
16static volatile int threads_flags[55];
17
18static 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
28static 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
41static 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
49static 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
61static 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 */
124static void run(void)
125{
126	if (!SAFE_FORK())
127		do_child();
128}
129
130static 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
138static struct tst_test test = {
139	.setup = setup,
140	.test_all = run,
141	.test_variants = ARRAY_SIZE(variants),
142	.forks_child = 1,
143};
144