/src/openssl36/ssl/quic/quic_reactor.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright 2022-2026 The OpenSSL Project Authors. All Rights Reserved. |
3 | | * |
4 | | * Licensed under the Apache License 2.0 (the "License"). You may not use |
5 | | * this file except in compliance with the License. You can obtain a copy |
6 | | * in the file LICENSE in the source distribution or at |
7 | | * https://www.openssl.org/source/license.html |
8 | | */ |
9 | | #include "internal/quic_reactor.h" |
10 | | #include "internal/common.h" |
11 | | #include "internal/thread_arch.h" |
12 | | #include <assert.h> |
13 | | |
14 | | #if defined(OPENSSL_SYS_WINDOWS) |
15 | | #include <winsock2.h> |
16 | | #include <mstcpip.h> |
17 | | #include <mswsock.h> |
18 | | #endif |
19 | | |
20 | | /* |
21 | | * Core I/O Reactor Framework |
22 | | * ========================== |
23 | | */ |
24 | | static void rtor_notify_other_threads(QUIC_REACTOR *rtor); |
25 | | |
26 | | int ossl_quic_reactor_init(QUIC_REACTOR *rtor, |
27 | | void (*tick_cb)(QUIC_TICK_RESULT *res, void *arg, |
28 | | uint32_t flags), |
29 | | void *tick_cb_arg, |
30 | | CRYPTO_MUTEX *mutex, |
31 | | OSSL_TIME initial_tick_deadline, |
32 | | uint64_t flags) |
33 | 30.0k | { |
34 | 30.0k | rtor->poll_r.type = BIO_POLL_DESCRIPTOR_TYPE_NONE; |
35 | 30.0k | rtor->poll_w.type = BIO_POLL_DESCRIPTOR_TYPE_NONE; |
36 | 30.0k | rtor->net_read_desired = 0; |
37 | 30.0k | rtor->net_write_desired = 0; |
38 | 30.0k | rtor->can_poll_r = 0; |
39 | 30.0k | rtor->can_poll_w = 0; |
40 | 30.0k | rtor->tick_deadline = initial_tick_deadline; |
41 | | |
42 | 30.0k | rtor->tick_cb = tick_cb; |
43 | 30.0k | rtor->tick_cb_arg = tick_cb_arg; |
44 | 30.0k | rtor->mutex = mutex; |
45 | | |
46 | 30.0k | rtor->cur_blocking_waiters = 0; |
47 | | |
48 | 30.0k | if ((flags & QUIC_REACTOR_FLAG_USE_NOTIFIER) != 0) { |
49 | 0 | if (!ossl_rio_notifier_init(&rtor->notifier)) |
50 | 0 | return 0; |
51 | | |
52 | 0 | if ((rtor->notifier_cv = ossl_crypto_condvar_new()) == NULL) { |
53 | 0 | ossl_rio_notifier_cleanup(&rtor->notifier); |
54 | 0 | return 0; |
55 | 0 | } |
56 | | |
57 | 0 | rtor->have_notifier = 1; |
58 | 30.0k | } else { |
59 | 30.0k | rtor->have_notifier = 0; |
60 | 30.0k | } |
61 | | |
62 | 30.0k | return 1; |
63 | 30.0k | } |
64 | | |
65 | | void ossl_quic_reactor_cleanup(QUIC_REACTOR *rtor) |
66 | 30.0k | { |
67 | 30.0k | if (rtor == NULL) |
68 | 0 | return; |
69 | | |
70 | 30.0k | if (rtor->have_notifier) { |
71 | 0 | ossl_rio_notifier_cleanup(&rtor->notifier); |
72 | 0 | rtor->have_notifier = 0; |
73 | |
|
74 | 0 | ossl_crypto_condvar_free(&rtor->notifier_cv); |
75 | 0 | } |
76 | 30.0k | } |
77 | | |
78 | | #if defined(OPENSSL_SYS_WINDOWS) |
79 | | |
80 | | /* Work around for MinGW builds. */ |
81 | | #if defined(__MINGW32__) && !defined(SIO_UDP_NETRESET) |
82 | | #define SIO_UDP_NETRESET _WSAIOW(IOC_VENDOR, 15) |
83 | | #endif |
84 | | |
85 | | /* |
86 | | * On Windows recvfrom() may return WSAECONNRESET when destination port |
87 | | * used in preceding call to sendto() is no longer reachable. The reset |
88 | | * error received from UDP socket takes the whole port down. This behavior |
89 | | * must be suppressed for QUIC protocol so QUIC applications may rely on |
90 | | * QUIC protocol itself to detect network failures. |
91 | | */ |
92 | | static void rtor_configure_winsock(BIO_POLL_DESCRIPTOR *bpd) |
93 | | { |
94 | | BOOL bNewBehavior = FALSE; |
95 | | DWORD dwBytesReturned = 0; |
96 | | |
97 | | if (bpd->type == BIO_POLL_DESCRIPTOR_TYPE_SOCK_FD) { |
98 | | WSAIoctl(bpd->value.fd, SIO_UDP_CONNRESET, &bNewBehavior, |
99 | | sizeof(bNewBehavior), NULL, 0, &dwBytesReturned, NULL, NULL); |
100 | | WSAIoctl(bpd->value.fd, SIO_UDP_NETRESET, &bNewBehavior, |
101 | | sizeof(bNewBehavior), NULL, 0, &dwBytesReturned, NULL, NULL); |
102 | | } |
103 | | } |
104 | | #endif |
105 | | |
106 | | void ossl_quic_reactor_set_poll_r(QUIC_REACTOR *rtor, const BIO_POLL_DESCRIPTOR *r) |
107 | 40.0M | { |
108 | 40.0M | if (r == NULL) |
109 | 0 | rtor->poll_r.type = BIO_POLL_DESCRIPTOR_TYPE_NONE; |
110 | 40.0M | else |
111 | 40.0M | rtor->poll_r = *r; |
112 | | |
113 | | #if defined(OPENSSL_SYS_WINDOWS) |
114 | | rtor_configure_winsock(&rtor->poll_r); |
115 | | #endif |
116 | | |
117 | 40.0M | rtor->can_poll_r |
118 | 40.0M | = ossl_quic_reactor_can_support_poll_descriptor(rtor, &rtor->poll_r); |
119 | 40.0M | } |
120 | | |
121 | | void ossl_quic_reactor_set_poll_w(QUIC_REACTOR *rtor, const BIO_POLL_DESCRIPTOR *w) |
122 | 40.0M | { |
123 | 40.0M | if (w == NULL) |
124 | 0 | rtor->poll_w.type = BIO_POLL_DESCRIPTOR_TYPE_NONE; |
125 | 40.0M | else |
126 | 40.0M | rtor->poll_w = *w; |
127 | | |
128 | | #if defined(OPENSSL_SYS_WINDOWS) |
129 | | rtor_configure_winsock(&rtor->poll_w); |
130 | | #endif |
131 | | |
132 | 40.0M | rtor->can_poll_w |
133 | 40.0M | = ossl_quic_reactor_can_support_poll_descriptor(rtor, &rtor->poll_w); |
134 | 40.0M | } |
135 | | |
136 | | const BIO_POLL_DESCRIPTOR *ossl_quic_reactor_get_poll_r(const QUIC_REACTOR *rtor) |
137 | 0 | { |
138 | 0 | return &rtor->poll_r; |
139 | 0 | } |
140 | | |
141 | | const BIO_POLL_DESCRIPTOR *ossl_quic_reactor_get_poll_w(const QUIC_REACTOR *rtor) |
142 | 0 | { |
143 | 0 | return &rtor->poll_w; |
144 | 0 | } |
145 | | |
146 | | int ossl_quic_reactor_can_support_poll_descriptor(const QUIC_REACTOR *rtor, |
147 | | const BIO_POLL_DESCRIPTOR *d) |
148 | 80.0M | { |
149 | 80.0M | return d->type == BIO_POLL_DESCRIPTOR_TYPE_SOCK_FD; |
150 | 80.0M | } |
151 | | |
152 | | int ossl_quic_reactor_can_poll_r(const QUIC_REACTOR *rtor) |
153 | 73.2M | { |
154 | 73.2M | return rtor->can_poll_r; |
155 | 73.2M | } |
156 | | |
157 | | int ossl_quic_reactor_can_poll_w(const QUIC_REACTOR *rtor) |
158 | 54.6M | { |
159 | 54.6M | return rtor->can_poll_w; |
160 | 54.6M | } |
161 | | |
162 | | int ossl_quic_reactor_net_read_desired(QUIC_REACTOR *rtor) |
163 | 0 | { |
164 | 0 | return rtor->net_read_desired; |
165 | 0 | } |
166 | | |
167 | | int ossl_quic_reactor_net_write_desired(QUIC_REACTOR *rtor) |
168 | 0 | { |
169 | 0 | return rtor->net_write_desired; |
170 | 0 | } |
171 | | |
172 | | OSSL_TIME ossl_quic_reactor_get_tick_deadline(QUIC_REACTOR *rtor) |
173 | 68.6M | { |
174 | 68.6M | return rtor->tick_deadline; |
175 | 68.6M | } |
176 | | |
177 | | int ossl_quic_reactor_tick(QUIC_REACTOR *rtor, uint32_t flags) |
178 | 33.3M | { |
179 | 33.3M | QUIC_TICK_RESULT res = { 0 }; |
180 | | |
181 | | /* |
182 | | * Note that the tick callback cannot fail; this is intentional. Arguably it |
183 | | * does not make that much sense for ticking to 'fail' (in the sense of an |
184 | | * explicit error indicated to the user) because ticking is by its nature |
185 | | * best effort. If something fatal happens with a connection we can report |
186 | | * it on the next actual application I/O call. |
187 | | */ |
188 | 33.3M | rtor->tick_cb(&res, rtor->tick_cb_arg, flags); |
189 | | |
190 | 33.3M | rtor->net_read_desired = res.net_read_desired; |
191 | 33.3M | rtor->net_write_desired = res.net_write_desired; |
192 | 33.3M | rtor->tick_deadline = res.tick_deadline; |
193 | 33.3M | if (res.notify_other_threads) |
194 | 2.09M | rtor_notify_other_threads(rtor); |
195 | | |
196 | 33.3M | return 1; |
197 | 33.3M | } |
198 | | |
199 | | RIO_NOTIFIER *ossl_quic_reactor_get0_notifier(QUIC_REACTOR *rtor) |
200 | 0 | { |
201 | 0 | return rtor->have_notifier ? &rtor->notifier : NULL; |
202 | 0 | } |
203 | | |
204 | | /* |
205 | | * Blocking I/O Adaptation Layer |
206 | | * ============================= |
207 | | */ |
208 | | |
209 | | /* |
210 | | * Utility which can be used to poll on up to two FDs. This is designed to |
211 | | * support use of split FDs (e.g. with SSL_set_rfd and SSL_set_wfd where |
212 | | * different FDs are used for read and write). |
213 | | * |
214 | | * Generally use of poll(2) is preferred where available. Windows, however, |
215 | | * hasn't traditionally offered poll(2), only select(2). WSAPoll() was |
216 | | * introduced in Vista but has seemingly been buggy until relatively recent |
217 | | * versions of Windows 10. Moreover we support XP so this is not a suitable |
218 | | * target anyway. However, the traditional issues with select(2) turn out not to |
219 | | * be an issue on Windows; whereas traditional *NIX select(2) uses a bitmap of |
220 | | * FDs (and thus is limited in the magnitude of the FDs expressible), Windows |
221 | | * select(2) is very different. In Windows, socket handles are not allocated |
222 | | * contiguously from zero and thus this bitmap approach was infeasible. Thus in |
223 | | * adapting the Berkeley sockets API to Windows a different approach was taken |
224 | | * whereby the fd_set contains a fixed length array of socket handles and an |
225 | | * integer indicating how many entries are valid; thus Windows select() |
226 | | * ironically is actually much more like *NIX poll(2) than *NIX select(2). In |
227 | | * any case, this means that the relevant limit for Windows select() is the |
228 | | * number of FDs being polled, not the magnitude of those FDs. Since we only |
229 | | * poll for two FDs here, this limit does not concern us. |
230 | | * |
231 | | * Usage: rfd and wfd may be the same or different. Either or both may also be |
232 | | * -1. If rfd_want_read is 1, rfd is polled for readability, and if |
233 | | * wfd_want_write is 1, wfd is polled for writability. Note that since any |
234 | | * passed FD is always polled for error conditions, setting rfd_want_read=0 and |
235 | | * wfd_want_write=0 is not the same as passing -1 for both FDs. |
236 | | * |
237 | | * deadline is a timestamp to return at. If it is ossl_time_infinite(), the call |
238 | | * never times out. |
239 | | * |
240 | | * Returns 0 on error and 1 on success. Timeout expiry is considered a success |
241 | | * condition. We don't elaborate our return values here because the way we are |
242 | | * actually using this doesn't currently care. |
243 | | * |
244 | | * If mutex is non-NULL, it is assumed to be held for write and is unlocked for |
245 | | * the duration of the call. |
246 | | * |
247 | | * Precondition: mutex is NULL or is held for write (unchecked) |
248 | | * Postcondition: mutex is NULL or is held for write (unless |
249 | | * CRYPTO_THREAD_write_lock fails) |
250 | | */ |
251 | | static int poll_two_fds(int rfd, int rfd_want_read, |
252 | | int wfd, int wfd_want_write, |
253 | | int notify_rfd, |
254 | | OSSL_TIME deadline, |
255 | | CRYPTO_MUTEX *mutex) |
256 | 0 | { |
257 | | #if defined(OPENSSL_SYS_WINDOWS) || !defined(POLLIN) |
258 | | fd_set rfd_set, wfd_set, efd_set; |
259 | | OSSL_TIME now, timeout; |
260 | | struct timeval tv, *ptv; |
261 | | int maxfd, pres; |
262 | | |
263 | | #ifndef OPENSSL_SYS_WINDOWS |
264 | | /* |
265 | | * On Windows there is no relevant limit to the magnitude of a fd value (see |
266 | | * above). On *NIX the fd_set uses a bitmap and we must check the limit. |
267 | | */ |
268 | | if (rfd >= FD_SETSIZE || wfd >= FD_SETSIZE) |
269 | | return 0; |
270 | | #endif |
271 | | |
272 | | FD_ZERO(&rfd_set); |
273 | | FD_ZERO(&wfd_set); |
274 | | FD_ZERO(&efd_set); |
275 | | |
276 | | if (rfd != INVALID_SOCKET && rfd_want_read) |
277 | | openssl_fdset(rfd, &rfd_set); |
278 | | if (wfd != INVALID_SOCKET && wfd_want_write) |
279 | | openssl_fdset(wfd, &wfd_set); |
280 | | |
281 | | /* Always check for error conditions. */ |
282 | | if (rfd != INVALID_SOCKET) |
283 | | openssl_fdset(rfd, &efd_set); |
284 | | if (wfd != INVALID_SOCKET) |
285 | | openssl_fdset(wfd, &efd_set); |
286 | | |
287 | | /* Check for notifier FD readability. */ |
288 | | if (notify_rfd != INVALID_SOCKET) { |
289 | | openssl_fdset(notify_rfd, &rfd_set); |
290 | | openssl_fdset(notify_rfd, &efd_set); |
291 | | } |
292 | | |
293 | | maxfd = rfd; |
294 | | if (wfd > maxfd) |
295 | | maxfd = wfd; |
296 | | if (notify_rfd > maxfd) |
297 | | maxfd = notify_rfd; |
298 | | |
299 | | if (!ossl_assert(rfd != INVALID_SOCKET || wfd != INVALID_SOCKET |
300 | | || !ossl_time_is_infinite(deadline))) |
301 | | /* Do not block forever; should not happen. */ |
302 | | return 0; |
303 | | |
304 | | /* |
305 | | * The mutex dance (unlock/re-locak after poll/seclect) is |
306 | | * potentially problematic. This may create a situation when |
307 | | * two threads arrive to select/poll with the same file |
308 | | * descriptors. We just need to be aware of this. |
309 | | */ |
310 | | #if defined(OPENSSL_THREADS) |
311 | | if (mutex != NULL) |
312 | | ossl_crypto_mutex_unlock(mutex); |
313 | | #endif |
314 | | |
315 | | do { |
316 | | /* |
317 | | * select expects a timeout, not a deadline, so do the conversion. |
318 | | * Update for each call to ensure the correct value is used if we repeat |
319 | | * due to EINTR. |
320 | | */ |
321 | | if (ossl_time_is_infinite(deadline)) { |
322 | | ptv = NULL; |
323 | | } else { |
324 | | now = ossl_time_now(); |
325 | | /* |
326 | | * ossl_time_subtract saturates to zero so we don't need to check if |
327 | | * now > deadline. |
328 | | */ |
329 | | timeout = ossl_time_subtract(deadline, now); |
330 | | tv = ossl_time_to_timeval(timeout); |
331 | | ptv = &tv; |
332 | | } |
333 | | |
334 | | pres = select(maxfd + 1, &rfd_set, &wfd_set, &efd_set, ptv); |
335 | | } while (pres == -1 && get_last_socket_error_is_eintr()); |
336 | | |
337 | | #if defined(OPENSSL_THREADS) |
338 | | if (mutex != NULL) |
339 | | ossl_crypto_mutex_lock(mutex); |
340 | | #endif |
341 | | |
342 | | return pres < 0 ? 0 : 1; |
343 | | #else |
344 | 0 | int pres, timeout_ms; |
345 | 0 | OSSL_TIME now, timeout; |
346 | 0 | struct pollfd pfds[3] = { 0 }; |
347 | 0 | size_t npfd = 0; |
348 | |
|
349 | 0 | if (rfd == wfd) { |
350 | 0 | pfds[npfd].fd = rfd; |
351 | 0 | pfds[npfd].events = (rfd_want_read ? POLLIN : 0) |
352 | 0 | | (wfd_want_write ? POLLOUT : 0); |
353 | 0 | if (rfd >= 0 && pfds[npfd].events != 0) |
354 | 0 | ++npfd; |
355 | 0 | } else { |
356 | 0 | pfds[npfd].fd = rfd; |
357 | 0 | pfds[npfd].events = (rfd_want_read ? POLLIN : 0); |
358 | 0 | if (rfd >= 0 && pfds[npfd].events != 0) |
359 | 0 | ++npfd; |
360 | |
|
361 | 0 | pfds[npfd].fd = wfd; |
362 | 0 | pfds[npfd].events = (wfd_want_write ? POLLOUT : 0); |
363 | 0 | if (wfd >= 0 && pfds[npfd].events != 0) |
364 | 0 | ++npfd; |
365 | 0 | } |
366 | |
|
367 | 0 | if (notify_rfd >= 0) { |
368 | 0 | pfds[npfd].fd = notify_rfd; |
369 | 0 | pfds[npfd].events = POLLIN; |
370 | 0 | ++npfd; |
371 | 0 | } |
372 | |
|
373 | 0 | if (!ossl_assert(npfd != 0 || !ossl_time_is_infinite(deadline))) |
374 | | /* Do not block forever; should not happen. */ |
375 | 0 | return 0; |
376 | | |
377 | 0 | #if defined(OPENSSL_THREADS) |
378 | 0 | if (mutex != NULL) |
379 | 0 | ossl_crypto_mutex_unlock(mutex); |
380 | 0 | #endif |
381 | |
|
382 | 0 | do { |
383 | 0 | if (ossl_time_is_infinite(deadline)) { |
384 | 0 | timeout_ms = -1; |
385 | 0 | } else { |
386 | 0 | now = ossl_time_now(); |
387 | 0 | timeout = ossl_time_subtract(deadline, now); |
388 | 0 | timeout_ms = ossl_time2ms(timeout); |
389 | 0 | } |
390 | |
|
391 | 0 | pres = poll(pfds, npfd, timeout_ms); |
392 | 0 | } while (pres == -1 && get_last_socket_error_is_eintr()); |
393 | |
|
394 | 0 | #if defined(OPENSSL_THREADS) |
395 | 0 | if (mutex != NULL) |
396 | 0 | ossl_crypto_mutex_lock(mutex); |
397 | 0 | #endif |
398 | |
|
399 | 0 | return pres < 0 ? 0 : 1; |
400 | 0 | #endif |
401 | 0 | } |
402 | | |
403 | | static int poll_descriptor_to_fd(const BIO_POLL_DESCRIPTOR *d, int *fd) |
404 | 0 | { |
405 | 0 | if (d == NULL || d->type == BIO_POLL_DESCRIPTOR_TYPE_NONE) { |
406 | 0 | *fd = INVALID_SOCKET; |
407 | 0 | return 1; |
408 | 0 | } |
409 | | |
410 | 0 | if (d->type != BIO_POLL_DESCRIPTOR_TYPE_SOCK_FD |
411 | 0 | || d->value.fd == INVALID_SOCKET) |
412 | 0 | return 0; |
413 | | |
414 | 0 | *fd = d->value.fd; |
415 | 0 | return 1; |
416 | 0 | } |
417 | | |
418 | | /* |
419 | | * Poll up to two abstract poll descriptors, as well as an optional notify FD. |
420 | | * Currently we only support poll descriptors which represent FDs. |
421 | | * |
422 | | * If mutex is non-NULL, it is assumed be a lock currently held for write and is |
423 | | * unlocked for the duration of any wait. |
424 | | * |
425 | | * Precondition: mutex is NULL or is held for write (unchecked) |
426 | | * Postcondition: mutex is NULL or is held for write (unless |
427 | | * CRYPTO_THREAD_write_lock fails) |
428 | | */ |
429 | | static int poll_two_descriptors(const BIO_POLL_DESCRIPTOR *r, int r_want_read, |
430 | | const BIO_POLL_DESCRIPTOR *w, int w_want_write, |
431 | | int notify_rfd, |
432 | | OSSL_TIME deadline, |
433 | | CRYPTO_MUTEX *mutex) |
434 | 0 | { |
435 | 0 | int rfd, wfd; |
436 | |
|
437 | 0 | if (!poll_descriptor_to_fd(r, &rfd) |
438 | 0 | || !poll_descriptor_to_fd(w, &wfd)) |
439 | 0 | return 0; |
440 | | |
441 | 0 | return poll_two_fds(rfd, r_want_read, wfd, w_want_write, |
442 | 0 | notify_rfd, deadline, mutex); |
443 | 0 | } |
444 | | |
445 | | /* |
446 | | * Notify other threads currently blocking in |
447 | | * ossl_quic_reactor_block_until_pred() calls that a predicate they are using |
448 | | * might now be met due to state changes. |
449 | | * |
450 | | * This function must be called after state changes which might cause a |
451 | | * predicate in another thread to now be met (i.e., ticking). It is a no-op if |
452 | | * inter-thread notification is not being used. |
453 | | * |
454 | | * The reactor mutex must be held while calling this function. |
455 | | */ |
456 | | static void rtor_notify_other_threads(QUIC_REACTOR *rtor) |
457 | 2.09M | { |
458 | 2.09M | if (!rtor->have_notifier) |
459 | 2.09M | return; |
460 | | |
461 | | /* |
462 | | * This function is called when we have done anything on this thread which |
463 | | * might allow a predicate for a block_until_pred call on another thread to |
464 | | * now be met. |
465 | | * |
466 | | * When this happens, we need to wake those threads using the notifier. |
467 | | * However, we do not want to wake *this* thread (if/when it subsequently |
468 | | * enters block_until_pred) due to the notifier FD becoming readable. |
469 | | * Therefore, signal the notifier, and use a CV to detect when all other |
470 | | * threads have woken. |
471 | | */ |
472 | | |
473 | 0 | if (rtor->cur_blocking_waiters == 0) |
474 | | /* Nothing to do in this case. */ |
475 | 0 | return; |
476 | | |
477 | | /* Signal the notifier to wake up all threads. */ |
478 | 0 | if (!rtor->signalled_notifier) { |
479 | 0 | ossl_rio_notifier_signal(&rtor->notifier); |
480 | 0 | rtor->signalled_notifier = 1; |
481 | 0 | } |
482 | | |
483 | | /* |
484 | | * Wait on the CV until all threads have finished the first phase of the |
485 | | * wakeup process and the last thread out has taken responsibility for |
486 | | * unsignalling the notifier. |
487 | | */ |
488 | 0 | while (rtor->signalled_notifier) |
489 | 0 | ossl_crypto_condvar_wait(rtor->notifier_cv, rtor->mutex); |
490 | 0 | } |
491 | | |
492 | | /* |
493 | | * Block until a predicate function evaluates to true. |
494 | | * |
495 | | * If mutex is non-NULL, it is assumed be a lock currently held for write and is |
496 | | * unlocked for the duration of any wait. |
497 | | * |
498 | | * Precondition: Must hold channel write lock (unchecked) |
499 | | * Precondition: mutex is NULL or is held for write (unchecked) |
500 | | * Postcondition: mutex is NULL or is held for write (unless |
501 | | * CRYPTO_THREAD_write_lock fails) |
502 | | */ |
503 | | int ossl_quic_reactor_block_until_pred(QUIC_REACTOR *rtor, |
504 | | int (*pred)(void *arg), void *pred_arg, |
505 | | uint32_t flags) |
506 | 0 | { |
507 | 0 | int res, net_read_desired, net_write_desired, notifier_fd; |
508 | 0 | OSSL_TIME tick_deadline; |
509 | |
|
510 | 0 | notifier_fd |
511 | 0 | = (rtor->have_notifier ? ossl_rio_notifier_as_fd(&rtor->notifier) |
512 | 0 | : INVALID_SOCKET); |
513 | |
|
514 | 0 | for (;;) { |
515 | 0 | if ((flags & SKIP_FIRST_TICK) != 0) |
516 | 0 | flags &= ~SKIP_FIRST_TICK; |
517 | 0 | else |
518 | | /* best effort */ |
519 | 0 | ossl_quic_reactor_tick(rtor, 0); |
520 | |
|
521 | 0 | if ((res = pred(pred_arg)) != 0) |
522 | 0 | return res; |
523 | | |
524 | 0 | net_read_desired = ossl_quic_reactor_net_read_desired(rtor); |
525 | 0 | net_write_desired = ossl_quic_reactor_net_write_desired(rtor); |
526 | 0 | tick_deadline = ossl_quic_reactor_get_tick_deadline(rtor); |
527 | 0 | if (!net_read_desired && !net_write_desired |
528 | 0 | && ossl_time_is_infinite(tick_deadline)) |
529 | | /* Can't wait if there is nothing to wait for. */ |
530 | 0 | return 0; |
531 | | |
532 | 0 | ossl_quic_reactor_enter_blocking_section(rtor); |
533 | |
|
534 | 0 | res = poll_two_descriptors(ossl_quic_reactor_get_poll_r(rtor), |
535 | 0 | net_read_desired, |
536 | 0 | ossl_quic_reactor_get_poll_w(rtor), |
537 | 0 | net_write_desired, |
538 | 0 | notifier_fd, |
539 | 0 | tick_deadline, |
540 | 0 | rtor->mutex); |
541 | | |
542 | | /* |
543 | | * We have now exited the OS poller call. We may have |
544 | | * (rtor->signalled_notifier), and other threads may still be blocking. |
545 | | * This means that cur_blocking_waiters may still be non-zero. As such, |
546 | | * we cannot unsignal the notifier until all threads have had an |
547 | | * opportunity to wake up. |
548 | | * |
549 | | * At the same time, we cannot unsignal in the case where |
550 | | * cur_blocking_waiters is now zero because this condition may not occur |
551 | | * reliably. Consider the following scenario: |
552 | | * |
553 | | * T1 enters block_until_pred, cur_blocking_waiters -> 1 |
554 | | * T2 enters block_until_pred, cur_blocking_waiters -> 2 |
555 | | * T3 enters block_until_pred, cur_blocking_waiters -> 3 |
556 | | * |
557 | | * T4 enters block_until_pred, does not block, ticks, |
558 | | * sees that cur_blocking_waiters > 0 and signals the notifier |
559 | | * |
560 | | * T3 wakes, cur_blocking_waiters -> 2 |
561 | | * T3 predicate is not satisfied, cur_blocking_waiters -> 3, block again |
562 | | * |
563 | | * Notifier is still signalled, so T3 immediately wakes again |
564 | | * and is stuck repeating the above steps. |
565 | | * |
566 | | * T1, T2 are also woken by the notifier but never see |
567 | | * cur_blocking_waiters drop to 0, so never unsignal the notifier. |
568 | | * |
569 | | * As such, a two phase approach is chosen when designalling the |
570 | | * notifier: |
571 | | * |
572 | | * First, all of the poll_two_descriptor calls on all threads are |
573 | | * allowed to exit due to the notifier being signalled. |
574 | | * |
575 | | * Second, the thread which happened to be the one which decremented |
576 | | * cur_blocking_waiters to 0 unsignals the notifier and is then |
577 | | * responsible for broadcasting to a CV to indicate to the other |
578 | | * threads that the synchronised wakeup has been completed. Other |
579 | | * threads wait for this CV to be signalled. |
580 | | * |
581 | | */ |
582 | 0 | ossl_quic_reactor_leave_blocking_section(rtor); |
583 | |
|
584 | 0 | if (!res) |
585 | | /* |
586 | | * We don't actually care why the call succeeded (timeout, FD |
587 | | * readiness), we just call reactor_tick and start trying to do I/O |
588 | | * things again. If poll_two_fds returns 0, this is some other |
589 | | * non-timeout failure and we should stop here. |
590 | | * |
591 | | * TODO(QUIC FUTURE): In the future we could avoid unnecessary |
592 | | * syscalls by not retrying network I/O that isn't ready based |
593 | | * on the result of the poll call. However this might be difficult |
594 | | * because it requires we do the call to poll(2) or equivalent |
595 | | * syscall ourselves, whereas in the general case the application |
596 | | * does the polling and just calls SSL_handle_events(). |
597 | | * Implementing this optimisation in the future will probably |
598 | | * therefore require API changes. |
599 | | */ |
600 | 0 | return 0; |
601 | 0 | } |
602 | | |
603 | 0 | return res; |
604 | 0 | } |
605 | | |
606 | | void ossl_quic_reactor_enter_blocking_section(QUIC_REACTOR *rtor) |
607 | 0 | { |
608 | 0 | ++rtor->cur_blocking_waiters; |
609 | 0 | } |
610 | | |
611 | | void ossl_quic_reactor_leave_blocking_section(QUIC_REACTOR *rtor) |
612 | 0 | { |
613 | 0 | assert(rtor->cur_blocking_waiters > 0); |
614 | 0 | --rtor->cur_blocking_waiters; |
615 | |
|
616 | 0 | if (rtor->have_notifier && rtor->signalled_notifier) { |
617 | 0 | if (rtor->cur_blocking_waiters == 0) { |
618 | 0 | ossl_rio_notifier_unsignal(&rtor->notifier); |
619 | 0 | rtor->signalled_notifier = 0; |
620 | | |
621 | | /* |
622 | | * Release the other threads which have woken up (and possibly |
623 | | * rtor_notify_other_threads as well). |
624 | | */ |
625 | 0 | ossl_crypto_condvar_broadcast(rtor->notifier_cv); |
626 | 0 | } else { |
627 | | /* We are not the last waiter out - so wait for that one. */ |
628 | 0 | while (rtor->signalled_notifier) |
629 | 0 | ossl_crypto_condvar_wait(rtor->notifier_cv, rtor->mutex); |
630 | 0 | } |
631 | 0 | } |
632 | 0 | } |