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