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