Home | History | Annotate | Line # | Download | only in internal
      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)
     90     slots;
     91 };
     92 
     93 /* Initialises a wait context. */
     94 void ossl_quic_reactor_wait_ctx_init(QUIC_REACTOR_WAIT_CTX *ctx);
     95 
     96 /* Uprefs a given reactor. */
     97 int ossl_quic_reactor_wait_ctx_enter(QUIC_REACTOR_WAIT_CTX *ctx,
     98     QUIC_REACTOR *rtor);
     99 
    100 /* Downrefs a given reactor. */
    101 void ossl_quic_reactor_wait_ctx_leave(QUIC_REACTOR_WAIT_CTX *ctx,
    102     QUIC_REACTOR *rtor);
    103 
    104 /*
    105  * Destroys a wait context. Must be called after calling init().
    106  *
    107  * Precondition: All prior calls to ossl_quic_reactor_wait_ctx_enter() must have
    108  * been balanced with corresponding leave() calls before calling this
    109  * (unchecked).
    110  */
    111 void ossl_quic_reactor_wait_ctx_cleanup(QUIC_REACTOR_WAIT_CTX *ctx);
    112 
    113 #endif
    114 
    115 #endif
    116