MEDIUM: cfgparse: do not store unnamed defaults in name tree

Defaults section are indexed by their name in defproxy_by_name tree. For
named sections, there is no duplicate : if two instances have the same
name, the older one is removed from the tree. However, this was not the
case for unnamed defaults which are all stored inconditionnally in
defproxy_by_name.

This commit introduces a new approach for unnamed defaults. Now, these
instances are never inserted in the defproxy_by_name tree. Indeed, this
is not needed as no tree lookup is performed with empty names. This may
optimize slightly config parsing with a huge number of named and unnamed
defaults sections, as the first ones won't fill up the tree needlessly.

However, defproxy_by_name tree is also used to purge unreferenced
defaults instances, both on postparsing and deinit. Thus, a new approach
is needed for unnamed sections cleanup. Now, each time a new defaults is
parsed, if the previous instance is unnamed, it is freed unless if
referenced by a proxy. When config parsing is ended, a similar operation
is performed to ensure the last unnamed defaults section won't stay in
memory. To implement this, last_defproxy static variable is now set to
global. Unnamed sections which cannot be removed due to proxies
referencing proxies will still be removed when such proxies are freed
themselves, at runtime or on deinit.
This commit is contained in:
Amaury Denoyelle 2026-01-21 10:22:23 +01:00
parent 848e0cd052
commit 116983ad94
5 changed files with 52 additions and 28 deletions

View File

@ -111,6 +111,7 @@ extern char *cursection;
extern int non_global_section_parsed;
extern struct proxy *curproxy;
extern struct proxy *last_defproxy;
extern char initial_cwd[PATH_MAX];
int cfg_parse_global(const char *file, int linenum, char **args, int inv);

View File

@ -299,7 +299,6 @@ int cfg_parse_listen_match_option(const char *file, int linenum, int kwm,
int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
{
static struct proxy *curr_defproxy = NULL;
static struct proxy *last_defproxy = NULL;
const char *err;
int rc;
int err_code = 0;
@ -388,35 +387,49 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
err_code |= ERR_ALERT | ERR_FATAL;
}
if (*args[1] && rc & PR_CAP_DEF) {
/* for default proxies, if another one has the same
* name and was explicitly referenced, this is an error
* that we must reject. E.g.
* defaults def
* backend bck from def
* defaults def
if (rc & PR_CAP_DEF) {
/* If last defaults is unnamed, it will be made
* invisible by the current newer section. It must be
* freed unless it is still referenced by proxies.
*/
curproxy = proxy_find_by_name(args[1], PR_CAP_DEF, 0);
if (curproxy && curproxy->flags & PR_FL_EXPLICIT_REF) {
ha_alert("Parsing [%s:%d]: %s '%s' has the same name as another defaults section declared at"
" %s:%d which was explicitly referenced hence cannot be replaced. Please remove or"
" rename one of the offending defaults section.\n",
file, linenum, proxy_cap_str(rc), args[1],
curproxy->conf.file, curproxy->conf.line);
err_code |= ERR_ALERT | ERR_ABORT;
goto out;
if (last_defproxy && last_defproxy->id[0] == '\0' &&
!last_defproxy->conf.refcount) {
defaults_px_destroy(last_defproxy);
}
last_defproxy = NULL;
/* if the other proxy exists, we don't need to keep it
* since neither will support being explicitly referenced
* so let's drop it from the index but keep a reference to
* its location for error messages.
*/
if (curproxy) {
file_prev = curproxy->conf.file;
line_prev = curproxy->conf.line;
defaults_px_detach(curproxy);
curproxy = NULL;
/* If current defaults is named, check collision with previous instances. */
if (*args[1]) {
curproxy = proxy_find_by_name(args[1], PR_CAP_DEF, 0);
/* for default proxies, if another one has the same
* name and was explicitly referenced, this is an error
* that we must reject. E.g.
* defaults def
* backend bck from def
* defaults def
*/
if (curproxy && curproxy->flags & PR_FL_EXPLICIT_REF) {
ha_alert("Parsing [%s:%d]: %s '%s' has the same name as another defaults section declared at"
" %s:%d which was explicitly referenced hence cannot be replaced. Please remove or"
" rename one of the offending defaults section.\n",
file, linenum, proxy_cap_str(rc), args[1],
curproxy->conf.file, curproxy->conf.line);
err_code |= ERR_ALERT | ERR_ABORT;
goto out;
}
/* if the other proxy exists, we don't need to keep it
* since neither will support being explicitly referenced
* so let's drop it from the index but keep a reference to
* its location for error messages.
*/
if (curproxy) {
file_prev = curproxy->conf.file;
line_prev = curproxy->conf.line;
defaults_px_detach(curproxy);
curproxy = NULL;
}
}
}

View File

@ -110,6 +110,8 @@ extern struct proxy *mworker_proxy;
/* curproxy is only valid during parsing and will be NULL afterwards. */
struct proxy *curproxy = NULL;
/* last defaults section parsed, NULL after parsing */
struct proxy *last_defproxy = NULL;
char *cursection = NULL;
int cfg_maxpconn = 0; /* # of simultaneous connections per proxy (-N) */

View File

@ -2098,6 +2098,13 @@ static void step_init_2(int argc, char** argv)
struct pre_check_fct *prcf;
const char *cc, *cflags, *opts;
/* Free last defaults if it is unnamed and unreferenced. */
if (last_defproxy && last_defproxy->id[0] == '\0' &&
!last_defproxy->conf.refcount) {
defaults_px_destroy(last_defproxy);
}
last_defproxy = NULL; /* This variable is not used after parsing. */
/* destroy unreferenced defaults proxies */
defaults_px_destroy_all_unref();

View File

@ -1718,7 +1718,8 @@ int setup_new_proxy(struct proxy *px, const char *name, unsigned int cap, char *
px->cap = cap;
px->last_change = ns_to_sec(now_ns);
if (name && !(cap & PR_CAP_INT))
/* Internal proxies or with empty name are not stored in the named tree. */
if (name && name[0] != '\0' && !(cap & PR_CAP_INT))
proxy_store_name(px);
if (!(cap & PR_CAP_DEF))