/* $OpenLDAP$ */
/* This work is part of OpenLDAP Software .
*
* Copyright 1998-2024 The OpenLDAP Foundation.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted only as authorized by the OpenLDAP
* Public License.
*
* A copy of this license is available in the file LICENSE in the
* top-level directory of the distribution or, alternatively, at
* .
*/
#include "portable.h"
#include
#include "lload.h"
#include "lutil.h"
static LloadTierInit weighted_init;
static LloadTierBackendCb weighted_add_backend;
static LloadTierBackendCb weighted_remove_backend;
static LloadTierSelect weighted_select;
struct lload_tier_type weighted_tier;
/*
* Linear Congruential Generator - we don't need
* high quality randomness, and we don't want to
* interfere with anyone else's use of srand().
*
* The PRNG here cycles thru 941,955 numbers.
*/
static float weighted_seed;
static void
weighted_srand( int seed )
{
weighted_seed = (float)seed / (float)RAND_MAX;
}
static float
weighted_rand()
{
float val = 9821.0 * weighted_seed + .211327;
weighted_seed = val - (int)val;
return weighted_seed;
}
static void
weighted_shuffle( LloadBackend **b, int n )
{
int i, j, p;
uintptr_t total = 0, r;
for ( i = 0; i < n; i++ )
total += b[i]->b_weight;
/* all weights are zero, do a straight Fisher-Yates shuffle */
if ( !total ) {
while ( n ) {
LloadBackend *t;
i = weighted_rand() * n--;
t = b[n];
b[n] = b[i];
b[i] = t;
}
return;
}
/* Do a shuffle per RFC2782 Page 4 */
p = n;
for ( i = 0; i < n - 1; i++ ) {
r = weighted_rand() * total;
for ( j = 0; j < p; j++ ) {
r -= b[j]->b_weight;
if ( r <= 0 ) {
if ( j ) {
LloadBackend *t = b[0];
b[0] = b[j];
b[j] = t;
}
total -= b[0]->b_weight;
b++;
p--;
break;
}
}
/* TODO: once we have total == 0, should we jump over to the previous
* case? */
}
}
LloadTier *
weighted_init( void )
{
LloadTier *tier;
tier = ch_calloc( 1, sizeof(LloadTier) );
tier->t_type = weighted_tier;
ldap_pvt_thread_mutex_init( &tier->t_mutex );
LDAP_CIRCLEQ_INIT( &tier->t_backends );
weighted_srand( rand() );
return tier;
}
int
weighted_add_backend( LloadTier *tier, LloadBackend *to_add )
{
LloadBackend *b;
uintptr_t added = 1;
assert( to_add->b_tier == tier );
/* This requires us to use LDAP_CIRCLEQ_ENTRY_INIT() every time we have
* removed the backend from the list */
if ( LDAP_CIRCLEQ_NEXT( to_add, b_next ) ) {
added = 0;
LDAP_CIRCLEQ_REMOVE( &tier->t_backends, to_add, b_next );
}
/*
* Keep it sorted. The only thing RFC 2782 specifies is that weight 0
* entries are at the front of the list so they have a chance to be
* selected.
*
* Even with that in mind, there is a problem outlined in the RFC 2782
* errata[0] where the ordering affects the likelihood of an entry being
* selected with weight 0 entries in the mix - they are an afterthought
* into the design after all.
*
* [0]. https://www.rfc-editor.org/errata/eid2984
*/
LDAP_CIRCLEQ_FOREACH ( b, &tier->t_backends, b_next ) {
if ( to_add->b_weight < b->b_weight ) {
LDAP_CIRCLEQ_INSERT_BEFORE( &tier->t_backends, b, to_add, b_next );
goto done;
}
}
LDAP_CIRCLEQ_INSERT_TAIL( &tier->t_backends, to_add, b_next );
done:
tier->t_nbackends += added;
return LDAP_SUCCESS;
}
static int
weighted_remove_backend( LloadTier *tier, LloadBackend *b )
{
assert_locked( &tier->t_mutex );
assert_locked( &b->b_mutex );
assert( b->b_tier == tier );
assert( tier->t_nbackends );
LDAP_CIRCLEQ_REMOVE( &tier->t_backends, b, b_next );
LDAP_CIRCLEQ_ENTRY_INIT( b, b_next );
tier->t_nbackends--;
return LDAP_SUCCESS;
}
int
weighted_select(
LloadTier *tier,
LloadOperation *op,
LloadConnection **cp,
int *res,
char **message )
{
LloadBackend *b, **sorted;
int rc = 0, i = 0;
if ( !tier->t_nbackends ) return rc;
sorted = ch_malloc( tier->t_nbackends * sizeof(LloadBackend *) );
LDAP_CIRCLEQ_FOREACH ( b, &tier->t_backends, b_next ) {
sorted[i++] = b;
}
assert( i == tier->t_nbackends );
weighted_shuffle( sorted, tier->t_nbackends );
for ( i = 0; i < tier->t_nbackends; i++ ) {
int result;
checked_lock( &sorted[i]->b_mutex );
result = backend_select( sorted[i], op, cp, res, message );
checked_unlock( &sorted[i]->b_mutex );
rc |= result;
if ( result && *cp ) {
break;
}
}
ch_free( sorted );
return rc;
}
struct lload_tier_type weighted_tier = {
.tier_name = "weighted",
.tier_init = weighted_init,
.tier_startup = tier_startup,
.tier_reset = tier_reset,
.tier_destroy = tier_destroy,
.tier_oc = BER_BVC("olcBkLloadTierConfig"),
.tier_backend_oc = BER_BVC("olcBkLloadBackendConfig"),
.tier_add_backend = weighted_add_backend,
.tier_remove_backend = weighted_remove_backend,
.tier_select = weighted_select,
};