From 6a94b7419eea9af5516c3fc80ea0ed705f4e0aea Mon Sep 17 00:00:00 2001 From: Christopher Faulet Date: Wed, 25 Sep 2024 15:10:08 +0200 Subject: [PATCH] MINOR: stream: Support dynamic changes of the number of connection retries Thanks to the previous patch, it is now possible to add an action to dynamically change the maxumum number of connection retires for a stream. "set-retries" action may now be used to do so, from a "tcp-request content" or a "http-request" rule. This action accepts an expression or an integer between 0 and 100. The integer value is checked during the configuration parsing and leads to an error if it is not in the expected range. However, for the expression, the value is retrieve at runtime. So, invalid value are just ignored. Too high value is forbidden to avoid any trouble. 100 retries seems already be an amazingly hight value. In addition, the option is only available on backend or listen sections. Because the max retries is limited to 100 at most, it can be stored as a unsigned short. This save some space in the stream structure. --- doc/configuration.txt | 17 ++++++++ include/haproxy/stream-t.h | 3 +- src/stream.c | 88 +++++++++++++++++++++++++++++++++++++- 3 files changed, 104 insertions(+), 4 deletions(-) diff --git a/doc/configuration.txt b/doc/configuration.txt index 8799e945a..699796a99 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -14595,6 +14595,7 @@ set-priority-class - - - X - X - set-priority-offset - - - X - X - - --keyword---------------QUIC--Ini---TCP--RqCon-RqSes-RqCnt-RsCnt---HTTP--Req-Res-Aft- set-query - - - - - X - - +set-retries - - - X - X - - set-src - X X X - X - - set-src-port - X X X - X - - set-status - - - - - - X X @@ -15772,6 +15773,22 @@ set-query http-request set-query %[query,regsub(%3D,=,g)] +set-retries | + Usable in: QUIC Ini| TCP RqCon| RqSes| RqCnt| RsCnt| HTTP Req| Res| Aft + - | - | - | X | - | X | - | - + + This action overrides the specified "retries" value for the current stream + only. It can be an integer value, in the range [0, 100], or an expression + which must return a integer in the range [0, 100]. + + Note that this action is only relevant on the backend side and thus this rule + is only available for the proxies with backend capability. It is also not + allowed in "defaults" sections. + + Example: + tcp-request content set-retries 3 + http-request set-retries var(txn.retries) + set-src Usable in: QUIC Ini| TCP RqCon| RqSes| RqCnt| RsCnt| HTTP Req| Res| Aft - | X | X | X | - | X | - | - diff --git a/include/haproxy/stream-t.h b/include/haproxy/stream-t.h index 43353c367..ad79f5dec 100644 --- a/include/haproxy/stream-t.h +++ b/include/haproxy/stream-t.h @@ -234,7 +234,6 @@ struct stream { * This is a bit field of TASK_WOKEN_* */ int conn_retries; /* number of connect retries performed */ unsigned int conn_exp; /* wake up time for connect, queue, turn-around, ... */ - unsigned int max_retries; /* Maximum number of connection retried (=0 is backend is not set) */ unsigned int conn_err_type; /* first error detected, one of STRM_ET_* */ struct stream *parent; /* Pointer to the parent stream, if any. NULL most of time */ @@ -248,8 +247,8 @@ struct stream { uint64_t cpu_time; /* total CPU time consumed */ struct freq_ctr call_rate; /* stream task call rate without making progress */ + unsigned short max_retries; /* Maximum number of connection retried (=0 is backend is not set) */ short store_count; - /* 2 unused bytes here */ struct { struct stksess *ts; diff --git a/src/stream.c b/src/stream.c index d0623a9ad..e151b2c95 100644 --- a/src/stream.c +++ b/src/stream.c @@ -430,7 +430,8 @@ struct stream *stream_new(struct session *sess, struct stconn *sc, struct buffer s->task = t; s->pending_events = 0; - s->conn_retries = s->max_retries = 0; + s->conn_retries = 0; + s->max_retries = 0; s->conn_exp = TICK_ETERNITY; s->conn_err_type = STRM_ET_NONE; s->prev_conn_state = SC_ST_INI; @@ -1123,7 +1124,7 @@ static int process_switching_rules(struct stream *s, struct channel *req, int an } - /* Se the max connection retries for the stream. */ + /* Se the max connection retries for the stream. may be overwriten later */ s->max_retries = s->be->conn_retries; /* we don't want to run the TCP or HTTP filters again if the backend has not changed */ @@ -2893,6 +2894,87 @@ struct ist stream_generate_unique_id(struct stream *strm, struct lf_expr *format /************************************************************************/ /* All supported ACL keywords must be declared here. */ /************************************************************************/ +static enum act_return stream_action_set_retries(struct act_rule *rule, struct proxy *px, + struct session *sess, struct stream *s, int flags) +{ + struct sample *smp; + + if (!rule->arg.expr_int.expr) + s->max_retries = rule->arg.expr_int.value; + else { + smp = sample_fetch_as_type(px, sess, s, SMP_OPT_DIR_REQ|SMP_OPT_FINAL, rule->arg.expr_int.expr, SMP_T_SINT); + if (!smp || smp->data.u.sint < 0 || smp->data.u.sint > 100) + goto end; + s->max_retries = smp->data.u.sint; + } + + end: + return ACT_RET_CONT; +} + + +/* Parse a "set-retries" action. It takes the level value as argument. It + * returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error. + */ +static enum act_parse_ret stream_parse_set_retries(const char **args, int *cur_arg, struct proxy *px, + struct act_rule *rule, char **err) +{ + struct sample_expr *expr; + char *endp; + unsigned int where; + + if (!*args[*cur_arg]) { + bad_retries: + memprintf(err, "expects exactly 1 argument (an expression or an integer between 1 and 100)"); + return ACT_RET_PRS_ERR; + } + if (!(px->cap & PR_CAP_BE)) { + memprintf(err, "'%s' only available in backend or listen section", args[0]); + return ACT_RET_PRS_ERR; + } + if (px->cap & PR_CAP_DEF) { + memprintf(err, "'%s' is not allowed in 'defaults' sections", args[0]); + return ACT_RET_PRS_ERR; + } + + /* value may be either an unsigned integer or an expression */ + rule->arg.expr_int.expr = NULL; + rule->arg.expr_int.value = strtol(args[*cur_arg], &endp, 0); + if (*endp == '\0') { + if (rule->arg.expr_int.value < 0 || rule->arg.expr_int.value > 100) { + memprintf(err, "expects an expression or an integer between 1 and 100"); + return ACT_RET_PRS_ERR; + } + /* valid unsigned integer */ + (*cur_arg)++; + } + else { /* invalid unsigned integer, fallback to expr */ + expr = sample_parse_expr((char **)args, cur_arg, px->conf.args.file, px->conf.args.line, err, &px->conf.args, NULL); + if (!expr) + return ACT_RET_PRS_ERR; + where = 0; + if (px->cap & PR_CAP_FE) + where |= SMP_VAL_FE_HRQ_HDR; + if (px->cap & PR_CAP_BE) + where |= SMP_VAL_BE_HRQ_HDR; + + if (!(expr->fetch->val & where)) { + memprintf(err, + "fetch method '%s' extracts information from '%s', none of which is available here", + args[*cur_arg-1], sample_src_names(expr->fetch->use)); + free(expr); + return ACT_RET_PRS_ERR; + } + rule->arg.expr_int.expr = expr; + } + + /* Register processing function. */ + rule->action = ACT_CUSTOM; + rule->action_ptr = stream_action_set_retries; + rule->release_ptr = release_expr_int_action; + return ACT_RET_PRS_OK; +} + static enum act_return stream_action_set_log_level(struct act_rule *rule, struct proxy *px, struct session *sess, struct stream *s, int flags) { @@ -3907,6 +3989,7 @@ INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws); /* main configuration keyword registration. */ static struct action_kw_list stream_tcp_req_keywords = { ILH, { + { "set-retries", stream_parse_set_retries }, { "set-log-level", stream_parse_set_log_level }, { "set-nice", stream_parse_set_nice }, { "switch-mode", stream_parse_switch_mode }, @@ -3926,6 +4009,7 @@ static struct action_kw_list stream_tcp_res_keywords = { ILH, { INITCALL1(STG_REGISTER, tcp_res_cont_keywords_register, &stream_tcp_res_keywords); static struct action_kw_list stream_http_req_keywords = { ILH, { + { "set-retries", stream_parse_set_retries }, { "set-log-level", stream_parse_set_log_level }, { "set-nice", stream_parse_set_nice }, { "use-service", stream_parse_use_service },