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;
--- /dev/null
+/*
+ * 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
$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
--- /dev/null
+/*
+ * 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);
+ }
+}