Home | History | Annotate | Line # | Download | only in lloadd
      1 /*	$NetBSD: tier_weighted.c,v 1.2 2025/09/05 21:16:24 christos Exp $	*/
      2 
      3 /* $OpenLDAP$ */
      4 /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
      5  *
      6  * Copyright 1998-2024 The OpenLDAP Foundation.
      7  * All rights reserved.
      8  *
      9  * Redistribution and use in source and binary forms, with or without
     10  * modification, are permitted only as authorized by the OpenLDAP
     11  * Public License.
     12  *
     13  * A copy of this license is available in the file LICENSE in the
     14  * top-level directory of the distribution or, alternatively, at
     15  * <http://www.OpenLDAP.org/license.html>.
     16  */
     17 
     18 #include <sys/cdefs.h>
     19 __RCSID("$NetBSD: tier_weighted.c,v 1.2 2025/09/05 21:16:24 christos Exp $");
     20 
     21 #include "portable.h"
     22 
     23 #include <ac/string.h>
     24 
     25 #include "lload.h"
     26 #include "lutil.h"
     27 
     28 static LloadTierInit weighted_init;
     29 static LloadTierBackendCb weighted_add_backend;
     30 static LloadTierBackendCb weighted_remove_backend;
     31 static LloadTierSelect weighted_select;
     32 
     33 struct lload_tier_type weighted_tier;
     34 
     35 /*
     36  * Linear Congruential Generator - we don't need
     37  * high quality randomness, and we don't want to
     38  * interfere with anyone else's use of srand().
     39  *
     40  * The PRNG here cycles thru 941,955 numbers.
     41  */
     42 static float weighted_seed;
     43 
     44 static void
     45 weighted_srand( int seed )
     46 {
     47     weighted_seed = (float)seed / (float)RAND_MAX;
     48 }
     49 
     50 static float
     51 weighted_rand()
     52 {
     53     float val = 9821.0 * weighted_seed + .211327;
     54     weighted_seed = val - (int)val;
     55     return weighted_seed;
     56 }
     57 
     58 static void
     59 weighted_shuffle( LloadBackend **b, int n )
     60 {
     61     int i, j, p;
     62     uintptr_t total = 0, r;
     63 
     64     for ( i = 0; i < n; i++ )
     65         total += b[i]->b_weight;
     66 
     67     /* all weights are zero, do a straight Fisher-Yates shuffle */
     68     if ( !total ) {
     69         while ( n ) {
     70             LloadBackend *t;
     71             i = weighted_rand() * n--;
     72             t = b[n];
     73             b[n] = b[i];
     74             b[i] = t;
     75         }
     76         return;
     77     }
     78 
     79     /* Do a shuffle per RFC2782 Page 4 */
     80     p = n;
     81     for ( i = 0; i < n - 1; i++ ) {
     82         r = weighted_rand() * total;
     83         for ( j = 0; j < p; j++ ) {
     84             r -= b[j]->b_weight;
     85             if ( r <= 0 ) {
     86                 if ( j ) {
     87                     LloadBackend *t = b[0];
     88                     b[0] = b[j];
     89                     b[j] = t;
     90                 }
     91                 total -= b[0]->b_weight;
     92                 b++;
     93                 p--;
     94                 break;
     95             }
     96         }
     97         /* TODO: once we have total == 0, should we jump over to the previous
     98          * case? */
     99     }
    100 }
    101 
    102 LloadTier *
    103 weighted_init( void )
    104 {
    105     LloadTier *tier;
    106 
    107     tier = ch_calloc( 1, sizeof(LloadTier) );
    108 
    109     tier->t_type = weighted_tier;
    110     ldap_pvt_thread_mutex_init( &tier->t_mutex );
    111     LDAP_CIRCLEQ_INIT( &tier->t_backends );
    112 
    113     weighted_srand( rand() );
    114 
    115     return tier;
    116 }
    117 
    118 int
    119 weighted_add_backend( LloadTier *tier, LloadBackend *to_add )
    120 {
    121     LloadBackend *b;
    122     uintptr_t added = 1;
    123 
    124     assert( to_add->b_tier == tier );
    125 
    126     /* This requires us to use LDAP_CIRCLEQ_ENTRY_INIT() every time we have
    127      * removed the backend from the list */
    128     if ( LDAP_CIRCLEQ_NEXT( to_add, b_next ) ) {
    129         added = 0;
    130         LDAP_CIRCLEQ_REMOVE( &tier->t_backends, to_add, b_next );
    131     }
    132 
    133     /*
    134      * Keep it sorted. The only thing RFC 2782 specifies is that weight 0
    135      * entries are at the front of the list so they have a chance to be
    136      * selected.
    137      *
    138      * Even with that in mind, there is a problem outlined in the RFC 2782
    139      * errata[0] where the ordering affects the likelihood of an entry being
    140      * selected with weight 0 entries in the mix - they are an afterthought
    141      * into the design after all.
    142      *
    143      * [0]. https://www.rfc-editor.org/errata/eid2984
    144      */
    145     LDAP_CIRCLEQ_FOREACH ( b, &tier->t_backends, b_next ) {
    146         if ( to_add->b_weight < b->b_weight ) {
    147             LDAP_CIRCLEQ_INSERT_BEFORE( &tier->t_backends, b, to_add, b_next );
    148             goto done;
    149         }
    150     }
    151     LDAP_CIRCLEQ_INSERT_TAIL( &tier->t_backends, to_add, b_next );
    152 
    153 done:
    154     tier->t_nbackends += added;
    155     return LDAP_SUCCESS;
    156 }
    157 
    158 static int
    159 weighted_remove_backend( LloadTier *tier, LloadBackend *b )
    160 {
    161     assert_locked( &tier->t_mutex );
    162     assert_locked( &b->b_mutex );
    163 
    164     assert( b->b_tier == tier );
    165     assert( tier->t_nbackends );
    166 
    167     LDAP_CIRCLEQ_REMOVE( &tier->t_backends, b, b_next );
    168     LDAP_CIRCLEQ_ENTRY_INIT( b, b_next );
    169     tier->t_nbackends--;
    170 
    171     return LDAP_SUCCESS;
    172 }
    173 
    174 int
    175 weighted_select(
    176         LloadTier *tier,
    177         LloadOperation *op,
    178         LloadConnection **cp,
    179         int *res,
    180         char **message )
    181 {
    182     LloadBackend *b, **sorted;
    183     int rc = 0, i = 0;
    184 
    185     if ( !tier->t_nbackends ) return rc;
    186 
    187     sorted = ch_malloc( tier->t_nbackends * sizeof(LloadBackend *) );
    188 
    189     LDAP_CIRCLEQ_FOREACH ( b, &tier->t_backends, b_next ) {
    190         sorted[i++] = b;
    191     }
    192 
    193     assert( i == tier->t_nbackends );
    194 
    195     weighted_shuffle( sorted, tier->t_nbackends );
    196 
    197     for ( i = 0; i < tier->t_nbackends; i++ ) {
    198         int result;
    199 
    200         checked_lock( &sorted[i]->b_mutex );
    201         result = backend_select( sorted[i], op, cp, res, message );
    202         checked_unlock( &sorted[i]->b_mutex );
    203 
    204         rc |= result;
    205         if ( result && *cp ) {
    206             break;
    207         }
    208     }
    209 
    210     ch_free( sorted );
    211     return rc;
    212 }
    213 
    214 struct lload_tier_type weighted_tier = {
    215         .tier_name = "weighted",
    216 
    217         .tier_init = weighted_init,
    218         .tier_startup = tier_startup,
    219         .tier_reset = tier_reset,
    220         .tier_destroy = tier_destroy,
    221 
    222         .tier_oc = BER_BVC("olcBkLloadTierConfig"),
    223         .tier_backend_oc = BER_BVC("olcBkLloadBackendConfig"),
    224 
    225         .tier_add_backend = weighted_add_backend,
    226         .tier_remove_backend = weighted_remove_backend,
    227 
    228         .tier_select = weighted_select,
    229 };
    230