[PATCH 1 of 2] Upstream: improved reporting of peer failures
Maxim Dounin
mdounin at mdounin.ru
Sun Jan 11 22:58:59 UTC 2026
# HG changeset patch
# User Maxim Dounin <mdounin at mdounin.ru>
# Date 1768085239 -10800
# Sun Jan 11 01:47:19 2026 +0300
# Node ID 59592781f10b2d4f7e63e4dfcf6b865b5ee19254
# Parent d596a1fb8b9c3c293a3eca2d505b1d385481d51c
Upstream: improved reporting of peer failures.
Previously, peer failures were not reported to the balancer if an error
response listed in u->conf->next_upstream was received, but switching
to the next upstream server was not possible due to other reasons (for
example, when no u->peer.tries left, or due to unbuffered request body).
In particular, this resulted in upstream servers never switched off due
to 502 errors in configurations with "grpc_next_upstream http_502", as
gRPC proxying implies unbuffered request body
(https://freenginx.org/pipermail/nginx/2025-February/000150.html).
Fix is to preserve information about the failure as detected by
ngx_http_upstream_test_next(), and propagate it to the balancer when
u->peer.free() is called during upstream request finalization.
diff --git a/src/http/ngx_http_upstream.c b/src/http/ngx_http_upstream.c
--- a/src/http/ngx_http_upstream.c
+++ b/src/http/ngx_http_upstream.c
@@ -2584,24 +2584,43 @@ ngx_http_upstream_test_next(ngx_http_req
continue;
}
- timeout = u->conf->next_upstream_timeout;
-
- if (u->request_sent
- && (r->method & (NGX_HTTP_POST|NGX_HTTP_LOCK|NGX_HTTP_PATCH)))
- {
- mask = un->mask | NGX_HTTP_UPSTREAM_FT_NON_IDEMPOTENT;
-
- } else {
- mask = un->mask;
- }
-
- if (u->peer.tries > 1
- && ((u->conf->next_upstream & mask) == mask)
- && !(u->request_sent && r->request_body_no_buffering)
- && !(timeout && ngx_current_msec - u->peer.start_time >= timeout))
- {
- ngx_http_upstream_next(r, u, un->mask);
- return NGX_OK;
+ if (u->conf->next_upstream & un->mask) {
+
+ timeout = u->conf->next_upstream_timeout;
+
+ if (u->request_sent
+ && (r->method & (NGX_HTTP_POST|NGX_HTTP_LOCK|NGX_HTTP_PATCH)))
+ {
+ mask = un->mask | NGX_HTTP_UPSTREAM_FT_NON_IDEMPOTENT;
+
+ } else {
+ mask = un->mask;
+ }
+
+ if (u->peer.tries > 1
+ && ((u->conf->next_upstream & mask) == mask)
+ && !(u->request_sent && r->request_body_no_buffering)
+ && !(timeout
+ && ngx_current_msec - u->peer.start_time >= timeout))
+ {
+ ngx_http_upstream_next(r, u, un->mask);
+ return NGX_OK;
+ }
+
+ /*
+ * if we were expected to switch to the next server, but
+ * were not able to do so due to additional restrictions,
+ * remember that the peer failed
+ */
+
+ if (un->mask == NGX_HTTP_UPSTREAM_FT_HTTP_403
+ || un->mask == NGX_HTTP_UPSTREAM_FT_HTTP_404)
+ {
+ u->peer_state = NGX_PEER_NEXT;
+
+ } else {
+ u->peer_state = NGX_PEER_FAILED;
+ }
}
#if (NGX_HTTP_CACHE)
@@ -4623,7 +4642,7 @@ ngx_http_upstream_finalize_request(ngx_h
u->finalize_request(r, rc);
if (u->peer.free && u->peer.sockaddr) {
- u->peer.free(&u->peer, u->peer.data, 0);
+ u->peer.free(&u->peer, u->peer.data, u->peer_state);
u->peer.sockaddr = NULL;
}
diff --git a/src/http/ngx_http_upstream.h b/src/http/ngx_http_upstream.h
--- a/src/http/ngx_http_upstream.h
+++ b/src/http/ngx_http_upstream.h
@@ -404,6 +404,8 @@ struct ngx_http_upstream_s {
unsigned request_body_sent:1;
unsigned request_body_blocked:1;
unsigned header_sent:1;
+
+ unsigned peer_state:3;
};
More information about the nginx-devel
mailing list