Coverage Report

Created: 2026-10-13 06:33

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