From cd66a94b634532a21d30adab0eae3f657848f85e Mon Sep 17 00:00:00 2001 From: Olivier Houchard Date: Thu, 12 Feb 2026 10:30:53 +0100 Subject: [PATCH] MEDIUM: servers: Introduce server_full To detect that a server is full, its served variable is used. That variable, however, is in a very busy cache line, and it can be costly to access it. So instead, introduce a new variable, in a different cache line, server_full, that indicates if the server is full. --- include/haproxy/server-t.h | 1 + include/haproxy/server.h | 19 ++++++++-- include/haproxy/stream.h | 8 +++-- src/backend.c | 71 +++++++++++++++++++++++++++++--------- src/cli.c | 9 +++-- src/stream.c | 25 +++++++++----- 6 files changed, 103 insertions(+), 30 deletions(-) diff --git a/include/haproxy/server-t.h b/include/haproxy/server-t.h index eaede6890..b08a7a807 100644 --- a/include/haproxy/server-t.h +++ b/include/haproxy/server-t.h @@ -554,6 +554,7 @@ struct server { EXTRA_COUNTERS(extra_counters); __decl_thread(HA_SPINLOCK_T state_lock);/* protect the following state fields */ uint8_t queues_not_empty; /* Are the request queues not empty ? Only changed when the queues go from non-empty to empty, and vice-versa. Protected by the state_lock lock when changed */ + uint8_t server_full; /* we reached maxconn, and can no longer process more requests, protected by the state_lock */ }; /* data provided to EVENT_HDL_SUB_SERVER handlers through event_hdl facility */ diff --git a/include/haproxy/server.h b/include/haproxy/server.h index abb01cf8d..43ae8b8f9 100644 --- a/include/haproxy/server.h +++ b/include/haproxy/server.h @@ -398,12 +398,27 @@ static inline int srv_manage_queues(struct server *srv, struct proxy *px) { int full = -1; - if (may_dequeue_tasks(srv, px)) - full = process_srv_queue(srv, &full); + if (may_dequeue_tasks(srv, px)) { + process_srv_queue(srv, &full); + if ((full && !srv->server_full) || (!full && srv->server_full)) { + HA_SPIN_LOCK(SERVER_LOCK, &srv->state_lock); + srv->server_full = (srv->served >= srv->maxconn); + HA_SPIN_UNLOCK(SERVER_LOCK, &srv->state_lock); + } + } return full; } +static inline void srv_check_full_state(struct server *srv, int served) +{ + if (srv->maxconn > 0 && served == srv->maxconn - 1) { + HA_SPIN_LOCK(SERVER_LOCK, &srv->state_lock); + srv->server_full = (srv->served >= srv->maxconn); + HA_SPIN_UNLOCK(SERVER_LOCK, &srv->state_lock); + } +} + #endif /* _HAPROXY_SERVER_H */ /* diff --git a/include/haproxy/stream.h b/include/haproxy/stream.h index b25f77c68..e742b90bd 100644 --- a/include/haproxy/stream.h +++ b/include/haproxy/stream.h @@ -353,8 +353,12 @@ static inline void stream_choose_redispatch(struct stream *s) (s->conn_retries % (s->max_retries + 1 + s->be->redispatch_after) == 0))) || (!(s->flags & SF_DIRECT) && s->be->srv_act > 1 && ((s->be->lbprm.algo & BE_LB_KIND) != BE_LB_KIND_HI)))) { - sess_change_server(s, NULL); - srv_manage_queues(objt_server(s->target), s->be); + struct server *srv = __objt_server(s->target); + int served; + + served = sess_change_server(s, NULL); + if (srv_manage_queues(srv, s->be) == -1) + srv_check_full_state(srv, served); sockaddr_free(&s->scb->dst); s->flags &= ~(SF_DIRECT | SF_ASSIGNED); diff --git a/src/backend.c b/src/backend.c index 22413cafc..5ff273272 100644 --- a/src/backend.c +++ b/src/backend.c @@ -645,8 +645,10 @@ int assign_server(struct stream *s) /* We have to release any connection slot before applying any LB algo, * otherwise we may erroneously end up with no available slot. */ - if (conn_slot) - sess_change_server(s, NULL); + if (conn_slot) { + int served = sess_change_server(s, NULL); + srv_check_full_state(conn_slot, served); + } /* We will now try to find the good server and store it into target)>. * Note that target)> may be NULL in case of dispatch or proxy mode, @@ -858,7 +860,8 @@ out_ok: */ if (conn_slot) { if (conn_slot == srv) { - sess_change_server(s, srv); + int served = sess_change_server(s, srv); + srv_check_full_state(srv, served); } else { srv_manage_queues(conn_slot, s->be); } @@ -982,7 +985,7 @@ static int alloc_dst_address(struct sockaddr_storage **ss, int assign_server_and_queue(struct stream *s) { struct pendconn *p; - struct server *srv; + struct server *srv, *oldsrv; int count; int err; @@ -1039,10 +1042,16 @@ int assign_server_and_queue(struct stream *s) * assigned from persistence information (direct mode). */ if ((s->flags & SF_REDIRECTABLE) && srv->rdr_len) { + struct server *oldsrv; + + int served; /* server scheduled for redirection, and already assigned. We * don't want to go further nor check the queue. */ - sess_change_server(s, srv); /* not really needed in fact */ + oldsrv = s->srv_conn; + served = sess_change_server(s, srv); /* not really needed in fact */ + if (oldsrv) + srv_check_full_state(oldsrv, served); return SRV_STATUS_OK; } @@ -1095,12 +1104,21 @@ int assign_server_and_queue(struct stream *s) else return SRV_STATUS_INTERNAL; } + if (served + 1 == srv->maxconn) { + HA_SPIN_LOCK(SERVER_LOCK, &srv->state_lock); + srv->server_full = (srv->served >= srv->maxconn); + HA_SPIN_UNLOCK(SERVER_LOCK, &srv->state_lock); + } } else count = _HA_ATOMIC_ADD_FETCH(&srv->served, 1); HA_ATOMIC_UPDATE_MAX(&srv->counters.cur_sess_max, count); /* OK, we can use this server. Let's reserve our place */ - sess_change_server(s, srv); + oldsrv = s->srv_conn; + + count = sess_change_server(s, srv); + if (oldsrv != NULL) + srv_check_full_state(oldsrv, count); return SRV_STATUS_OK; case SRV_STATUS_FULL: @@ -1123,15 +1141,15 @@ int assign_server_and_queue(struct stream *s) * dequeue us eventually, so we can just do nothing. */ if (unlikely(s->be->ready_srv != NULL)) { - struct server *newserv; + struct server *newserv, *oldserv; + int served; newserv = HA_ATOMIC_XCHG(&s->be->ready_srv, NULL); if (newserv != NULL) { int got_slot = 0; while (_HA_ATOMIC_LOAD(&newserv->served) == 0) { - int served = 0; - + served = 0; if (_HA_ATOMIC_CAS(&newserv->served, &served, 1)) { got_slot = 1; break; @@ -1145,6 +1163,12 @@ int assign_server_and_queue(struct stream *s) return SRV_STATUS_QUEUED; } + if (served + 1 == newserv->maxconn) { + HA_SPIN_LOCK(SERVER_LOCK, &newserv->state_lock); + newserv->server_full = (newserv->served >= newserv->maxconn); + HA_SPIN_UNLOCK(SERVER_LOCK, &newserv->state_lock); + } + HA_SPIN_LOCK(QUEUE_LOCK, &p->queue->lock); if (!p->node.node.leaf_p) { HA_SPIN_UNLOCK(QUEUE_LOCK, &p->queue->lock); @@ -1185,7 +1209,11 @@ int assign_server_and_queue(struct stream *s) stream_set_srv_target(s, newserv); s->pend_pos = NULL; - sess_change_server(s, newserv); + oldserv = s->srv_conn; + + served = sess_change_server(s, newserv); + if (oldserv) + srv_check_full_state(oldserv, served); return SRV_STATUS_OK; } } @@ -2481,6 +2509,8 @@ void back_try_conn_req(struct stream *s) * abort, retry immediately or redispatch. */ if (conn_err == SF_ERR_INTERNAL) { + int served; + if (!s->conn_err_type) { s->conn_err_type = STRM_ET_CONN_OTHER; } @@ -2495,8 +2525,9 @@ void back_try_conn_req(struct stream *s) _HA_ATOMIC_INC(&s->be_tgcounters->failed_conns); /* release other streams waiting for this server */ - sess_change_server(s, NULL); - srv_manage_queues(srv, s->be); + served = sess_change_server(s, NULL); + if (srv && srv_manage_queues(srv, s->be) == -1) + srv_check_full_state(srv, served); /* Failed and not retryable. */ sc_abort(sc); @@ -2804,6 +2835,9 @@ void back_handle_st_cer(struct stream *s) /* ensure that we have enough retries left */ if (s->conn_retries >= s->max_retries || !(s->be->retry_type & PR_RE_CONN_FAILED)) { + struct server *srv = objt_server(s->target); + int served; + if (!s->conn_err_type) { s->conn_err_type = STRM_ET_CONN_ERR; } @@ -2812,8 +2846,9 @@ void back_handle_st_cer(struct stream *s) _HA_ATOMIC_INC(&s->sv_tgcounters->failed_conns); if (s->be_tgcounters) _HA_ATOMIC_INC(&s->be_tgcounters->failed_conns); - sess_change_server(s, NULL); - srv_manage_queues(objt_server(s->target), s->be); + served = sess_change_server(s, NULL); + if (srv && srv_manage_queues(srv, s->be) == -1) + srv_check_full_state(srv, served); /* shutw is enough to stop a connecting socket */ sc_shutdown(sc); @@ -2838,6 +2873,9 @@ void back_handle_st_cer(struct stream *s) * ST_TAR and SC_FL_ERROR and SF_CONN_EXP flags will be unset. */ if (sc_reset_endp(sc) < 0) { + struct server *srv = objt_server(s->target); + int served; + if (!s->conn_err_type) s->conn_err_type = STRM_ET_CONN_OTHER; @@ -2845,8 +2883,9 @@ void back_handle_st_cer(struct stream *s) _HA_ATOMIC_INC(&s->sv_tgcounters->internal_errors); if (s->be_tgcounters) _HA_ATOMIC_INC(&s->be_tgcounters->internal_errors); - sess_change_server(s, NULL); - srv_manage_queues(objt_server(s->target), s->be); + served = sess_change_server(s, NULL); + if (srv && srv_manage_queues(srv, s->be) == -1) + srv_check_full_state(srv, served); /* shutw is enough to stop a connecting socket */ sc_shutdown(sc); diff --git a/src/cli.c b/src/cli.c index 1958de060..9eac10c18 100644 --- a/src/cli.c +++ b/src/cli.c @@ -3485,8 +3485,13 @@ int pcli_wait_for_response(struct stream *s, struct channel *rep, int an_bit) if (s->flags & SF_BE_ASSIGNED) { HA_ATOMIC_DEC(&be->beconn); - if (unlikely(s->srv_conn)) - sess_change_server(s, NULL); + if (unlikely(s->srv_conn)) { + struct server *srv = s->srv_conn; + int served; + + served = sess_change_server(s, NULL); + srv_check_full_state(srv, served); + } } s->logs.t_close = ns_to_ms(now_ns - s->logs.accept_ts); diff --git a/src/stream.c b/src/stream.c index 6f566794a..83e07673d 100644 --- a/src/stream.c +++ b/src/stream.c @@ -641,8 +641,9 @@ void stream_free(struct stream *s) * free the connection before. */ if (!(oldsrv->flags & SRV_F_STRICT_MAXCONN)) { - sess_change_server(s, NULL); - srv_manage_queues(oldsrv, s->be); + int served = sess_change_server(s, NULL); + if (srv_manage_queues(oldsrv, s->be) == -1) + srv_check_full_state(oldsrv, served); } } @@ -751,8 +752,9 @@ void stream_free(struct stream *s) struct server *oldsrv = s->srv_conn; if ((oldsrv->flags & SRV_F_STRICT_MAXCONN)) { - sess_change_server(s, NULL); - srv_manage_queues(oldsrv, s->be); + int served = sess_change_server(s, NULL); + if (srv_manage_queues(oldsrv, s->be) == -1) + srv_check_full_state(oldsrv, served); } } @@ -2052,8 +2054,11 @@ struct task *process_stream(struct task *t, void *context, unsigned int state) * free the connection before. */ if (!(srv->flags & SRV_F_STRICT_MAXCONN)) { - sess_change_server(s, NULL); - srv_manage_queues(srv, s->be); + int served; + + served = sess_change_server(s, NULL); + if (srv_manage_queues(srv, s->be) == -1) + srv_check_full_state(srv, served); } } @@ -2969,8 +2974,12 @@ void stream_shutdown_self(struct stream *stream, int why) stream->flags |= why; if (objt_server(stream->target)) { - sess_change_server(stream, NULL); - srv_manage_queues(__objt_server(stream->target), stream->be); + struct server *srv = __objt_server(stream->target); + int served; + + served = sess_change_server(stream, NULL); + if (srv_manage_queues(srv, stream->be) == -1) + srv_check_full_state(srv, served); } /* shutw is enough to stop a connecting socket */