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 | |
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(0 == uv_sem_init(&wc->sem_waiting, 0)); |
59 | ASSERT(0 == uv_sem_init(&wc->sem_signaled, 0)); |
60 | ASSERT(0 == uv_cond_init(&wc->cond)); |
61 | ASSERT(0 == uv_mutex_init(&wc->mutex)); |
62 | } |
63 | |
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. */ |
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. */ |
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(*flag == 0); |
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. */ |
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(*flag == 1); |
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. */ |
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(0 == uv_thread_create(&thread, worker, &wc)); |
134 | |
135 | /* We wait-then-signal. */ |
136 | ASSERT(0 == wc.wait_cond(&wc, &wc.posted_1)); |
137 | wc.signal_cond(&wc, &wc.posted_2); |
138 | |
139 | ASSERT(0 == 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. */ |
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(0 == uv_thread_create(&thread, worker, &wc)); |
153 | |
154 | /* We wait-then-signal. */ |
155 | ASSERT(0 == wc.wait_cond(&wc, &wc.posted_1)); |
156 | wc.signal_cond(&wc, &wc.posted_2); |
157 | |
158 | ASSERT(0 == 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. */ |
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(r == 0); /* Should not time out. */ |
180 | } while (*flag == 0); |
181 | ASSERT(*flag == 1); |
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. */ |
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(0 == 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(0 == 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. */ |
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(0 == 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(0 == 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. */ |
229 | TEST_IMPL(condvar_5) { |
230 | worker_config wc; |
231 | int r; |
232 | /* ns */ |
233 | uint64_t before; |
234 | uint64_t after; |
235 | uint64_t elapsed; |
236 | uint64_t timeout; |
237 | |
238 | timeout = 100 * 1000 * 1000; /* 100 ms in ns */ |
239 | |
240 | /* Mostly irrelevant. We need cond and mutex initialized. */ |
241 | worker_config_init(&wc, 0, NULL, NULL); |
242 | |
243 | uv_mutex_lock(&wc.mutex); |
244 | |
245 | /* We wait. |
246 | * No signaler, so this will only return if timeout is delivered. */ |
247 | before = uv_hrtime(); |
248 | r = uv_cond_timedwait(&wc.cond, &wc.mutex, timeout); |
249 | after = uv_hrtime(); |
250 | |
251 | uv_mutex_unlock(&wc.mutex); |
252 | |
253 | /* It timed out. */ |
254 | ASSERT(r == UV_ETIMEDOUT); |
255 | |
256 | /* It must have taken at least timeout, modulo system timer ticks. |
257 | * But it should not take too much longer. |
258 | * cf. MSDN docs: |
259 | * https://msdn.microsoft.com/en-us/library/ms687069(VS.85).aspx */ |
260 | elapsed = after - before; |
261 | ASSERT(0.75 * timeout <= elapsed); /* 1.0 too large for Windows. */ |
262 | ASSERT(elapsed <= 5.0 * timeout); /* MacOS has reported failures up to 1.75. */ |
263 | |
264 | worker_config_destroy(&wc); |
265 | |
266 | return 0; |
267 | } |
268 | |