MEDIUM: stconn/muxes: Add an abort reason for SE shutdowns on muxes

A reason is now passed as parameter to muxes shutdowns to pass additional
info about the abort, if any. No info means no abort or only generic one.

For now, the reason is composed of 2 32-bits integer. The first on represents
the abort code and the other one represents the info about the code (for
instance the source). The code should be interpreted according to the associated
info.

One info is the source, encoding on 5 bits. Other bits are reserverd for now.
For now, the muxes are the only supported source. But we can imagine to extend
it to applets, streams, health-checks...

The current design is quite simple and will most probably evolved.. But the
idea is to let the opposite side forward some errors and let's a mux know
why its stream was aborted. At first glance, a abort reason must only be
evaluated if SE_SHW_SILENT flag is set.

The main goal at short term, is to forward some H2 RST_STREAM codes because
it is mandatory for gRPC applications, mainly to forward gRPC cancellation
from an H2 client to an H2 server. But we can imagine to alter this reason
at the applicative level to enrich it. It would also be used to report more
accurate errors in logs.
This commit is contained in:
Christopher Faulet 2024-04-29 08:12:07 +02:00
parent 28489021b3
commit 96f8b7ad08
8 changed files with 42 additions and 8 deletions

View File

@ -411,7 +411,7 @@ struct mux_ops {
size_t (*done_fastfwd)(struct stconn *sc); /* Callback to terminate fast data forwarding */
int (*fastfwd)(struct stconn *sc, unsigned int count, unsigned int flags); /* Callback to init fast data forwarding */
int (*resume_fastfwd)(struct stconn *sc, unsigned int flags); /* Callback to resume fast data forwarding */
void (*shut)(struct stconn *sc, enum se_shut_mode); /* shutdown function */
void (*shut)(struct stconn *sc, enum se_shut_mode, struct se_abort_info *reason); /* shutdown function */
int (*attach)(struct connection *conn, struct sedesc *, struct session *sess); /* attach a stconn to an outgoing connection */
struct stconn *(*get_first_sc)(const struct connection *); /* retrieves any valid stconn from this connection */

View File

@ -266,6 +266,24 @@ enum sc_state_bit {
struct stconn;
/* represent the abort code, enriched with contextual info:
* - First 5 bits are used for the source (31 possible sources)
* - other bits are reserved for now
*/
#define SE_ABRT_SRC_SHIFT 0
#define SE_ABRT_SRC_MASK 0x0000001f
#define SE_ABRT_SRC_MUX_PT 0x01 /* Code set by the PT mux */
#define SE_ABRT_SRC_MUX_H1 0x02 /* Code set bu the H1 mux */
#define SE_ABRT_SRC_MUX_H2 0x03 /* Code set bu the H2 mux */
#define SE_ABRT_SRC_MUX_QUIC 0x04 /* Code set bu the QUIC/H3 mux */
#define SE_ABRT_SRC_MUX_FCGI 0x05 /* Code set bu the FCGI mux */
struct se_abort_info {
uint32_t info;
uint64_t code;
};
/* A Stream Endpoint Descriptor (sedesc) is the link between the stream
* connector (ex. stconn) and the Stream Endpoint (mux or appctx).
* It always exists for either of them, and binds them together. It also
@ -296,6 +314,7 @@ struct sedesc {
struct stconn *sc; /* the stream connector we're attached to, or NULL */
struct iobuf iobuf; /* contains data forwarded by the other side and that must be sent by the stream endpoint */
unsigned int flags; /* SE_FL_* */
struct se_abort_info abort_info; /* Info about abort, as reported by the endpoint and eventually enriched by the app level */
unsigned int lra; /* the last read activity */
unsigned int fsb; /* the first send blocked */
/* 4 bytes hole here */

View File

@ -3791,7 +3791,7 @@ struct task *fcgi_deferred_shut(struct task *t, void *ctx, unsigned int state)
return NULL;
}
static void fcgi_shut(struct stconn *sc, enum se_shut_mode mode)
static void fcgi_shut(struct stconn *sc, enum se_shut_mode mode, struct se_abort_info *reason)
{
struct fcgi_strm *fstrm = __sc_mux_strm(sc);

View File

@ -4291,7 +4291,7 @@ static void h1_detach(struct sedesc *sd)
TRACE_LEAVE(H1_EV_STRM_END);
}
static void h1_shut(struct stconn *sc, enum se_shut_mode mode)
static void h1_shut(struct stconn *sc, enum se_shut_mode mode, struct se_abort_info *reason)
{
struct h1s *h1s = __sc_mux_strm(sc);
struct h1c *h1c;

View File

@ -5079,7 +5079,7 @@ struct task *h2_deferred_shut(struct task *t, void *ctx, unsigned int state)
return t;
}
static void h2_shut(struct stconn *sc, enum se_shut_mode mode)
static void h2_shut(struct stconn *sc, enum se_shut_mode mode, struct se_abort_info *reason)
{
struct h2s *h2s = __sc_mux_strm(sc);

View File

@ -462,7 +462,7 @@ static int mux_pt_avail_streams(struct connection *conn)
return 1 - mux_pt_used_streams(conn);
}
static void mux_pt_shut(struct stconn *sc, enum se_shut_mode mode)
static void mux_pt_shut(struct stconn *sc, enum se_shut_mode mode, struct se_abort_info *reason)
{
struct connection *conn = __sc_conn(sc);
struct mux_pt_ctx *ctx = conn->ctx;

View File

@ -3095,7 +3095,7 @@ static int qmux_wake(struct connection *conn)
return 1;
}
static void qmux_strm_shut(struct stconn *sc, enum se_shut_mode mode)
static void qmux_strm_shut(struct stconn *sc, enum se_shut_mode mode, struct se_abort_info *reason)
{
struct qcs *qcs = __sc_mux_strm(sc);
struct qcc *qcc = qcs->qcc;

View File

@ -100,6 +100,9 @@ void sedesc_init(struct sedesc *sedesc)
sedesc->xref.peer = NULL;
se_fl_setall(sedesc, SE_FL_NONE);
sedesc->abort_info.info = 0;
sedesc->abort_info.code = 0;
sedesc->iobuf.pipe = NULL;
sedesc->iobuf.buf = NULL;
sedesc->iobuf.offset = sedesc->iobuf.data = 0;
@ -150,8 +153,20 @@ void se_shutdown(struct sedesc *sedesc, enum se_shut_mode mode)
flags |= (mode & SE_SHR_DRAIN) ? SE_FL_SHRD : SE_FL_SHRR;
if (flags) {
if (mux && mux->shut)
mux->shut(sedesc->sc, mode);
if (mux && mux->shut) {
struct se_abort_info *reason = NULL;
struct xref *peer = xref_get_peer_and_lock(&sedesc->xref);
if (peer) {
struct sedesc *sdo = container_of(peer, struct sedesc, xref);
reason = &sdo->abort_info;
xref_unlock(&sedesc->xref, peer);
}
mux->shut(sedesc->sc, mode, reason);
}
se_fl_set(sedesc, flags);
}
}