From: Hugo Landau Date: Mon, 13 May 2024 19:20:23 +0000 (+0100) Subject: QUIC REACTOR: Add utility for tracking recursive blocking operations X-Git-Tag: openssl-3.5.0-alpha1~350 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=7f2adb82b1ec465fd4a754fc5a04f368a7e8bbd2;p=thirdparty%2Fopenssl.git QUIC REACTOR: Add utility for tracking recursive blocking operations Reviewed-by: Matt Caswell Reviewed-by: Saša Nedvědický Reviewed-by: Tomas Mraz (Merged from https://github.com/openssl/openssl/pull/25416) --- diff --git a/include/internal/quic_predef.h b/include/internal/quic_predef.h index 06fad8cbd4a..509bf66de4e 100644 --- a/include/internal/quic_predef.h +++ b/include/internal/quic_predef.h @@ -27,6 +27,7 @@ typedef struct quic_stream_st QUIC_STREAM; typedef struct quic_sstream_st QUIC_SSTREAM; typedef struct quic_rstream_st QUIC_RSTREAM; typedef struct quic_reactor_st QUIC_REACTOR; +typedef struct quic_reactor_wait_ctx_st QUIC_REACTOR_WAIT_CTX; typedef struct ossl_statm_st OSSL_STATM; typedef struct quic_demux_st QUIC_DEMUX; typedef struct ossl_qrx_pkt_st OSSL_QRX_PKT; diff --git a/include/internal/quic_reactor_wait_ctx.h b/include/internal/quic_reactor_wait_ctx.h new file mode 100644 index 00000000000..62df11be9dd --- /dev/null +++ b/include/internal/quic_reactor_wait_ctx.h @@ -0,0 +1,114 @@ +/* + * Copyright 2024 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 toa 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 diff --git a/ssl/quic/build.info b/ssl/quic/build.info index 0ac91877249..43a2c9479a9 100644 --- a/ssl/quic/build.info +++ b/ssl/quic/build.info @@ -1,29 +1,26 @@ $LIBSSL=../../libssl -#QUIC TLS API is available even in the event of no-quic -SOURCE[$LIBSSL]=quic_tls.c quic_tls_api.c - -IF[{- !$disabled{quic} -}] - SOURCE[$LIBSSL]=quic_method.c quic_impl.c quic_wire.c quic_ackm.c quic_statm.c - SOURCE[$LIBSSL]=cc_newreno.c quic_demux.c quic_record_rx.c - SOURCE[$LIBSSL]=quic_record_tx.c quic_record_util.c quic_record_shared.c quic_wire_pkt.c - SOURCE[$LIBSSL]=quic_rx_depack.c - SOURCE[$LIBSSL]=quic_fc.c uint_set.c - SOURCE[$LIBSSL]=quic_cfq.c quic_txpim.c quic_fifd.c quic_txp.c - SOURCE[$LIBSSL]=quic_stream_map.c - SOURCE[$LIBSSL]=quic_sf_list.c quic_rstream.c quic_sstream.c - SOURCE[$LIBSSL]=quic_reactor.c - SOURCE[$LIBSSL]=quic_channel.c quic_port.c quic_engine.c - SOURCE[$LIBSSL]=quic_tserver.c - SOURCE[$LIBSSL]=quic_thread_assist.c - SOURCE[$LIBSSL]=quic_trace.c - SOURCE[$LIBSSL]=quic_srtm.c quic_srt_gen.c - SOURCE[$LIBSSL]=quic_lcidm.c quic_rcidm.c - SOURCE[$LIBSSL]=quic_types.c - SOURCE[$LIBSSL]=qlog_event_helpers.c - IF[{- !$disabled{qlog} -}] - SOURCE[$LIBSSL]=json_enc.c qlog.c - SHARED_SOURCE[$LIBSSL]=../../crypto/getenv.c ../../crypto/ctype.c - ENDIF +SOURCE[$LIBSSL]=quic_method.c quic_impl.c quic_wire.c quic_ackm.c quic_statm.c +SOURCE[$LIBSSL]=cc_newreno.c quic_demux.c quic_record_rx.c +SOURCE[$LIBSSL]=quic_record_tx.c quic_record_util.c quic_record_shared.c quic_wire_pkt.c +SOURCE[$LIBSSL]=quic_rx_depack.c +SOURCE[$LIBSSL]=quic_fc.c uint_set.c +SOURCE[$LIBSSL]=quic_cfq.c quic_txpim.c quic_fifd.c quic_txp.c +SOURCE[$LIBSSL]=quic_stream_map.c +SOURCE[$LIBSSL]=quic_sf_list.c quic_rstream.c quic_sstream.c +SOURCE[$LIBSSL]=quic_reactor.c +SOURCE[$LIBSSL]=quic_reactor_wait_ctx.c +SOURCE[$LIBSSL]=quic_channel.c quic_port.c quic_engine.c +SOURCE[$LIBSSL]=quic_tserver.c +SOURCE[$LIBSSL]=quic_tls.c +SOURCE[$LIBSSL]=quic_thread_assist.c +SOURCE[$LIBSSL]=quic_trace.c +SOURCE[$LIBSSL]=quic_srtm.c quic_srt_gen.c +SOURCE[$LIBSSL]=quic_lcidm.c quic_rcidm.c +SOURCE[$LIBSSL]=quic_types.c +SOURCE[$LIBSSL]=qlog_event_helpers.c +IF[{- !$disabled{qlog} -}] + SOURCE[$LIBSSL]=json_enc.c qlog.c + SHARED_SOURCE[$LIBSSL]=../../crypto/getenv.c ../../crypto/ctype.c ENDIF SOURCE[$LIBSSL]=quic_obj.c diff --git a/ssl/quic/quic_reactor_wait_ctx.c b/ssl/quic/quic_reactor_wait_ctx.c new file mode 100644 index 00000000000..f3215f9700b --- /dev/null +++ b/ssl/quic/quic_reactor_wait_ctx.c @@ -0,0 +1,85 @@ +/* + * Copyright 2024 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 + */ +#include "internal/quic_reactor_wait_ctx.h" +#include "internal/common.h" +#include "internal/thread_arch.h" +#include + +struct quic_reactor_wait_slot_st { + OSSL_LIST_MEMBER(quic_reactor_wait_slot, QUIC_REACTOR_WAIT_SLOT); + QUIC_REACTOR *rtor; /* primary key */ + size_t blocking_count; /* datum */ +}; + +DEFINE_LIST_OF_IMPL(quic_reactor_wait_slot, QUIC_REACTOR_WAIT_SLOT); + +void ossl_quic_reactor_wait_ctx_init(QUIC_REACTOR_WAIT_CTX *ctx) +{ + ossl_list_quic_reactor_wait_slot_init(&ctx->slots); +} + +static void slot_activate(QUIC_REACTOR_WAIT_SLOT *slot) +{ + if (++slot->blocking_count == 1) + ossl_quic_reactor_enter_blocking_section(slot->rtor); +} + +static void slot_deactivate(QUIC_REACTOR_WAIT_SLOT *slot) +{ + assert(slot->blocking_count > 0); + + if (--slot->blocking_count > 0) + return; + + ossl_quic_reactor_leave_blocking_section(slot->rtor); +} + +int ossl_quic_reactor_wait_ctx_enter(QUIC_REACTOR_WAIT_CTX *ctx, + QUIC_REACTOR *rtor) +{ + QUIC_REACTOR_WAIT_SLOT *slot; + + OSSL_LIST_FOREACH(slot, quic_reactor_wait_slot, &ctx->slots) + if (slot->rtor == rtor) + break; + + if (slot == NULL) { + if ((slot = OPENSSL_zalloc(sizeof(QUIC_REACTOR_WAIT_SLOT))) == NULL) + return 0; + + slot->rtor = rtor; + ossl_list_quic_reactor_wait_slot_insert_tail(&ctx->slots, slot); + } + + slot_activate(slot); + return 1; +} + +void ossl_quic_reactor_wait_ctx_leave(QUIC_REACTOR_WAIT_CTX *ctx, + QUIC_REACTOR *rtor) +{ + QUIC_REACTOR_WAIT_SLOT *slot; + + OSSL_LIST_FOREACH(slot, quic_reactor_wait_slot, &ctx->slots) + if (slot->rtor == rtor) + break; + + assert(slot != NULL); + slot_deactivate(slot); +} + +void ossl_quic_reactor_wait_ctx_cleanup(QUIC_REACTOR_WAIT_CTX *ctx) +{ + QUIC_REACTOR_WAIT_SLOT *slot, *nslot; + + OSSL_LIST_FOREACH_DELSAFE(slot, nslot, quic_reactor_wait_slot, &ctx->slots) { + assert(slot->blocking_count == 0); + OPENSSL_free(slot); + } +}