xref: /freebsd/crypto/openssl/include/internal/quic_reactor_wait_ctx.h (revision e7be843b4a162e68651d3911f0357ed464915629)
1 /*
2  * Copyright 2024-2025 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 #ifndef OSSL_QUIC_REACTOR_WAIT_CTX_H
10 # define OSSL_QUIC_REACTOR_WAIT_CTX_H
11 
12 # include "internal/quic_predef.h"
13 # include "internal/quic_reactor.h"
14 # include "internal/list.h"
15 
16 # ifndef OPENSSL_NO_QUIC
17 
18 /*
19  * QUIC_REACTOR_WAIT_CTX
20  * =====================
21  *
22  * In order to support inter-thread notification of events which may cause a
23  * blocking call on another thread to be able to make forward progress, we need
24  * to know when a thread enters and exits a blocking call. The details of why
25  * this is involves subtleties of inter-thread synchronisation and a detailed
26  * discussion can be found in the source of
27  * ossl_quic_reactor_block_until_pred().
28  *
29  * The core mechanism for such tracking is
30  * ossl_quic_reactor_(enter/leave)_blocking_section(), however this API does not
31  * support recursive usage to keep the internal implementation simple. In some
32  * cases, an API which can be used in a recursive fashion (with multiple
33  * balanced calls to enter()/leave() on a single thread) is more convenient.
34  *
35  * This utility allows multiple blocking operations to be registered on a given
36  * thread. Moreover, it allows multiple blocking operations to be registered
37  * across an arbitrarily large number of QUIC_REACTORs from a given thread.
38  *
39  * In short, this allows multiple 'concurrent' blocking calls to be ongoing on a
40  * given thread for a given reactor. While on the face of it the notion of
41  * multiple concurrent blocking calls on a single thread makes no sense, the
42  * implementation of our immediate-mode polling implementation (SSL_poll) makes
43  * it natural for us to implement it simply by registering a blocking call per
44  * SSL object passed in. Since multiple SSL objects may be passed to an SSL_poll
45  * call, and some SSL objects may correspond to the same reactor, and other SSL
46  * objects may correspond to a different reactor, we need to be able to
47  * determine when a SSL_poll() call has finished with all of the SSL objects
48  * *corresponding to a given reactor*.
49  *
50  * Doing this requires some ephemeral state tracking as a SSL_poll() call may
51  * map to an arbitrarily large set of reactor objects. For now, we track this
52  * separately from the reactor code as the state needed is only ephemeral and
53  * this keeps the reactor internals simple.
54  *
55  * The concept used is that a thread allocates (on the stack) a
56  * QUIC_REACTOR_WAIT_CTX before commencing a blocking operation, and then calls
57  * ossl_quic_reactor_wait_ctx_enter() whenever encountering a reactor involved
58  * in the imminent blocking operation. Later it must ensure it calls
59  * ossl_quic_reactor_wait_ctx_leave() the same number of times for each reactor.
60  * enter() and leave() may be called multiple times for the same reactor and
61  * wait context so long as the number of calls is balanced. The last leave()
62  * call for a given thread's wait context *and a given reactor* causes that
63  * reactor to do the inter-thread notification housekeeping needed for
64  * multithreaded blocking to work correctly.
65  *
66  * The gist is that a simple reactor-level counter of active concurrent blocking
67  * calls across all threads is not accurate and we need an accurate count of how
68  * many 'concurrent' blocking calls for a given reactor are active *on a given
69  * thread* in order to avoid deadlocks. Conceptually, you can think of this as
70  * refcounting a refcount (which is actually how it is implemented).
71  *
72  * Logically, a wait context is a map from a reactor pointer (i.e., a unique
73  * identifier for the reactor) to the number of 'recursive' calls outstanding:
74  *
75  *   (QUIC_REACTOR *) -> (outstanding call count)
76  *
77  * When the count for a reactor transitions from 0 to a nonzero value, or vice
78  * versa, ossl_quic_reactor_(enter/leave)_blocking_section() is called once.
79  *
80  * The internal implementation is based on linked lists as we expect the actual
81  * number of reactors involved in a given blocking operation to be very small,
82  * so spinning up a hashtable is not worthwhile.
83  */
84 typedef struct quic_reactor_wait_slot_st QUIC_REACTOR_WAIT_SLOT;
85 
86 DECLARE_LIST_OF(quic_reactor_wait_slot, QUIC_REACTOR_WAIT_SLOT);
87 
88 struct quic_reactor_wait_ctx_st {
89     OSSL_LIST(quic_reactor_wait_slot) slots;
90 };
91 
92 /* Initialises a wait context. */
93 void ossl_quic_reactor_wait_ctx_init(QUIC_REACTOR_WAIT_CTX *ctx);
94 
95 /* Uprefs a given reactor. */
96 int ossl_quic_reactor_wait_ctx_enter(QUIC_REACTOR_WAIT_CTX *ctx,
97                                      QUIC_REACTOR *rtor);
98 
99 /* Downrefs a given reactor. */
100 void ossl_quic_reactor_wait_ctx_leave(QUIC_REACTOR_WAIT_CTX *ctx,
101                                       QUIC_REACTOR *rtor);
102 
103 /*
104  * Destroys a wait context. Must be called after calling init().
105  *
106  * Precondition: All prior calls to ossl_quic_reactor_wait_ctx_enter() must have
107  * been balanced with corresponding leave() calls before calling this
108  * (unchecked).
109  */
110 void ossl_quic_reactor_wait_ctx_cleanup(QUIC_REACTOR_WAIT_CTX *ctx);
111 
112 # endif
113 
114 #endif
115