Coverage Report

Created: 2025-07-04 06:21

/src/haproxy/src/lb_fas.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * First Available Server load balancing algorithm.
3
 *
4
 * This file implements an algorithm which emerged during a discussion with
5
 * Steen Larsen, initially inspired from Anshul Gandhi et.al.'s work now
6
 * described as "packing" in section 3.5:
7
 *
8
 *    http://reports-archive.adm.cs.cmu.edu/anon/2012/CMU-CS-12-109.pdf
9
 *
10
 * Copyright 2000-2012 Willy Tarreau <w@1wt.eu>
11
 *
12
 * This program is free software; you can redistribute it and/or
13
 * modify it under the terms of the GNU General Public License
14
 * as published by the Free Software Foundation; either version
15
 * 2 of the License, or (at your option) any later version.
16
 *
17
 */
18
19
#include <import/eb32tree.h>
20
#include <haproxy/api.h>
21
#include <haproxy/backend.h>
22
#include <haproxy/queue.h>
23
#include <haproxy/server-t.h>
24
25
26
/* Remove a server from a tree. It must have previously been dequeued. This
27
 * function is meant to be called when a server is going down or has its
28
 * weight disabled.
29
 *
30
 * The server's lock and the lbprm's lock must be held.
31
 */
32
static inline void fas_remove_from_tree(struct server *s)
33
0
{
34
0
  s->lb_tree = NULL;
35
0
}
36
37
/* simply removes a server from a tree.
38
 *
39
 * The lbprm's lock must be held.
40
 */
41
static inline void fas_dequeue_srv(struct server *s)
42
0
{
43
0
  eb32_delete(&s->lb_node);
44
0
}
45
46
/* Queue a server in its associated tree, assuming the weight is >0.
47
 * Servers are sorted by unique ID so that we send all connections to the first
48
 * available server in declaration order (or ID order) until its maxconn is
49
 * reached. It is important to understand that the server weight is not used
50
 * here.
51
 *
52
 * The lbprm's lock must be held.
53
 */
54
static inline void fas_queue_srv(struct server *s)
55
0
{
56
0
  s->lb_node.key = s->puid;
57
0
  eb32_insert(s->lb_tree, &s->lb_node);
58
0
}
59
60
/* Re-position the server in the FS tree after it has been assigned one
61
 * connection or after it has released one. Note that it is possible that
62
 * the server has been moved out of the tree due to failed health-checks.
63
 * The lbprm's lock will be used.
64
 */
65
static void fas_srv_reposition(struct server *s)
66
0
{
67
0
  HA_RWLOCK_WRLOCK(LBPRM_LOCK, &s->proxy->lbprm.lock);
68
0
  if (s->lb_tree) {
69
0
    fas_dequeue_srv(s);
70
0
    fas_queue_srv(s);
71
0
  }
72
0
  HA_RWLOCK_WRUNLOCK(LBPRM_LOCK, &s->proxy->lbprm.lock);
73
0
}
74
75
/* This function updates the server trees according to server <srv>'s new
76
 * state. It should be called when server <srv>'s status changes to down.
77
 * It is not important whether the server was already down or not. It is not
78
 * important either that the new state is completely down (the caller may not
79
 * know all the variables of a server's state).
80
 *
81
 * The server's lock must be held. The lbprm's lock will be used.
82
 */
83
static void fas_set_server_status_down(struct server *srv)
84
0
{
85
0
  struct proxy *p = srv->proxy;
86
87
0
  if (!srv_lb_status_changed(srv))
88
0
    return;
89
90
0
  if (srv_willbe_usable(srv))
91
0
    goto out_update_state;
92
93
0
  HA_RWLOCK_WRLOCK(LBPRM_LOCK, &p->lbprm.lock);
94
95
0
  if (!srv_currently_usable(srv))
96
    /* server was already down */
97
0
    goto out_update_backend;
98
99
0
  if (srv->flags & SRV_F_BACKUP) {
100
0
    p->lbprm.tot_wbck -= srv->cur_eweight;
101
0
    p->srv_bck--;
102
103
0
    if (srv == p->lbprm.fbck) {
104
      /* we lost the first backup server in a single-backup
105
       * configuration, we must search another one.
106
       */
107
0
      struct server *srv2 = p->lbprm.fbck;
108
0
      do {
109
0
        srv2 = srv2->next;
110
0
      } while (srv2 &&
111
0
         !((srv2->flags & SRV_F_BACKUP) &&
112
0
           srv_willbe_usable(srv2)));
113
0
      p->lbprm.fbck = srv2;
114
0
    }
115
0
  } else {
116
0
    p->lbprm.tot_wact -= srv->cur_eweight;
117
0
    p->srv_act--;
118
0
  }
119
120
0
  fas_dequeue_srv(srv);
121
0
  fas_remove_from_tree(srv);
122
123
0
 out_update_backend:
124
  /* check/update tot_used, tot_weight */
125
0
  update_backend_weight(p);
126
0
  HA_RWLOCK_WRUNLOCK(LBPRM_LOCK, &p->lbprm.lock);
127
128
0
 out_update_state:
129
0
  srv_lb_commit_status(srv);
130
0
}
131
132
/* This function updates the server trees according to server <srv>'s new
133
 * state. It should be called when server <srv>'s status changes to up.
134
 * It is not important whether the server was already down or not. It is not
135
 * important either that the new state is completely UP (the caller may not
136
 * know all the variables of a server's state). This function will not change
137
 * the weight of a server which was already up.
138
 *
139
 * The server's lock must be held. The lbprm's lock will be used.
140
 */
141
static void fas_set_server_status_up(struct server *srv)
142
0
{
143
0
  struct proxy *p = srv->proxy;
144
145
0
  if (!srv_lb_status_changed(srv))
146
0
    return;
147
148
0
  if (!srv_willbe_usable(srv))
149
0
    goto out_update_state;
150
151
0
  HA_RWLOCK_WRLOCK(LBPRM_LOCK, &p->lbprm.lock);
152
153
0
  if (srv_currently_usable(srv))
154
    /* server was already up */
155
0
    goto out_update_backend;
156
157
0
  if (srv->flags & SRV_F_BACKUP) {
158
0
    srv->lb_tree = &p->lbprm.fas.bck;
159
0
    p->lbprm.tot_wbck += srv->next_eweight;
160
0
    p->srv_bck++;
161
162
0
    if (!(p->options & PR_O_USE_ALL_BK)) {
163
0
      if (!p->lbprm.fbck) {
164
        /* there was no backup server anymore */
165
0
        p->lbprm.fbck = srv;
166
0
      } else {
167
        /* we may have restored a backup server prior to fbck,
168
         * in which case it should replace it.
169
         */
170
0
        struct server *srv2 = srv;
171
0
        do {
172
0
          srv2 = srv2->next;
173
0
        } while (srv2 && (srv2 != p->lbprm.fbck));
174
0
        if (srv2)
175
0
          p->lbprm.fbck = srv;
176
0
      }
177
0
    }
178
0
  } else {
179
0
    srv->lb_tree = &p->lbprm.fas.act;
180
0
    p->lbprm.tot_wact += srv->next_eweight;
181
0
    p->srv_act++;
182
0
  }
183
184
  /* note that eweight cannot be 0 here */
185
0
  fas_queue_srv(srv);
186
187
0
 out_update_backend:
188
  /* check/update tot_used, tot_weight */
189
0
  update_backend_weight(p);
190
0
  HA_RWLOCK_WRUNLOCK(LBPRM_LOCK, &p->lbprm.lock);
191
192
0
 out_update_state:
193
0
  srv_lb_commit_status(srv);
194
0
}
195
196
/* This function must be called after an update to server <srv>'s effective
197
 * weight. It may be called after a state change too.
198
 *
199
 * The server's lock must be held. The lbprm's lock will be used.
200
 */
201
static void fas_update_server_weight(struct server *srv)
202
0
{
203
0
  int old_state, new_state;
204
0
  struct proxy *p = srv->proxy;
205
206
0
  if (!srv_lb_status_changed(srv))
207
0
    return;
208
209
  /* If changing the server's weight changes its state, we simply apply
210
   * the procedures we already have for status change. If the state
211
   * remains down, the server is not in any tree, so it's as easy as
212
   * updating its values. If the state remains up with different weights,
213
   * there are some computations to perform to find a new place and
214
   * possibly a new tree for this server.
215
   */
216
   
217
0
  old_state = srv_currently_usable(srv);
218
0
  new_state = srv_willbe_usable(srv);
219
220
0
  if (!old_state && !new_state) {
221
0
    srv_lb_commit_status(srv);
222
0
    return;
223
0
  }
224
0
  else if (!old_state && new_state) {
225
0
    fas_set_server_status_up(srv);
226
0
    return;
227
0
  }
228
0
  else if (old_state && !new_state) {
229
0
    fas_set_server_status_down(srv);
230
0
    return;
231
0
  }
232
233
0
  HA_RWLOCK_WRLOCK(LBPRM_LOCK, &p->lbprm.lock);
234
235
0
  if (srv->lb_tree)
236
0
    fas_dequeue_srv(srv);
237
238
0
  if (srv->flags & SRV_F_BACKUP) {
239
0
    p->lbprm.tot_wbck += srv->next_eweight - srv->cur_eweight;
240
0
    srv->lb_tree = &p->lbprm.fas.bck;
241
0
  } else {
242
0
    p->lbprm.tot_wact += srv->next_eweight - srv->cur_eweight;
243
0
    srv->lb_tree = &p->lbprm.fas.act;
244
0
  }
245
246
0
  fas_queue_srv(srv);
247
248
0
  update_backend_weight(p);
249
0
  HA_RWLOCK_WRUNLOCK(LBPRM_LOCK, &p->lbprm.lock);
250
251
0
  srv_lb_commit_status(srv);
252
0
}
253
254
/* This function is responsible for building the trees in case of fast
255
 * weighted least-conns. It also sets p->lbprm.wdiv to the eweight to
256
 * uweight ratio. Both active and backup groups are initialized.
257
 */
258
void fas_init_server_tree(struct proxy *p)
259
0
{
260
0
  struct server *srv;
261
0
  struct eb_root init_head = EB_ROOT;
262
263
0
  p->lbprm.set_server_status_up   = fas_set_server_status_up;
264
0
  p->lbprm.set_server_status_down = fas_set_server_status_down;
265
0
  p->lbprm.update_server_eweight  = fas_update_server_weight;
266
0
  p->lbprm.server_take_conn = fas_srv_reposition;
267
0
  p->lbprm.server_drop_conn = fas_srv_reposition;
268
269
0
  p->lbprm.wdiv = BE_WEIGHT_SCALE;
270
0
  for (srv = p->srv; srv; srv = srv->next) {
271
0
    srv->next_eweight = (srv->uweight * p->lbprm.wdiv + p->lbprm.wmult - 1) / p->lbprm.wmult;
272
0
    srv_lb_commit_status(srv);
273
0
  }
274
275
0
  recount_servers(p);
276
0
  update_backend_weight(p);
277
278
0
  p->lbprm.fas.act = init_head;
279
0
  p->lbprm.fas.bck = init_head;
280
281
  /* queue active and backup servers in two distinct groups */
282
0
  for (srv = p->srv; srv; srv = srv->next) {
283
0
    if (!srv_currently_usable(srv))
284
0
      continue;
285
0
    srv->lb_tree = (srv->flags & SRV_F_BACKUP) ? &p->lbprm.fas.bck : &p->lbprm.fas.act;
286
0
    fas_queue_srv(srv);
287
0
  }
288
0
}
289
290
/* Return next server from the FS tree in backend <p>. If the tree is empty,
291
 * return NULL. Saturated servers are skipped.
292
 *
293
 * The lbprm's lock will be used. The server's lock is not used.
294
 */
295
struct server *fas_get_next_server(struct proxy *p, struct server *srvtoavoid)
296
0
{
297
0
  struct server *srv, *avoided;
298
0
  struct eb32_node *node;
299
300
0
  srv = avoided = NULL;
301
302
0
  HA_RWLOCK_RDLOCK(LBPRM_LOCK, &p->lbprm.lock);
303
0
  if (p->srv_act)
304
0
    node = eb32_first(&p->lbprm.fas.act);
305
0
  else if (p->lbprm.fbck) {
306
0
    srv = p->lbprm.fbck;
307
0
    goto out;
308
0
  }
309
0
  else if (p->srv_bck)
310
0
    node = eb32_first(&p->lbprm.fas.bck);
311
0
  else {
312
0
    srv = NULL;
313
0
    goto out;
314
0
  }
315
316
0
  while (node) {
317
    /* OK, we have a server. However, it may be saturated, in which
318
     * case we don't want to reconsider it for now, so we'll simply
319
     * skip it. Same if it's the server we try to avoid, in which
320
     * case we simply remember it for later use if needed.
321
     */
322
0
    struct server *s;
323
324
0
    s = eb32_entry(node, struct server, lb_node);
325
0
    if (!s->maxconn || (!s->queueslength && s->served < srv_dynamic_maxconn(s))) {
326
0
      if (s != srvtoavoid) {
327
0
        srv = s;
328
0
        break;
329
0
      }
330
0
      avoided = s;
331
0
    }
332
0
    node = eb32_next(node);
333
0
  }
334
335
0
  if (!srv)
336
0
    srv = avoided;
337
0
  out:
338
0
  HA_RWLOCK_RDUNLOCK(LBPRM_LOCK, &p->lbprm.lock);
339
0
  return srv;
340
0
}
341
342
343
/*
344
 * Local variables:
345
 *  c-indent-level: 8
346
 *  c-basic-offset: 8
347
 * End:
348
 */