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