]>
Commit | Line | Data |
---|---|---|
214d78a2 A |
1 | #include <pthread.h> |
2 | #include <stdlib.h> | |
3 | #include <string.h> | |
4 | #include <unistd.h> | |
5 | #include <stdbool.h> | |
6 | #include <errno.h> | |
7 | #include <TargetConditionals.h> | |
8 | ||
9 | #include <pthread/pthread_spis.h> | |
10 | ||
11 | #include <sys/sysctl.h> | |
12 | ||
13 | #include "darwintest_defaults.h" | |
14 | #include <darwintest_multiprocess.h> | |
15 | ||
16 | // <rdar://problem/38810583> this test case is intended to test for the | |
17 | // specific issue found in this radar. That is, if: | |
18 | // | |
19 | // 1. A mutex is in first-fit policy mode, and | |
20 | // 2. is used as the mutex in a pthread_cond_wait (or timedwait), and | |
21 | // 3. the mutex has the K-bit set but has no kernel waiters, and | |
22 | // 4. the cvwait call preposts an unlock to the mutex | |
23 | // | |
24 | // Under these conditions, the fact that the cvwait preposted an unlock to | |
25 | // the paired mutex is lost during the call. The P-bit was never returned to | |
26 | // userspace and the kwq in the kernel would continue to exist. If the same | |
27 | // uaddr is then reused as another synchroniser type then we would often | |
28 | // return EINVAL from the wait/lock function. | |
29 | // | |
30 | // So this test is attempting to: | |
31 | // | |
32 | // 1. Repeatedly bang on a mutex+cvar for a number of iterations in the | |
33 | // hope of triggering a cvwait prepost situation. | |
34 | // 2. Then destroy both the mutex and cvar, and reinitialise each memory | |
35 | // location as the opposite type of synchroniser. Then cvwait once to | |
36 | // trigger the failure condition. | |
37 | ||
38 | struct context { | |
39 | union { | |
40 | pthread_mutex_t mutex; | |
41 | pthread_cond_t cond; | |
42 | }; | |
43 | union { | |
44 | pthread_mutex_t mutex2; | |
45 | pthread_cond_t cond2; | |
46 | }; | |
47 | long value; | |
48 | long count; | |
49 | long waiter; | |
50 | }; | |
51 | ||
52 | static void *test_cond(void *ptr) { | |
53 | struct context *context = ptr; | |
54 | int res; | |
55 | ||
56 | res = pthread_cond_wait(&context->cond, &context->mutex2); | |
57 | T_ASSERT_POSIX_ZERO(res, "condition wait on condvar completed"); | |
58 | res = pthread_mutex_unlock(&context->mutex2); | |
59 | T_ASSERT_POSIX_ZERO(res, "unlock condvar mutex"); | |
60 | return NULL; | |
61 | } | |
62 | ||
63 | static void *test_cond_wake(void *ptr) { | |
64 | struct context *context = ptr; | |
65 | int res; | |
66 | ||
67 | res = pthread_mutex_lock(&context->mutex2); | |
68 | T_ASSERT_POSIX_ZERO(res, "locked condvar mutex"); | |
69 | res = pthread_cond_signal(&context->cond); | |
70 | T_ASSERT_POSIX_ZERO(res, "condvar signalled"); | |
71 | res = pthread_mutex_unlock(&context->mutex2); | |
72 | T_ASSERT_POSIX_ZERO(res, "dropped condvar mutex"); | |
73 | ||
74 | return NULL; | |
75 | } | |
76 | ||
77 | static void *test_thread(void *ptr) { | |
78 | int res; | |
79 | long old; | |
80 | struct context *context = ptr; | |
81 | ||
82 | int i = 0; | |
83 | char *str; | |
84 | ||
85 | do { | |
86 | bool try = i++ & 1; | |
87 | bool cond = i & 16; | |
88 | ||
89 | if (!try){ | |
90 | str = "pthread_mutex_lock"; | |
91 | res = pthread_mutex_lock(&context->mutex); | |
92 | } else { | |
93 | str = "pthread_mutex_trylock"; | |
94 | res = pthread_mutex_trylock(&context->mutex); | |
95 | } | |
96 | if (res != 0) { | |
97 | if (try && res == EBUSY) { | |
98 | continue; | |
99 | } | |
100 | T_ASSERT_POSIX_ZERO(res, "[%ld] %s", context->count, str); | |
101 | } | |
102 | ||
103 | old = __sync_fetch_and_or(&context->value, 1); | |
104 | if ((old & 1) != 0) { | |
105 | T_FAIL("[%ld] OR %lx\n", context->count, old); | |
106 | } | |
107 | ||
108 | old = __sync_fetch_and_and(&context->value, 0); | |
109 | if ((old & 1) == 0) { | |
110 | T_FAIL("[%ld] AND %lx\n", context->count, old); | |
111 | } | |
112 | ||
113 | if (cond && !context->waiter) { | |
114 | context->waiter = 1; | |
115 | struct timespec ts = { | |
116 | .tv_sec = 0, | |
117 | .tv_nsec = 10ull * NSEC_PER_MSEC, | |
118 | }; | |
119 | ||
120 | res = pthread_cond_timedwait_relative_np(&context->cond2, &context->mutex, &ts); | |
121 | if (res == ETIMEDOUT) { | |
122 | // ignore, should be the last thread out | |
123 | } else if (res) { | |
124 | T_ASSERT_POSIX_ZERO(res, "[%ld] pthread_cond_wait", | |
125 | context->count); | |
126 | } | |
127 | context->waiter = 0; | |
128 | res = pthread_mutex_unlock(&context->mutex); | |
129 | if (res) { | |
130 | T_ASSERT_POSIX_ZERO(res, "[%ld] pthread_mutex_unlock", | |
131 | context->count); | |
132 | } | |
133 | } else { | |
134 | if (context->waiter) { | |
135 | res = pthread_cond_broadcast(&context->cond2); | |
136 | if (res) { | |
137 | T_ASSERT_POSIX_ZERO(res, "[%ld] pthread_cond_broadcast", | |
138 | context->count); | |
139 | } | |
140 | } | |
141 | res = pthread_mutex_unlock(&context->mutex); | |
142 | if (res) { | |
143 | T_ASSERT_POSIX_ZERO(res, "[%ld] pthread_mutex_unlock", | |
144 | context->count); | |
145 | } | |
146 | } | |
147 | } while (__sync_fetch_and_sub(&context->count, 1) > 0); | |
148 | return NULL; | |
149 | } | |
150 | ||
151 | ||
152 | static void | |
153 | _test_condvar_prepost_race(void) | |
154 | { | |
155 | struct context context = { | |
156 | .mutex = PTHREAD_MUTEX_INITIALIZER, | |
157 | .cond2 = PTHREAD_COND_INITIALIZER, | |
158 | .value = 0, | |
159 | .count = 10000, | |
160 | .waiter = false, | |
161 | }; | |
162 | int i; | |
163 | int res; | |
164 | int threads = 8; | |
165 | pthread_t p[threads]; | |
166 | for (i = 0; i < threads; ++i) { | |
167 | res = pthread_create(&p[i], NULL, test_thread, &context); | |
168 | T_QUIET; T_ASSERT_POSIX_ZERO(res, "pthread_create()"); | |
169 | } | |
170 | for (i = 0; i < threads; ++i) { | |
171 | res = pthread_join(p[i], NULL); | |
172 | T_QUIET; T_ASSERT_POSIX_ZERO(res, "pthread_join()"); | |
173 | } | |
174 | ||
175 | T_PASS("initial pthread mutex storm completed"); | |
176 | ||
177 | pthread_mutex_destroy(&context.mutex); | |
178 | pthread_cond_destroy(&context.cond2); | |
179 | ||
180 | pthread_mutex_init(&context.mutex2, NULL); | |
181 | pthread_cond_init(&context.cond, NULL); | |
182 | res = pthread_mutex_lock(&context.mutex2); | |
183 | T_ASSERT_POSIX_ZERO(res, "mutex lock for condition wait"); | |
184 | res = pthread_create(&p[0], NULL, test_cond, &context); | |
185 | T_QUIET; T_ASSERT_POSIX_ZERO(res, "pthread_create()"); | |
186 | res = pthread_create(&p[1], NULL, test_cond_wake, &context); | |
187 | T_QUIET; T_ASSERT_POSIX_ZERO(res, "pthread_create()"); | |
188 | ||
189 | res = pthread_join(p[0], NULL); | |
190 | T_QUIET; T_ASSERT_POSIX_ZERO(res, "pthread_join()"); | |
191 | res = pthread_join(p[1], NULL); | |
192 | T_QUIET; T_ASSERT_POSIX_ZERO(res, "pthread_join()"); | |
193 | ||
194 | pthread_cond_destroy(&context.cond); | |
195 | } | |
196 | ||
197 | T_DECL(cond_prepost_fairshare, "cond_prepost_fairshare (fairshare)", | |
198 | T_META_ALL_VALID_ARCHS(YES), | |
199 | T_META_ENVVAR("PTHREAD_MUTEX_DEFAULT_POLICY=1")) | |
200 | { | |
201 | int i; | |
202 | int count = 100; | |
203 | for (i=0; i < count; i++) { | |
204 | _test_condvar_prepost_race(); | |
205 | } | |
206 | } | |
207 | ||
208 | T_DECL(cond_prepost_firstfit, "cond_prepost_firstfit (firstfit)", | |
209 | T_META_ALL_VALID_ARCHS(YES), | |
210 | T_META_ENVVAR("PTHREAD_MUTEX_DEFAULT_POLICY=3")) | |
211 | { | |
212 | int i; | |
213 | int count = 100; | |
214 | for (i=0; i < count; i++) { | |
215 | _test_condvar_prepost_race(); | |
216 | } | |
217 | } |