]> git.ipfire.org Git - thirdparty/openssl.git/commitdiff
QUIC REACTOR: Add utility for tracking recursive blocking operations
authorHugo Landau <hlandau@openssl.org>
Mon, 13 May 2024 19:20:23 +0000 (20:20 +0100)
committerNeil Horman <nhorman@openssl.org>
Mon, 17 Feb 2025 16:27:33 +0000 (11:27 -0500)
Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Saša Nedvědický <sashan@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/25416)

include/internal/quic_predef.h
include/internal/quic_reactor_wait_ctx.h [new file with mode: 0644]
ssl/quic/build.info
ssl/quic/quic_reactor_wait_ctx.c [new file with mode: 0644]

index 06fad8cbd4a1867c67910d1700501c083200c6d1..509bf66de4ea05d7d711b335f904a125ff917375 100644 (file)
@@ -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 (file)
index 0000000..62df11b
--- /dev/null
@@ -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
index 0ac9187724904159fb9df67938560625c5b5bffb..43a2c9479a91a9b2f44e77901bc7080ccf35f75c 100644 (file)
@@ -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 (file)
index 0000000..f3215f9
--- /dev/null
@@ -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 <assert.h>
+
+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);
+    }
+}