]> git.saurik.com Git - apple/libpthread.git/blob - tests/cond_timed.c
libpthread-301.20.1.tar.gz
[apple/libpthread.git] / tests / cond_timed.c
1 #include <assert.h>
2 #include <pthread.h>
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <string.h>
6 #include <unistd.h>
7 #include <stdbool.h>
8 #include <errno.h>
9 #include <sys/time.h>
10 #include <libkern/OSAtomic.h>
11 #include <dispatch/dispatch.h>
12
13 #include "darwintest_defaults.h"
14
15 #define NUM_THREADS 8
16
17 struct context {
18 pthread_cond_t cond;
19 pthread_mutex_t mutex;
20 long udelay;
21 long count;
22 };
23
24 static void *wait_thread(void *ptr) {
25 int res;
26 struct context *context = ptr;
27
28 bool loop = true;
29 while (loop) {
30 struct timespec ts;
31 struct timeval tv;
32 gettimeofday(&tv, NULL);
33 tv.tv_sec += (tv.tv_usec + context->udelay) / (__typeof(tv.tv_sec)) USEC_PER_SEC;
34 tv.tv_usec = (tv.tv_usec + context->udelay) % (__typeof(tv.tv_usec)) USEC_PER_SEC;
35 TIMEVAL_TO_TIMESPEC(&tv, &ts);
36
37 res = pthread_mutex_lock(&context->mutex);
38 if (res) {
39 fprintf(stderr, "[%ld] pthread_mutex_lock: %s\n", context->count, strerror(res));
40 abort();
41 }
42
43 if (context->count > 0) {
44 res = pthread_cond_timedwait(&context->cond, &context->mutex, &ts);
45 if (res != ETIMEDOUT) {
46 fprintf(stderr, "[%ld] pthread_cond_timedwait: %s\n", context->count, strerror(res));
47 abort();
48 }
49 --context->count;
50 } else {
51 loop = false;
52 }
53
54 res = pthread_mutex_unlock(&context->mutex);
55 if (res) {
56 fprintf(stderr, "[%ld] pthread_mutex_unlock: %s\n", context->count, strerror(res));
57 abort();
58 }
59 }
60
61 return NULL;
62 }
63
64 T_DECL(cond_timedwait_timeout, "pthread_cond_timedwait() timeout")
65 {
66 // This testcase launches 8 threads that all perform timed wait on the same
67 // conditional variable that is not being signaled in a loop. Ater the total
68 // of 8000 timeouts all threads finish and the testcase prints out the
69 // expected time (5[ms]*8000[timeouts]/8[threads]=5s) vs elapsed time.
70 struct context context = {
71 .cond = PTHREAD_COND_INITIALIZER,
72 .mutex = PTHREAD_MUTEX_INITIALIZER,
73 .udelay = 5000,
74 .count = 8000,
75 };
76
77 long uexpected = (context.udelay * context.count) / NUM_THREADS;
78 T_LOG("waittime expected: %ld us", uexpected);
79 struct timeval start, end;
80 gettimeofday(&start, NULL);
81
82 pthread_t p[NUM_THREADS];
83 for (int i = 0; i < NUM_THREADS; ++i) {
84 T_ASSERT_POSIX_ZERO(pthread_create(&p[i], NULL, wait_thread, &context),
85 "pthread_create");
86 }
87
88 usleep((useconds_t) uexpected);
89 bool loop = true;
90 while (loop) {
91 T_QUIET; T_ASSERT_POSIX_ZERO(pthread_mutex_lock(&context.mutex),
92 "pthread_mutex_lock");
93 if (context.count <= 0) {
94 loop = false;
95 }
96 T_QUIET; T_ASSERT_POSIX_ZERO(pthread_mutex_unlock(&context.mutex),
97 "pthread_mutex_unlock");
98 }
99
100 for (int i = 0; i < NUM_THREADS; ++i) {
101 T_ASSERT_POSIX_ZERO(pthread_join(p[i], NULL), "pthread_join");
102 }
103
104 gettimeofday(&end, NULL);
105 uint64_t uelapsed =
106 ((uint64_t) end.tv_sec * USEC_PER_SEC + (uint64_t) end.tv_usec) -
107 ((uint64_t) start.tv_sec * USEC_PER_SEC + (uint64_t) start.tv_usec);
108 T_LOG("waittime actual: %llu us", uelapsed);
109 }
110
111 struct prodcons_context {
112 pthread_cond_t cond;
113 pthread_mutex_t mutex;
114 bool consumer_ready;
115 bool workitem_available;
116 bool padding[6];
117 };
118
119 static void *consumer_thread(void *ptr) {
120 struct prodcons_context *context = ptr;
121
122 // tell producer thread that we are ready
123 T_ASSERT_POSIX_ZERO(pthread_mutex_lock(&context->mutex), "pthread_mutex_lock");
124
125 context->consumer_ready = true;
126 T_ASSERT_POSIX_ZERO(pthread_cond_signal(&context->cond), "pthread_cond_signal");
127
128 // wait for a work item to become available
129 do {
130 // mutex will be dropped and allow producer thread to acquire
131 T_ASSERT_POSIX_ZERO(pthread_cond_wait(&context->cond, &context->mutex), "pthread_cond_wait");
132
133 // loop in case of spurious wakeups
134 } while (context->workitem_available == false);
135
136 // work item has been sent, so dequeue it and tell producer
137 context->workitem_available = false;
138 T_ASSERT_POSIX_ZERO(pthread_cond_signal(&context->cond), "pthread_cond_signal");
139
140 // unlock mutex, we are done here
141 T_ASSERT_POSIX_ZERO(pthread_mutex_unlock(&context->mutex), "pthread_mutex_unlock");
142
143 T_PASS("Consumer thread exiting");
144
145 return NULL;
146 }
147
148 #define TESTCASE_TIMEOUT (10) /* seconds */
149 typedef enum {
150 eNullTimeout,
151 eZeroTimeout,
152 eBeforeEpochTimeout,
153 eRecentPastTimeout
154 } TimeOutType;
155
156 static DT_TEST_RETURN cond_timedwait_timeouts_internal(TimeOutType timeout, bool relative);
157
158 T_DECL(cond_timedwait_nulltimeout, "pthread_cond_timedwait() with NULL timeout, ensure mutex is unlocked")
159 {
160 cond_timedwait_timeouts_internal(eNullTimeout, false);
161 }
162
163 T_DECL(cond_timedwait_zerotimeout, "pthread_cond_timedwait() with zero timeout, ensure mutex is unlocked")
164 {
165 cond_timedwait_timeouts_internal(eZeroTimeout, false);
166 }
167
168 T_DECL(cond_timedwait_beforeepochtimeout, "pthread_cond_timedwait() with timeout before the epoch, ensure mutex is unlocked")
169 {
170 cond_timedwait_timeouts_internal(eBeforeEpochTimeout, false);
171 }
172
173 T_DECL(cond_timedwait_pasttimeout, "pthread_cond_timedwait() with timeout in the past, ensure mutex is unlocked")
174 {
175 cond_timedwait_timeouts_internal(eRecentPastTimeout, false);
176 }
177
178 T_DECL(cond_timedwait_relative_nulltimeout, "pthread_cond_timedwait_relative_np() with relative NULL timeout, ensure mutex is unlocked")
179 {
180 cond_timedwait_timeouts_internal(eNullTimeout, true);
181 }
182
183 T_DECL(cond_timedwait_relative_pasttimeout, "pthread_cond_timedwait_relative_np() with relative timeout in the past, ensure mutex is unlocked")
184 {
185 cond_timedwait_timeouts_internal(eRecentPastTimeout, true);
186 }
187
188 static DT_TEST_RETURN cond_timedwait_timeouts_internal(TimeOutType timeout, bool relative)
189 {
190 // This testcase mimics a producer-consumer model where the consumer checks
191 // in and waits until work becomes available. The producer then waits until
192 // the work has been consumed and the consumer quiesces. Since condition
193 // variables may have spurious wakeups, the timeout should not matter,
194 // but there have been functional issues where the mutex would not be unlocked
195 // for a timeout in the past.
196 struct prodcons_context context = {
197 .cond = PTHREAD_COND_INITIALIZER,
198 .mutex = PTHREAD_MUTEX_INITIALIZER,
199 .consumer_ready = false,
200 .workitem_available = false
201 };
202
203 struct timeval test_timeout;
204 gettimeofday(&test_timeout, NULL);
205 test_timeout.tv_sec += TESTCASE_TIMEOUT;
206
207 T_ASSERT_POSIX_ZERO(pthread_mutex_lock(&context.mutex), "pthread_mutex_lock");
208
209 pthread_t p;
210 T_ASSERT_POSIX_ZERO(pthread_create(&p, NULL, consumer_thread, &context),
211 "pthread_create");
212
213 // Wait until consumer thread is able to acquire the mutex, check in, and block
214 // in its own condition variable. We do not want to start generating work before
215 // the consumer thread is available
216 do {
217 // mutex will be dropped and allow consumer thread to acquire
218 T_ASSERT_POSIX_ZERO(pthread_cond_wait(&context.cond, &context.mutex), "pthread_cond_wait");
219
220 // loop in case of spurious wakeups
221 } while (context.consumer_ready == false);
222
223 // consumer is ready and blocked in its own condition variable, and
224 // producer has mutex acquired. Send a work item and wait for it
225 // to be dequeued
226
227 context.workitem_available = true;
228 T_ASSERT_POSIX_ZERO(pthread_cond_signal(&context.cond), "pthread_cond_signal");
229
230 do {
231 struct timeval now;
232
233 gettimeofday(&now, NULL);
234 T_QUIET; T_ASSERT_TRUE(timercmp(&now, &test_timeout, <), "timeout reached waiting for consumer thread to consume");
235
236 struct timespec ts;
237
238 if (relative) {
239 switch (timeout) {
240 case eNullTimeout:
241 break;
242 case eRecentPastTimeout:
243 ts.tv_sec = -1;
244 ts.tv_nsec = 0;
245 break;
246 case eZeroTimeout:
247 case eBeforeEpochTimeout:
248 break;
249 }
250 } else {
251 switch (timeout) {
252 case eNullTimeout:
253 break;
254 case eZeroTimeout:
255 ts.tv_sec = 0;
256 ts.tv_nsec = 0;
257 break;
258 case eBeforeEpochTimeout:
259 ts.tv_sec = -1;
260 ts.tv_nsec = 0;
261 break;
262 case eRecentPastTimeout:
263 ts.tv_sec = now.tv_sec - 1;
264 ts.tv_nsec = now.tv_usec / 1000;
265 break;
266 }
267 }
268
269 int ret;
270 if (relative) {
271 ret = pthread_cond_timedwait_relative_np(&context.cond, &context.mutex, timeout == eNullTimeout ? NULL : &ts);
272 } else {
273 ret = pthread_cond_timedwait(&context.cond, &context.mutex, timeout == eNullTimeout ? NULL : &ts);
274 }
275 if (ret != 0 && ret != EINTR && ret != ETIMEDOUT) T_ASSERT_POSIX_ZERO(ret, "timedwait returned error");
276
277 usleep(10*1000); // avoid spinning in a CPU-bound loop
278
279 // loop in case of spurious wakeups
280 } while (context.workitem_available == true);
281
282 T_ASSERT_POSIX_ZERO(pthread_mutex_unlock(&context.mutex), "pthread_mutex_unlock");
283
284 T_ASSERT_POSIX_ZERO(pthread_join(p, NULL), "pthread_join");
285
286 T_PASS("Consumer completed work");
287 }