/* * Copyright 2024-2025 The OpenSSL Project Authors. All Rights Reserved. * * Licensed under the Apache License 2.0 (the "License"). You may not use * this file except in compliance with the License. You can obtain a copy * in the file LICENSE in the source distribution or at * https://www.openssl.org/source/license.html */ #ifndef OSSL_QUIC_REACTOR_WAIT_CTX_H # define OSSL_QUIC_REACTOR_WAIT_CTX_H # include "internal/quic_predef.h" # include "internal/quic_reactor.h" # include "internal/list.h" # ifndef OPENSSL_NO_QUIC /* * QUIC_REACTOR_WAIT_CTX * ===================== * * In order to support inter-thread notification of events which may cause a * blocking call on another thread to be able to make forward progress, we need * to know when a thread enters and exits a blocking call. The details of why * this is involves subtleties of inter-thread synchronisation and a detailed * discussion can be found in the source of * ossl_quic_reactor_block_until_pred(). * * The core mechanism for such tracking is * ossl_quic_reactor_(enter/leave)_blocking_section(), however this API does not * support recursive usage to keep the internal implementation simple. In some * cases, an API which can be used in a recursive fashion (with multiple * balanced calls to enter()/leave() on a single thread) is more convenient. * * This utility allows multiple blocking operations to be registered on a given * thread. Moreover, it allows multiple blocking operations to be registered * across an arbitrarily large number of QUIC_REACTORs from a given thread. * * In short, this allows multiple 'concurrent' blocking calls to be ongoing on a * given thread for a given reactor. While on the face of it the notion of * multiple concurrent blocking calls on a single thread makes no sense, the * implementation of our immediate-mode polling implementation (SSL_poll) makes * it natural for us to implement it simply by registering a blocking call per * SSL object passed in. Since multiple SSL objects may be passed to an SSL_poll * call, and some SSL objects may correspond to the same reactor, and other SSL * objects may correspond to a different reactor, we need to be able to * determine when a SSL_poll() call has finished with all of the SSL objects * *corresponding to a given reactor*. * * Doing this requires some ephemeral state tracking as a SSL_poll() call may * map to an arbitrarily large set of reactor objects. For now, we track this * separately from the reactor code as the state needed is only ephemeral and * this keeps the reactor internals simple. * * The concept used is that a thread allocates (on the stack) a * QUIC_REACTOR_WAIT_CTX before commencing a blocking operation, and then calls * ossl_quic_reactor_wait_ctx_enter() whenever encountering a reactor involved * in the imminent blocking operation. Later it must ensure it calls * ossl_quic_reactor_wait_ctx_leave() the same number of times for each reactor. * enter() and leave() may be called multiple times for the same reactor and * wait context so long as the number of calls is balanced. The last leave() * call for a given thread's wait context *and a given reactor* causes that * reactor to do the inter-thread notification housekeeping needed for * multithreaded blocking to work correctly. * * The gist is that a simple reactor-level counter of active concurrent blocking * calls across all threads is not accurate and we need an accurate count of how * many 'concurrent' blocking calls for a given reactor are active *on a given * thread* in order to avoid deadlocks. Conceptually, you can think of this as * refcounting a refcount (which is actually how it is implemented). * * Logically, a wait context is a map from a reactor pointer (i.e., a unique * identifier for the reactor) to the number of 'recursive' calls outstanding: * * (QUIC_REACTOR *) -> (outstanding call count) * * When the count for a reactor transitions from 0 to a nonzero value, or vice * versa, ossl_quic_reactor_(enter/leave)_blocking_section() is called once. * * The internal implementation is based on linked lists as we expect the actual * number of reactors involved in a given blocking operation to be very small, * so spinning up a hashtable is not worthwhile. */ typedef struct quic_reactor_wait_slot_st QUIC_REACTOR_WAIT_SLOT; DECLARE_LIST_OF(quic_reactor_wait_slot, QUIC_REACTOR_WAIT_SLOT); struct quic_reactor_wait_ctx_st { OSSL_LIST(quic_reactor_wait_slot) slots; }; /* Initialises a wait context. */ void ossl_quic_reactor_wait_ctx_init(QUIC_REACTOR_WAIT_CTX *ctx); /* Uprefs a given reactor. */ int ossl_quic_reactor_wait_ctx_enter(QUIC_REACTOR_WAIT_CTX *ctx, QUIC_REACTOR *rtor); /* Downrefs a given reactor. */ void ossl_quic_reactor_wait_ctx_leave(QUIC_REACTOR_WAIT_CTX *ctx, QUIC_REACTOR *rtor); /* * Destroys a wait context. Must be called after calling init(). * * Precondition: All prior calls to ossl_quic_reactor_wait_ctx_enter() must have * been balanced with corresponding leave() calls before calling this * (unchecked). */ void ossl_quic_reactor_wait_ctx_cleanup(QUIC_REACTOR_WAIT_CTX *ctx); # endif #endif