xref: /freebsd/crypto/openssl/doc/designs/quic-design/quic-concurrency.md (revision e7be843b4a162e68651d3911f0357ed464915629)
1*e7be843bSPierre ProncheryQUIC Concurrency Architecture
2*e7be843bSPierre Pronchery=============================
3*e7be843bSPierre Pronchery
4*e7be843bSPierre ProncheryIntroduction
5*e7be843bSPierre Pronchery------------
6*e7be843bSPierre Pronchery
7*e7be843bSPierre ProncheryMost QUIC implementations in C are offered as a simple state machine without any
8*e7be843bSPierre Proncheryincluded I/O solution. Applications must do significant integration work to
9*e7be843bSPierre Proncheryprovide the necessary infrastructure for a QUIC implementation to integrate
10*e7be843bSPierre Proncherywith. Moreover, blocking I/O at an application level may not be supported.
11*e7be843bSPierre Pronchery
12*e7be843bSPierre ProncheryOpenSSL QUIC seeks to offer a QUIC solution which can serve multiple use cases:
13*e7be843bSPierre Pronchery
14*e7be843bSPierre Pronchery- Firstly, it seeks to offer the simple state machine model and a fully
15*e7be843bSPierre Pronchery  customisable network path (via a BIO) for those who want it;
16*e7be843bSPierre Pronchery
17*e7be843bSPierre Pronchery- Secondly, it seeks to offer a turnkey solution with an in-the-box I/O
18*e7be843bSPierre Pronchery  and polling solution which can support blocking API calls in a Berkeley
19*e7be843bSPierre Pronchery  sockets-like way.
20*e7be843bSPierre Pronchery
21*e7be843bSPierre ProncheryThese usage modes are somewhat diametrically opposed. One involves libssl
22*e7be843bSPierre Proncheryconsuming no resources but those it is given, with an application responsible
23*e7be843bSPierre Proncheryfor synchronisation and a potentially custom network I/O path. This usage model
24*e7be843bSPierre Proncheryis not “smart”. Network traffic is connected to the state machine and state is
25*e7be843bSPierre Proncheryinput and output from the state machine as needed by an application on a purely
26*e7be843bSPierre Proncherynon-blocking basis. Determining *when* to do anything is largely the
27*e7be843bSPierre Proncheryapplication's responsibility.
28*e7be843bSPierre Pronchery
29*e7be843bSPierre ProncheryThe other diametrically opposed usage mode involves libssl managing more things
30*e7be843bSPierre Proncheryinternally to provide an easier to use solution. For example, it may involve
31*e7be843bSPierre Proncheryspinning up background threads to ensure connections are serviced regularly (as
32*e7be843bSPierre Proncheryin our existing client-side thread assisted mode).
33*e7be843bSPierre Pronchery
34*e7be843bSPierre ProncheryIn order to provide for these different use cases, the concept of concurrency
35*e7be843bSPierre Proncherymodels is introduced. A concurrency model defines how “cleverly” the QUIC engine
36*e7be843bSPierre Proncherywill operate and how many background resources (e.g. threads, other OS
37*e7be843bSPierre Proncheryresources) will be established to support operation.
38*e7be843bSPierre Pronchery
39*e7be843bSPierre ProncheryConcurrency Models
40*e7be843bSPierre Pronchery------------------
41*e7be843bSPierre Pronchery
42*e7be843bSPierre Pronchery- **Unsynchronised Concurrency Model (UCM):** In the Unsynchronised Concurrency
43*e7be843bSPierre Pronchery  Model, calls to SSL objects are not synchronised. There is no locking on any
44*e7be843bSPierre Pronchery  APL call (the omission of which is purely an optimisation). The application is
45*e7be843bSPierre Pronchery  either single-threaded or is otherwise responsible for doing synchronisation
46*e7be843bSPierre Pronchery  itself.
47*e7be843bSPierre Pronchery
48*e7be843bSPierre Pronchery  Blocking API calls are not supported under this model. This model is intended
49*e7be843bSPierre Pronchery  primarily for single-threaded use as a simple state machine by advanced
50*e7be843bSPierre Pronchery  applications, and many applications will be likely to disable autoticking.
51*e7be843bSPierre Pronchery
52*e7be843bSPierre Pronchery- **Contentive Concurrency Model (CCM):** In the
53*e7be843bSPierre Pronchery  Contentive Concurrency Model, calls to SSL objects are wrapped in locks and
54*e7be843bSPierre Pronchery  multi-threaded usage of a QUIC connection (for example, parallel writes to
55*e7be843bSPierre Pronchery  different QUIC stream SSL objects belonging to the same QUIC connection) is
56*e7be843bSPierre Pronchery  synchronised by a mutex.
57*e7be843bSPierre Pronchery
58*e7be843bSPierre Pronchery  This is contentive in the sense that if a large number of threads are trying
59*e7be843bSPierre Pronchery  to write to different streams on the same connection, a large amount of lock
60*e7be843bSPierre Pronchery  contention will occur. As such, this concurrency model will not scale and
61*e7be843bSPierre Pronchery  provide good performance, at least within the context of concurrent use
62*e7be843bSPierre Pronchery  of a single connection.
63*e7be843bSPierre Pronchery
64*e7be843bSPierre Pronchery  Under this model, APL calls by the application result in lock-wrapped
65*e7be843bSPierre Pronchery  mutations of QUIC core objects (`QUIC_CHANNEL`, `QUIC_STREAM`, etc.) on the
66*e7be843bSPierre Pronchery  same thread.
67*e7be843bSPierre Pronchery
68*e7be843bSPierre Pronchery  This model may be used either in a variant which does not support blocking
69*e7be843bSPierre Pronchery  (NB-CCM) or which does support blocking (B-CCM). The blocking variant must
70*e7be843bSPierre Pronchery  spin up additional OS resources to correctly support blocking semantics.
71*e7be843bSPierre Pronchery
72*e7be843bSPierre Pronchery- **Thread Assisted Contentive Concurrency Model (TA-CCM):** This is currently
73*e7be843bSPierre Pronchery  implemented by our thread assisted mode for client-side QUIC usage. It does
74*e7be843bSPierre Pronchery  not realise the full state separation or performance of the Worker Concurrency
75*e7be843bSPierre Pronchery  Model (WCM) below. Instead, it simply spawns a background thread which ensures
76*e7be843bSPierre Pronchery  QUIC timer events are handled as needed. It makes use of the Contentive
77*e7be843bSPierre Pronchery  Concurrency Model for performing that handling, in that it obtains a lock when
78*e7be843bSPierre Pronchery  ticking a QUIC connection just as any call by an application would.
79*e7be843bSPierre Pronchery
80*e7be843bSPierre Pronchery  This mode is likely to be deprecated in favour of the full Worker Concurrency
81*e7be843bSPierre Pronchery  Model (WCM), which it will naturally be subsumed by.
82*e7be843bSPierre Pronchery
83*e7be843bSPierre Pronchery- **Worker Concurrency Model (WCM):** In the Worker Concurrency Model,
84*e7be843bSPierre Pronchery  a background worker thread is spawned to manage connection processing. All
85*e7be843bSPierre Pronchery  interaction with a SSL object goes through this thread in some way.
86*e7be843bSPierre Pronchery  Interactions with SSL objects are essentially translated into commands and
87*e7be843bSPierre Pronchery  handled by the worker thread. To optimise performance and minimise lock
88*e7be843bSPierre Pronchery  contention, there is an emphasis on message passing over locking.
89*e7be843bSPierre Pronchery  Internal dataflow for application data can be managed in a zero-copy way to
90*e7be843bSPierre Pronchery  minimise the costs of this message passing.
91*e7be843bSPierre Pronchery
92*e7be843bSPierre Pronchery  Under this model, QUIC core objects (`QUIC_CHANNEL`, `QUIC_STREAM`, etc.) will
93*e7be843bSPierre Pronchery  live solely on the worker thread and access to these objects by an application
94*e7be843bSPierre Pronchery  thread will be entirely forbidden.
95*e7be843bSPierre Pronchery
96*e7be843bSPierre Pronchery  Blocking API calls are supported under this model.
97*e7be843bSPierre Pronchery
98*e7be843bSPierre ProncheryThese concurrency models are summarised as follows:
99*e7be843bSPierre Pronchery
100*e7be843bSPierre Pronchery| Model  | Sophistication | Concurrency           | Blocking Supported | OS Resources              | Timer Events    | RX Steering | Core State Affinity  |
101*e7be843bSPierre Pronchery|--------|----------------|-----------------------|--------------------|---------------------------|-----------------|-------------|----------------------|
102*e7be843bSPierre Pronchery| UCM    | Lowest         | ST only               | No                 | None                      | App Responsible | None        | App Thread           |
103*e7be843bSPierre Pronchery| CCM    |                | MT (Contentive)       | Optional           | Mutex, (Notifier)         | App Responsible | TBD         | App Threads          |
104*e7be843bSPierre Pronchery| TA-CCM† |                | MT (Contentive)       | Optional           | Mutex, Thread, (Notifier) | Managed         | TBD         | App & Assist Threads |
105*e7be843bSPierre Pronchery| WCM    | Highest        | MT (High Performance) | Yes                | Mutex, Thread, Notifier   | Managed         | Futureproof | Worker Thread        |
106*e7be843bSPierre Pronchery
107*e7be843bSPierre Pronchery† To eventually be deprecated in favour of WCM.
108*e7be843bSPierre Pronchery
109*e7be843bSPierre ProncheryLegend:
110*e7be843bSPierre Pronchery
111*e7be843bSPierre Pronchery- **Blocking Supported:** Whether blocking calls to e.g. `SSL_read` can be
112*e7be843bSPierre Pronchery  supported. If this is listed as “optional”, extra resources are required to
113*e7be843bSPierre Pronchery  support this under the listed model and these resources could be omitted if an
114*e7be843bSPierre Pronchery  application indicates it does not need this functionality at initialisation
115*e7be843bSPierre Pronchery  time.
116*e7be843bSPierre Pronchery
117*e7be843bSPierre Pronchery- **OS Resources:** “Mutex” refers to mutex and condition variable resources.
118*e7be843bSPierre Pronchery  “Notifier” refers to a kind of OS resource needed to allow one thread to wake
119*e7be843bSPierre Pronchery  another thread which is currently blocking in an OS socket polling call such
120*e7be843bSPierre Pronchery  as poll(2) (e.g. an eventfd or socketpair). Resources listed in parentheses in
121*e7be843bSPierre Pronchery  the table above are required only if blocking support is desired.
122*e7be843bSPierre Pronchery
123*e7be843bSPierre Pronchery- **Timer Events:** Is an application responsible for ensuring QUIC timeout
124*e7be843bSPierre Pronchery  events are handled in a timely manner?
125*e7be843bSPierre Pronchery
126*e7be843bSPierre Pronchery- **RX Steering:** The matter of RX steering will be discussed in detail in a
127*e7be843bSPierre Pronchery  future document. Broadly speaking, RX steering concerns whether incoming
128*e7be843bSPierre Pronchery  traffic for multiple different QUIC connections on the same local port (e.g.
129*e7be843bSPierre Pronchery  for a server) can be vectored *by the OS* to different threads or whether the
130*e7be843bSPierre Pronchery  demuxing of incoming traffic for different connections has to be done manually
131*e7be843bSPierre Pronchery  on an in-process basis.
132*e7be843bSPierre Pronchery
133*e7be843bSPierre Pronchery  The WCM model most readily supports RX steering and is futureproof in this
134*e7be843bSPierre Pronchery  regard. The feasibility of having the UCM and CCM models support RX steering
135*e7be843bSPierre Pronchery  is left for future analysis.
136*e7be843bSPierre Pronchery
137*e7be843bSPierre Pronchery- **Core State Affinity:** Which threads are allowed to touch the QUIC core
138*e7be843bSPierre Pronchery  objects (`QUIC_CHANNEL`, `QUIC_STREAM`, etc.)
139*e7be843bSPierre Pronchery
140*e7be843bSPierre ProncheryArchitecture
141*e7be843bSPierre Pronchery------------
142*e7be843bSPierre Pronchery
143*e7be843bSPierre ProncheryTo recap, the API Personality Layer (APL) refers to the code in `quic_impl.c`
144*e7be843bSPierre Proncherywhich implements the libssl API personality (`SSL_write`, etc.). The APL is
145*e7be843bSPierre Proncherycleanly separated from the QUIC core implementation (`QUIC_CHANNEL`, etc.).
146*e7be843bSPierre Pronchery
147*e7be843bSPierre ProncherySince UCM is basically a slight optimisation of CCM in which unnecessary locking
148*e7be843bSPierre Proncheryis elided, discussion from hereon in will focus on CCM and WCM except where
149*e7be843bSPierre Proncherythere are specific differences between CCM and UCM.
150*e7be843bSPierre Pronchery
151*e7be843bSPierre ProncherySupporting both CCM and WCM creates significant architectural challenges. Under
152*e7be843bSPierre ProncheryCCM, QUIC core objects have their state mutated under lock by arbitrary
153*e7be843bSPierre Proncheryapplication threads and these mutations happen during APL calls. By contrast, a
154*e7be843bSPierre Proncheryperformant WCM architecture requires that APL calls be recorded and serviced in
155*e7be843bSPierre Proncheryan asynchronous fashion involving message passing to a worker thread. This
156*e7be843bSPierre Proncherythreatens to require highly divergent dispatch architectures for the two
157*e7be843bSPierre Proncheryconcurrency models.
158*e7be843bSPierre Pronchery
159*e7be843bSPierre ProncheryAs such, the concept of a **Concurrency Management Layer (CML)** is introduced.
160*e7be843bSPierre ProncheryThe CML lives between the APL and the QUIC core code. It is responsible for
161*e7be843bSPierre Proncherydispatching in-thread mutations of QUIC core objects when operating under CCM,
162*e7be843bSPierre Proncheryand for dispatching messages to a worker thread under WCM.
163*e7be843bSPierre Pronchery
164*e7be843bSPierre Pronchery![Concurrency Models Diagram](images/quic-concurrency-models.svg)
165*e7be843bSPierre Pronchery
166*e7be843bSPierre ProncheryThere are two different CMLs:
167*e7be843bSPierre Pronchery
168*e7be843bSPierre Pronchery- **Direct CML (DCML)**, in which core objects are worked on in the same thread
169*e7be843bSPierre Pronchery  which made an APL call, under lock;
170*e7be843bSPierre Pronchery
171*e7be843bSPierre Pronchery- **Worker CML (WCML)**, in which core objects are managed by a worker thread
172*e7be843bSPierre Pronchery  with communication via message passing. This CML is split into a front end
173*e7be843bSPierre Pronchery  (WCML-FE) and back end (WCML-BE).
174*e7be843bSPierre Pronchery
175*e7be843bSPierre ProncheryThe legacy thread assisted mode uses a bespoke method which is similar to the
176*e7be843bSPierre Proncheryapproach used by the DCML.
177*e7be843bSPierre Pronchery
178*e7be843bSPierre ProncheryCML Design
179*e7be843bSPierre Pronchery----------
180*e7be843bSPierre Pronchery
181*e7be843bSPierre ProncheryThe CML is designed to have as small an API surface area as possible to enable
182*e7be843bSPierre Proncheryunified handling of as many kinds of (APL) API operations as possible. The idea
183*e7be843bSPierre Proncheryis that complex APL calls are translated into simple operations on the CML.
184*e7be843bSPierre Pronchery
185*e7be843bSPierre ProncheryAt its core, the CML exposes some number of *pipes*. The number of pipes which
186*e7be843bSPierre Proncherycan be accessed via the CML varies as connections and streams are created and
187*e7be843bSPierre Proncherydestroyed. A pipe is a *unidirectional* transport for byte streams. Zero-copy
188*e7be843bSPierre Proncheryoptimisations are expected to be implemented in future but are deferred.
189*e7be843bSPierre Pronchery
190*e7be843bSPierre ProncheryThe CML (`QUIC_CML`) allows the caller to refer to a pipe by providing an opaque
191*e7be843bSPierre Proncherypipe handle (`QUIC_CML_PIPE`). If the pipe is a sending pipe, the caller can use
192*e7be843bSPierre Pronchery`ossl_cml_write` to try and add bytes to it. Conversely, if it is a receiving
193*e7be843bSPierre Proncherypipe, the caller can use `ossl_cml_read` to try and read bytes from it.
194*e7be843bSPierre Pronchery
195*e7be843bSPierre ProncheryThe method `ossl_cml_block_until` allows the caller to block until at least one
196*e7be843bSPierre Proncheryof the provided pipe handles is ready. Ready means that at least one byte can be
197*e7be843bSPierre Proncherywritten (for a sending pipe) or at least one byte can be read (for a receiving
198*e7be843bSPierre Proncherypipe).
199*e7be843bSPierre Pronchery
200*e7be843bSPierre ProncheryNote that there is only expected to be one `QUIC_CML` instance per QUIC event
201*e7be843bSPierre Proncheryprocessing domain (i.e., per `QUIC_DOMAIN` / `QUIC_ENGINE` instance). The CML
202*e7be843bSPierre Proncheryfully abstracts the QUIC core objects such as `QUIC_ENGINE` or `QUIC_CHANNEL` so
203*e7be843bSPierre Proncherythat the APL never sees them.
204*e7be843bSPierre Pronchery
205*e7be843bSPierre ProncheryThe caller retrieves a pipe handle using `ossl_cml_get_pipe`. This function
206*e7be843bSPierre Proncheryretrieves a pipe based on two values:
207*e7be843bSPierre Pronchery
208*e7be843bSPierre Pronchery  - a CML pipe class;
209*e7be843bSPierre Pronchery  - a CML *selector*.
210*e7be843bSPierre Pronchery
211*e7be843bSPierre ProncheryThe CML selector is a tagged union structure which specifies what pipe is to be
212*e7be843bSPierre Proncheryretrieved. Abstractly, examples of selectors include:
213*e7be843bSPierre Pronchery
214*e7be843bSPierre Pronchery```text
215*e7be843bSPierre Pronchery    Domain      ()
216*e7be843bSPierre Pronchery    Listener    (listener_id: uint)
217*e7be843bSPierre Pronchery    Conn        (conn_id:     uint)
218*e7be843bSPierre Pronchery    Stream      (conn_id:     uint, stream_id: u64)
219*e7be843bSPierre Pronchery```
220*e7be843bSPierre Pronchery
221*e7be843bSPierre ProncheryIn other words, the CML selector selects the “object” to retrieve a pipe from.
222*e7be843bSPierre Pronchery
223*e7be843bSPierre ProncheryThe CML pipe class is one of the following values:
224*e7be843bSPierre Pronchery
225*e7be843bSPierre Pronchery- Request
226*e7be843bSPierre Pronchery- Notification
227*e7be843bSPierre Pronchery- App Send
228*e7be843bSPierre Pronchery- App Recv
229*e7be843bSPierre Pronchery
230*e7be843bSPierre ProncheryThe pipe classes available for a given selector vary. For example, the “App
231*e7be843bSPierre ProncherySend” and “App Recv” pipes only exist on a stream, so it is invalid to request
232*e7be843bSPierre Proncherysuch a pipe in conjunction with a different type of selector.
233*e7be843bSPierre Pronchery
234*e7be843bSPierre ProncheryThe “Request” and “App Send” classes expose send-only streams, and the
235*e7be843bSPierre Pronchery“Notification” and “App Recv” classes expose receive-only streams.
236*e7be843bSPierre Pronchery
237*e7be843bSPierre ProncheryFor any given CML selector, the Request pipe is used to send serialized commands
238*e7be843bSPierre Proncheryfor asynchronous processing in relation to the entity selected by that selector.
239*e7be843bSPierre ProncheryConversely, the Notification pipe returns asynchronous notifications. These
240*e7be843bSPierre Proncherycould be in relation to a previous Command (e.g. indicating whether a command
241*e7be843bSPierre Proncherysucceeded), or unprompted notifications about other events.
242*e7be843bSPierre Pronchery
243*e7be843bSPierre ProncheryThe underlying pattern here is that there is a bidirectional channel for control
244*e7be843bSPierre Proncherymessages, and a bidirectional channel for application data, both comprised of
245*e7be843bSPierre Proncherytwo unidirectional pipes in turn.
246*e7be843bSPierre Pronchery
247*e7be843bSPierre ProncheryPipe handles are stable for as long as the pipe they reference exists, so an APL
248*e7be843bSPierre Proncheryobject can cache a pipe handle if desired.
249*e7be843bSPierre Pronchery
250*e7be843bSPierre ProncheryAll CML methods are thread safe. The CML implementation handles any necessary
251*e7be843bSPierre Proncherylocking (if any) internally.
252*e7be843bSPierre Pronchery
253*e7be843bSPierre ProncheryThe `ossl_cml_write_available` and `ossl_cml_read_available` calls determine the
254*e7be843bSPierre Proncherynumber of bytes which can currently be written to a send-only pipe, or read from
255*e7be843bSPierre Proncherya receive-only pipe, respectively.
256*e7be843bSPierre Pronchery
257*e7be843bSPierre Pronchery**Race conditions.** Because these are separate calls to `ossl_cml_write` and
258*e7be843bSPierre Pronchery`ossl_cml_read`, the values returned by these functions may become out of date
259*e7be843bSPierre Proncherybefore the caller has a chance to read `ossl_cml_write` or `ossl_cml_read`.
260*e7be843bSPierre ProncheryHowever, such changes are guaranteed to be monotonically in favour of the
261*e7be843bSPierre Proncherycaller; for example, the value returned by `ossl_cml_write_available` will only
262*e7be843bSPierre Proncheryever increase asynchronously (and only decrease as a result of an
263*e7be843bSPierre Pronchery`ossl_cml_write` call). Conversely, the value returned by
264*e7be843bSPierre Pronchery`ossl_cml_read_available` will only ever increase asynchronously (and only
265*e7be843bSPierre Proncherydecrease as a result of an `ossl_cml_read` call). Assuming that only one thread
266*e7be843bSPierre Proncherymakes calls to CML functions at a given time *for a given pipe*, this therefore
267*e7be843bSPierre Proncheryposes no issue for callers.
268*e7be843bSPierre Pronchery
269*e7be843bSPierre ProncheryConcurrent use of `ossl_cml_write` or `ossl_cml_read` for a given pipe is not
270*e7be843bSPierre Proncheryintended (and would not make sense in any case). The caller is responsible for
271*e7be843bSPierre Proncherysynchronising such calls.
272*e7be843bSPierre Pronchery
273*e7be843bSPierre Pronchery**Examples of pipe usage.** The application data pipes are used to serialize the
274*e7be843bSPierre Proncheryactual application data sent or received on a QUIC stream. The usage of the
275*e7be843bSPierre Proncheryrequest/notification pipes is more varied and used for control activity. There
276*e7be843bSPierre Proncheryis therefore a “control/data” separation here. The request and notification
277*e7be843bSPierre Proncherypipes transport tagged unions. Abstractly, commands and notifications might
278*e7be843bSPierre Proncheryinclude:
279*e7be843bSPierre Pronchery
280*e7be843bSPierre Pronchery- Request: Reset Stream (error code: u64)
281*e7be843bSPierre Pronchery- Notification: Connection Terminated by Peer
282*e7be843bSPierre Pronchery
283*e7be843bSPierre Pronchery**Example implementation of `SSL_write`.** An `SSL_write`-like API might be
284*e7be843bSPierre Proncheryimplemented in the APL like this:
285*e7be843bSPierre Pronchery
286*e7be843bSPierre Pronchery```c
287*e7be843bSPierre Proncheryint do_write(QUIC_CML *cml,
288*e7be843bSPierre Pronchery             QUIC_CML_PIPE notification_pipe,
289*e7be843bSPierre Pronchery             QUIC_CML_PIPE app_send_pipe,
290*e7be843bSPierre Pronchery             const void *buf, size_t buf_len)
291*e7be843bSPierre Pronchery{
292*e7be843bSPierre Pronchery    size_t bytes_written = 0;
293*e7be843bSPierre Pronchery
294*e7be843bSPierre Pronchery    for (;;) {
295*e7be843bSPierre Pronchery        /* e.g. connection termination */
296*e7be843bSPierre Pronchery        process_any_notifications(notification_pipe);
297*e7be843bSPierre Pronchery
298*e7be843bSPierre Pronchery        /* state checks, etc. */
299*e7be843bSPierre Pronchery        if (...->conn_terminated)
300*e7be843bSPierre Pronchery            return 0;
301*e7be843bSPierre Pronchery
302*e7be843bSPierre Pronchery        if (buf_len == 0)
303*e7be843bSPierre Pronchery            return 1;
304*e7be843bSPierre Pronchery
305*e7be843bSPierre Pronchery        if (!ossl_cml_write(cml, app_send_pipe, buf, buf_len, &bytes_written))
306*e7be843bSPierre Pronchery            return 0;
307*e7be843bSPierre Pronchery
308*e7be843bSPierre Pronchery        if (bytes_written == 0) {
309*e7be843bSPierre Pronchery            if (!should_block())
310*e7be843bSPierre Pronchery                break;
311*e7be843bSPierre Pronchery
312*e7be843bSPierre Pronchery            ossl_cml_block_until(cml, {notification_pipe, app_send_pipe});
313*e7be843bSPierre Pronchery            continue; /* try again */
314*e7be843bSPierre Pronchery        }
315*e7be843bSPierre Pronchery
316*e7be843bSPierre Pronchery        buf     += bytes_written;
317*e7be843bSPierre Pronchery        buf_len -= bytes_written;
318*e7be843bSPierre Pronchery    }
319*e7be843bSPierre Pronchery
320*e7be843bSPierre Pronchery    return 1;
321*e7be843bSPierre Pronchery}
322*e7be843bSPierre Pronchery```
323*e7be843bSPierre Pronchery
324*e7be843bSPierre Pronchery```c
325*e7be843bSPierre Pronchery/*
326*e7be843bSPierre Pronchery * Creates a new CML using the Direct CML (DCML) implementation. need_locking
327*e7be843bSPierre Pronchery * may be 0 to elide mutex usage if the application is guaranteed to synchronise
328*e7be843bSPierre Pronchery * access or is purely single-threaded.
329*e7be843bSPierre Pronchery */
330*e7be843bSPierre ProncheryQUIC_CML *ossl_cml_new_direct(int need_locking);
331*e7be843bSPierre Pronchery
332*e7be843bSPierre Pronchery/* Creates a new CML using the Worker CML (WCML) implementation. */
333*e7be843bSPierre ProncheryQUIC_CML *ossl_cml_new_worker(size_t num_worker_threads);
334*e7be843bSPierre Pronchery
335*e7be843bSPierre Pronchery/*
336*e7be843bSPierre Pronchery * Starts the CML operating. Idempotent after it returns successfully. For the
337*e7be843bSPierre Pronchery * WCML this might e.g. start background threads; for the DCML it is likely to
338*e7be843bSPierre Pronchery * be a no-op (but must still be called).
339*e7be843bSPierre Pronchery */
340*e7be843bSPierre Proncheryint ossl_cml_start(QUIC_CML *cml);
341*e7be843bSPierre Pronchery
342*e7be843bSPierre Pronchery/*
343*e7be843bSPierre Pronchery * Begins the CML shutdown process. Returns 1 once shutdown is complete; may
344*e7be843bSPierre Pronchery * need to be called multiple times until shutdown is done.
345*e7be843bSPierre Pronchery */
346*e7be843bSPierre Proncheryint ossl_cml_shutdown(QUIC_CML *cml);
347*e7be843bSPierre Pronchery
348*e7be843bSPierre Pronchery/*
349*e7be843bSPierre Pronchery * Immediate free of the CML. This is always safe but may cause handling
350*e7be843bSPierre Pronchery * of a connection to be aborted abruptly as it is an immediate teardown
351*e7be843bSPierre Pronchery * of all state.
352*e7be843bSPierre Pronchery */
353*e7be843bSPierre Proncheryvoid ossl_cml_free(QUIC_CML *cml);
354*e7be843bSPierre Pronchery
355*e7be843bSPierre Pronchery/*
356*e7be843bSPierre Pronchery * Retrieves a pipe for a logical CML object described by selector. The pipe
357*e7be843bSPierre Pronchery * handle, which is stable over the life of the logical CML object, is written
358*e7be843bSPierre Pronchery * to *pipe_handle. class_ is a QUIC_CML_CLASS value.
359*e7be843bSPierre Pronchery */
360*e7be843bSPierre Proncheryenum {
361*e7be843bSPierre Pronchery    QUIC_CML_CLASS_REQUEST,         /* control; send */
362*e7be843bSPierre Pronchery    QUIC_CML_CLASS_NOTIFICATION,    /* control; recv */
363*e7be843bSPierre Pronchery    QUIC_CML_CLASS_APP_SEND,        /* data; send */
364*e7be843bSPierre Pronchery    QUIC_CML_CLASS_APP_RECV         /* data; recv */
365*e7be843bSPierre Pronchery};
366*e7be843bSPierre Pronchery
367*e7be843bSPierre Proncheryint ossl_cml_get_pipe(QUIC_CML                  *cml,
368*e7be843bSPierre Pronchery                      int                       class_,
369*e7be843bSPierre Pronchery                      const QUIC_CML_SELECTOR   *selector,
370*e7be843bSPierre Pronchery                      QUIC_CML_PIPE             *pipe_handle);
371*e7be843bSPierre Pronchery
372*e7be843bSPierre Pronchery/*
373*e7be843bSPierre Pronchery * Returns the number of bytes a sending pipe can currently accept. The returned
374*e7be843bSPierre Pronchery * value may increase over time asynchronously but will only decrease in
375*e7be843bSPierre Pronchery * response to an ossl_cml_write call.
376*e7be843bSPierre Pronchery */
377*e7be843bSPierre Proncherysize_t ossl_cml_write_available(QUIC_CML *cml, QUIC_CML_PIPE pipe_handle);
378*e7be843bSPierre Pronchery
379*e7be843bSPierre Pronchery/*
380*e7be843bSPierre Pronchery * Appends bytes into a sending pipe by copying them. The buffer can be freed
381*e7be843bSPierre Pronchery * as soon as this call returns.
382*e7be843bSPierre Pronchery */
383*e7be843bSPierre Proncheryint ossl_cml_write(QUIC_CML *cml, QUIC_CML_PIPE pipe_handle,
384*e7be843bSPierre Pronchery                   const void *buf, size_t buf_len);
385*e7be843bSPierre Pronchery
386*e7be843bSPierre Pronchery/*
387*e7be843bSPierre Pronchery * Returns the number of bytes a receiving pipe currently has waiting to be
388*e7be843bSPierre Pronchery * read. The returned value may increase over time asynchronously but will only
389*e7be843bSPierre Pronchery * decreate in response to an ossl_cml_read call.
390*e7be843bSPierre Pronchery */
391*e7be843bSPierre Proncherysize_t ossl_cml_read_available(QUIC_CML *cml, QUIC_CML_PIPE pipe_handle);
392*e7be843bSPierre Pronchery
393*e7be843bSPierre Pronchery/*
394*e7be843bSPierre Pronchery * Reads bytes from a receiving pipe by copying them.
395*e7be843bSPierre Pronchery */
396*e7be843bSPierre Proncheryint ossl_cml_read(QUIC_CML *cml, QUIC_CML_PIPE pipe_handle,
397*e7be843bSPierre Pronchery                  void *buf, size_t buf_len);
398*e7be843bSPierre Pronchery
399*e7be843bSPierre Pronchery/*
400*e7be843bSPierre Pronchery * Blocks until at least one of the pipes in the array specified by
401*e7be843bSPierre Pronchery * pipe_handles is ready, or until the deadline given is reached.
402*e7be843bSPierre Pronchery *
403*e7be843bSPierre Pronchery * A pipe is ready if:
404*e7be843bSPierre Pronchery *
405*e7be843bSPierre Pronchery *   - it is a sending pipe and one or more bytes can now be written;
406*e7be843bSPierre Pronchery *   - it is a receiving pipe and one or more bytes can now be read.
407*e7be843bSPierre Pronchery */
408*e7be843bSPierre Proncheryint ossl_cml_block_until(QUIC_CML *cml,
409*e7be843bSPierre Pronchery                         const QUIC_CML_PIPE *pipe_handles,
410*e7be843bSPierre Pronchery                         size_t num_pipe_handles,
411*e7be843bSPierre Pronchery                         OSSL_TIME deadline);
412*e7be843bSPierre Pronchery```
413