return n;
}
+gsize rspamd_upstreams_count_total(struct upstream_list *ups)
+{
+ if (ups == NULL) {
+ return 0;
+ }
+
+ /*
+ * Includes SRV parent placeholders. Used by "is anything configured"
+ * gates that run at config-load time, before async SRV resolution has
+ * populated members; a parent-only list must read non-empty here or
+ * the rule would be rejected on every fresh start.
+ */
+ return ups->ups->len;
+}
+
gsize rspamd_upstreams_alive(struct upstream_list *ups)
{
return ups != NULL ? ups->alive->len : 0;
void rspamd_upstreams_destroy(struct upstream_list *ups);
/**
- * Returns count of upstreams in a list
+ * Returns count of upstreams in a list. SRV parent placeholders are excluded
+ * (they are not selectable themselves); use this when sizing the dispatchable
+ * cluster (e.g. weight calculations, output table preallocation).
* @param ups
* @return
*/
gsize rspamd_upstreams_count(struct upstream_list *ups);
+/**
+ * Returns the total count of configured upstreams, including SRV parents that
+ * have not yet been resolved into members. Use this for "is anything configured
+ * at all" checks at config-load time, before async SRV resolution has run.
+ * @param ups
+ * @return
+ */
+gsize rspamd_upstreams_count_total(struct upstream_list *ups);
+
/**
* Returns the number of upstreams in the list
* @param ups
strlen(shingles_key_str), NULL, 0);
rule->shingles_key->len = 16;
- if (rspamd_upstreams_count(rule->read_servers) == 0) {
+ /*
+ * Use the *total* count so a rule whose only entry is an SRV
+ * placeholder (service=...) loads cleanly; rspamd_upstreams_count
+ * would return 0 here because the SRV parent isn't a dispatchable
+ * upstream until async resolution materialises members.
+ */
+ if (rspamd_upstreams_count_total(rule->read_servers) == 0) {
msg_err_config("no servers defined for fuzzy rule with name: %s",
rule->name);
return -1;