xref: /libuv/test/test-condvar.c (revision 011a1ac1)
1 /* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
2  *
3  * Permission is hereby granted, free of charge, to any person obtaining a copy
4  * of this software and associated documentation files (the "Software"), to
5  * deal in the Software without restriction, including without limitation the
6  * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7  * sell copies of the Software, and to permit persons to whom the Software is
8  * furnished to do so, subject to the following conditions:
9  *
10  * The above copyright notice and this permission notice shall be included in
11  * all copies or substantial portions of the Software.
12  *
13  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
19  * IN THE SOFTWARE.
20  */
21 
22 #include "uv.h"
23 #include "task.h"
24 
25 #include <string.h>
26 #include <errno.h>
27 
28 struct worker_config;
29 
30 typedef void (*signal_func)(struct worker_config* c, int* flag);
31 typedef int (*wait_func)(struct worker_config* c, const int* flag);
32 
33 typedef struct worker_config {
34   uv_sem_t sem_waiting; /* post before waiting. */
35   uv_sem_t sem_signaled; /* post after signaling. */
36   uv_mutex_t mutex;
37   uv_cond_t cond;
38   int use_broadcast;
39   int posted_1;
40   int posted_2;
41   signal_func signal_cond;
42   wait_func wait_cond;
43 } worker_config;
44 
worker_config_init(worker_config * wc,int use_broadcast,signal_func signal_f,wait_func wait_f)45 void worker_config_init(worker_config* wc,
46                         int use_broadcast,
47                         signal_func signal_f,
48                         wait_func wait_f) {
49   /* Wipe. */
50   memset(wc, 0, sizeof(*wc));
51 
52   /* Copy vars. */
53   wc->signal_cond = signal_f;
54   wc->wait_cond = wait_f;
55   wc->use_broadcast = use_broadcast;
56 
57   /* Init. */
58   ASSERT_OK(uv_sem_init(&wc->sem_waiting, 0));
59   ASSERT_OK(uv_sem_init(&wc->sem_signaled, 0));
60   ASSERT_OK(uv_cond_init(&wc->cond));
61   ASSERT_OK(uv_mutex_init(&wc->mutex));
62 }
63 
worker_config_destroy(worker_config * wc)64 void worker_config_destroy(worker_config* wc) {
65   uv_mutex_destroy(&wc->mutex);
66   uv_cond_destroy(&wc->cond);
67   uv_sem_destroy(&wc->sem_signaled);
68   uv_sem_destroy(&wc->sem_waiting);
69 }
70 
71 /* arg is a worker_config.
72  * Call signal_cond then wait_cond.
73  * Partner should call wait then signal. */
worker(void * arg)74 static void worker(void* arg) {
75   worker_config* c = arg;
76   c->signal_cond(c, &c->posted_1);
77   c->wait_cond(c, &c->posted_2);
78 }
79 
80 /* 1. Signal a waiting waiter.
81  * 2. Tell waiter we finished. */
condvar_signal(worker_config * c,int * flag)82 static void condvar_signal(worker_config* c, int* flag) {
83   /* Wait until waiter holds mutex and is preparing to wait. */
84   uv_sem_wait(&c->sem_waiting);
85 
86   /* Make sure waiter has begun waiting. */
87   uv_mutex_lock(&c->mutex);
88 
89   /* Help waiter differentiate between spurious and legitimate wakeup. */
90   ASSERT_OK(*flag);
91   *flag = 1;
92 
93   if (c->use_broadcast)
94     uv_cond_broadcast(&c->cond);
95   else
96     uv_cond_signal(&c->cond);
97 
98   uv_mutex_unlock(&c->mutex);
99 
100   /* Done signaling. */
101   uv_sem_post(&c->sem_signaled);
102 }
103 
104 /* 1. Wait on a signal.
105  * 2. Ensure that the signaler finished. */
condvar_wait(worker_config * c,const int * flag)106 static int condvar_wait(worker_config* c, const int* flag) {
107   uv_mutex_lock(&c->mutex);
108 
109   /* Tell signal'er that I am waiting. */
110   uv_sem_post(&c->sem_waiting);
111 
112   /* Wait until I get a non-spurious signal. */
113   do {
114     uv_cond_wait(&c->cond, &c->mutex);
115   } while (*flag == 0);
116   ASSERT_EQ(1, *flag);
117 
118   uv_mutex_unlock(&c->mutex);
119 
120   /* Wait for my signal'er to finish. */
121   uv_sem_wait(&c->sem_signaled);
122 
123   return 0;
124 }
125 
126 /* uv_cond_wait: One thread signals, the other waits. */
TEST_IMPL(condvar_1)127 TEST_IMPL(condvar_1) {
128   worker_config wc;
129   uv_thread_t thread;
130 
131   /* Helper signal-then-wait. */
132   worker_config_init(&wc, 0, condvar_signal, condvar_wait);
133   ASSERT_OK(uv_thread_create(&thread, worker, &wc));
134 
135   /* We wait-then-signal. */
136   ASSERT_OK(wc.wait_cond(&wc, &wc.posted_1));
137   wc.signal_cond(&wc, &wc.posted_2);
138 
139   ASSERT_OK(uv_thread_join(&thread));
140   worker_config_destroy(&wc);
141 
142   return 0;
143 }
144 
145 /* uv_cond_wait: One thread broadcasts, the other waits. */
TEST_IMPL(condvar_2)146 TEST_IMPL(condvar_2) {
147   worker_config wc;
148   uv_thread_t thread;
149 
150   /* Helper to signal-then-wait. */
151   worker_config_init(&wc, 1, condvar_signal, condvar_wait);
152   ASSERT_OK(uv_thread_create(&thread, worker, &wc));
153 
154   /* We wait-then-signal. */
155   ASSERT_OK(wc.wait_cond(&wc, &wc.posted_1));
156   wc.signal_cond(&wc, &wc.posted_2);
157 
158   ASSERT_OK(uv_thread_join(&thread));
159   worker_config_destroy(&wc);
160 
161   return 0;
162 }
163 
164 /* 1. Wait on a signal (hopefully not timeout, else we'll hang).
165  * 2. Ensure that the signaler finished. */
condvar_timedwait(worker_config * c,const int * flag)166 static int condvar_timedwait(worker_config* c, const int* flag) {
167   int r;
168 
169   r = 0;
170 
171   uv_mutex_lock(&c->mutex);
172 
173   /* Tell signal'er that I am waiting. */
174   uv_sem_post(&c->sem_waiting);
175 
176   /* Wait until I get a non-spurious signal. */
177   do {
178     r = uv_cond_timedwait(&c->cond, &c->mutex, (uint64_t)(1 * 1e9)); /* 1 s */
179     ASSERT_OK(r); /* Should not time out. */
180   } while (*flag == 0);
181   ASSERT_EQ(1, *flag);
182 
183   uv_mutex_unlock(&c->mutex);
184 
185   /* Wait for my signal'er to finish. */
186   uv_sem_wait(&c->sem_signaled);
187   return r;
188 }
189 
190 /* uv_cond_timedwait: One thread signals, the other timedwaits. */
TEST_IMPL(condvar_3)191 TEST_IMPL(condvar_3) {
192   worker_config wc;
193   uv_thread_t thread;
194 
195   /* Helper to signal-then-wait. */
196   worker_config_init(&wc, 0, condvar_signal, condvar_timedwait);
197   ASSERT_OK(uv_thread_create(&thread, worker, &wc));
198 
199   /* We wait-then-signal. */
200   wc.wait_cond(&wc, &wc.posted_1);
201   wc.signal_cond(&wc, &wc.posted_2);
202 
203   ASSERT_OK(uv_thread_join(&thread));
204   worker_config_destroy(&wc);
205 
206   return 0;
207 }
208 
209 /* uv_cond_timedwait: One thread broadcasts, the other waits. */
TEST_IMPL(condvar_4)210 TEST_IMPL(condvar_4) {
211   worker_config wc;
212   uv_thread_t thread;
213 
214   /* Helper to signal-then-wait. */
215   worker_config_init(&wc, 1, condvar_signal, condvar_timedwait);
216   ASSERT_OK(uv_thread_create(&thread, worker, &wc));
217 
218   /* We wait-then-signal. */
219   wc.wait_cond(&wc, &wc.posted_1);
220   wc.signal_cond(&wc, &wc.posted_2);
221 
222   ASSERT_OK(uv_thread_join(&thread));
223   worker_config_destroy(&wc);
224 
225   return 0;
226 }
227 
228 /* uv_cond_timedwait: One thread waits, no signal. Timeout should be delivered. */
TEST_IMPL(condvar_5)229 TEST_IMPL(condvar_5) {
230   worker_config wc;
231   uint64_t timeout;
232 
233   timeout = 100 * 1000 * 1000; /* 100 ms in ns */
234 
235   /* Mostly irrelevant. We need cond and mutex initialized. */
236   worker_config_init(&wc, 0, NULL, NULL);
237 
238   uv_mutex_lock(&wc.mutex);
239 
240   /* We wait. No signaler, so this will only return if timeout is delivered. */
241   ASSERT_EQ(UV_ETIMEDOUT, uv_cond_timedwait(&wc.cond, &wc.mutex, timeout));
242 
243   uv_mutex_unlock(&wc.mutex);
244 
245   worker_config_destroy(&wc);
246 
247   return 0;
248 }
249