Home | History | Annotate | Line # | Download | only in lloadd
operation.c revision 1.3
      1 /*	$NetBSD: operation.c,v 1.3 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: operation.c,v 1.3 2025/09/05 21:16:24 christos Exp $");
     20 
     21 #include "portable.h"
     22 
     23 #include "lutil.h"
     24 #include "lload.h"
     25 
     26 ldap_pvt_thread_mutex_t lload_pin_mutex;
     27 unsigned long lload_next_pin = 1;
     28 
     29 TAvlnode *lload_control_actions = NULL;
     30 TAvlnode *lload_exop_actions = NULL;
     31 enum op_restriction lload_default_exop_action = LLOAD_OP_NOT_RESTRICTED;
     32 
     33 ber_tag_t
     34 slap_req2res( ber_tag_t tag )
     35 {
     36     switch ( tag ) {
     37         case LDAP_REQ_ADD:
     38         case LDAP_REQ_BIND:
     39         case LDAP_REQ_COMPARE:
     40         case LDAP_REQ_EXTENDED:
     41         case LDAP_REQ_MODIFY:
     42         case LDAP_REQ_MODRDN:
     43             tag++;
     44             break;
     45 
     46         case LDAP_REQ_DELETE:
     47             tag = LDAP_RES_DELETE;
     48             break;
     49 
     50         case LDAP_REQ_ABANDON:
     51         case LDAP_REQ_UNBIND:
     52             tag = LBER_SEQUENCE;
     53             break;
     54 
     55         case LDAP_REQ_SEARCH:
     56             tag = LDAP_RES_SEARCH_RESULT;
     57             break;
     58 
     59         default:
     60             tag = LBER_SEQUENCE;
     61     }
     62 
     63     return tag;
     64 }
     65 
     66 const char *
     67 lload_msgtype2str( ber_tag_t tag )
     68 {
     69     switch ( tag ) {
     70         case LDAP_REQ_ABANDON: return "abandon request";
     71         case LDAP_REQ_ADD: return "add request";
     72         case LDAP_REQ_BIND: return "bind request";
     73         case LDAP_REQ_COMPARE: return "compare request";
     74         case LDAP_REQ_DELETE: return "delete request";
     75         case LDAP_REQ_EXTENDED: return "extended request";
     76         case LDAP_REQ_MODIFY: return "modify request";
     77         case LDAP_REQ_RENAME: return "rename request";
     78         case LDAP_REQ_SEARCH: return "search request";
     79         case LDAP_REQ_UNBIND: return "unbind request";
     80 
     81         case LDAP_RES_ADD: return "add result";
     82         case LDAP_RES_BIND: return "bind result";
     83         case LDAP_RES_COMPARE: return "compare result";
     84         case LDAP_RES_DELETE: return "delete result";
     85         case LDAP_RES_EXTENDED: return "extended result";
     86         case LDAP_RES_INTERMEDIATE: return "intermediate response";
     87         case LDAP_RES_MODIFY: return "modify result";
     88         case LDAP_RES_RENAME: return "rename result";
     89         case LDAP_RES_SEARCH_ENTRY: return "search-entry response";
     90         case LDAP_RES_SEARCH_REFERENCE: return "search-reference response";
     91         case LDAP_RES_SEARCH_RESULT: return "search result";
     92     }
     93     return "unknown message";
     94 }
     95 
     96 int
     97 lload_restriction_cmp( const void *left, const void *right )
     98 {
     99     const struct restriction_entry *l = left, *r = right;
    100     return ber_bvcmp( &l->oid, &r->oid );
    101 }
    102 
    103 int
    104 operation_client_cmp( const void *left, const void *right )
    105 {
    106     const LloadOperation *l = left, *r = right;
    107 
    108     assert( l->o_client_connid == r->o_client_connid );
    109     if ( l->o_client_msgid || r->o_client_msgid ) {
    110         return ( l->o_client_msgid < r->o_client_msgid ) ?
    111                 -1 :
    112                 ( l->o_client_msgid > r->o_client_msgid );
    113     } else {
    114         return ( l->o_pin_id < r->o_pin_id ) ? -1 :
    115                 ( l->o_pin_id > r->o_pin_id );
    116     }
    117 }
    118 
    119 int
    120 operation_upstream_cmp( const void *left, const void *right )
    121 {
    122     const LloadOperation *l = left, *r = right;
    123 
    124     assert( l->o_upstream_connid == r->o_upstream_connid );
    125     if ( l->o_upstream_msgid || r->o_upstream_msgid ) {
    126         return ( l->o_upstream_msgid < r->o_upstream_msgid ) ?
    127                 -1 :
    128                 ( l->o_upstream_msgid > r->o_upstream_msgid );
    129     } else {
    130         return ( l->o_pin_id < r->o_pin_id ) ? -1 :
    131                 ( l->o_pin_id > r->o_pin_id );
    132     }
    133 }
    134 
    135 /*
    136  * Entered holding c_mutex for now.
    137  */
    138 LloadOperation *
    139 operation_init( LloadConnection *c, BerElement *ber )
    140 {
    141     LloadOperation *op;
    142     ber_tag_t tag;
    143     ber_len_t len;
    144     int rc;
    145 
    146     if ( !IS_ALIVE( c, c_live ) ) {
    147         return NULL;
    148     }
    149 
    150     op = ch_calloc( 1, sizeof(LloadOperation) );
    151     op->o_client = c;
    152     op->o_client_connid = c->c_connid;
    153     op->o_ber = ber;
    154     gettimeofday( &op->o_start, NULL );
    155 
    156     ldap_pvt_thread_mutex_init( &op->o_link_mutex );
    157 
    158     op->o_refcnt = 1;
    159 
    160     tag = ber_get_int( ber, &op->o_client_msgid );
    161     if ( tag != LDAP_TAG_MSGID ) {
    162         goto fail;
    163     }
    164 
    165     if ( !op->o_client_msgid ) {
    166         goto fail;
    167     }
    168 
    169     CONNECTION_ASSERT_LOCKED(c);
    170     rc = ldap_tavl_insert( &c->c_ops, op, operation_client_cmp, ldap_avl_dup_error );
    171     if ( rc ) {
    172         Debug( LDAP_DEBUG_PACKETS, "operation_init: "
    173                 "several operations with same msgid=%d in-flight "
    174                 "from client connid=%lu\n",
    175                 op->o_client_msgid, op->o_client_connid );
    176         goto fail;
    177     }
    178 
    179     tag = op->o_tag = ber_skip_element( ber, &op->o_request );
    180     switch ( tag ) {
    181         case LBER_ERROR:
    182             rc = -1;
    183             break;
    184     }
    185     if ( rc ) {
    186         ldap_tavl_delete( &c->c_ops, op, operation_client_cmp );
    187         goto fail;
    188     }
    189 
    190     tag = ber_peek_tag( ber, &len );
    191     if ( tag == LDAP_TAG_CONTROLS ) {
    192         ber_skip_element( ber, &op->o_ctrls );
    193     }
    194 
    195     switch ( op->o_tag ) {
    196         case LDAP_REQ_BIND:
    197             lload_stats.counters[LLOAD_STATS_OPS_BIND].lc_ops_received++;
    198             break;
    199         default:
    200             lload_stats.counters[LLOAD_STATS_OPS_OTHER].lc_ops_received++;
    201             break;
    202     }
    203 
    204     Debug( LDAP_DEBUG_STATS, "operation_init: "
    205             "received a new operation, %s with msgid=%d for client "
    206             "connid=%lu\n",
    207             lload_msgtype2str( op->o_tag ), op->o_client_msgid,
    208             op->o_client_connid );
    209 
    210     c->c_n_ops_executing++;
    211     return op;
    212 
    213 fail:
    214     ch_free( op );
    215     return NULL;
    216 }
    217 
    218 void
    219 operation_destroy( LloadOperation *op )
    220 {
    221     Debug( LDAP_DEBUG_TRACE, "operation_destroy: "
    222             "op=%p destroyed operation from client connid=%lu, "
    223             "client msgid=%d\n",
    224             op, op->o_client_connid, op->o_client_msgid );
    225 
    226     assert( op->o_refcnt == 0 );
    227     assert( op->o_client == NULL );
    228     assert( op->o_upstream == NULL );
    229 
    230     ber_free( op->o_ber, 1 );
    231     ldap_pvt_thread_mutex_destroy( &op->o_link_mutex );
    232     ch_free( op );
    233 }
    234 
    235 int
    236 operation_unlink( LloadOperation *op )
    237 {
    238     LloadConnection *client, *upstream;
    239     uintptr_t prev_refcnt;
    240     int result = 0;
    241 
    242     assert( op->o_refcnt == 0 );
    243 
    244     Debug( LDAP_DEBUG_TRACE, "operation_unlink: "
    245             "unlinking operation between client connid=%lu and upstream "
    246             "connid=%lu "
    247             "client msgid=%d\n",
    248             op->o_client_connid, op->o_upstream_connid, op->o_client_msgid );
    249 
    250     checked_lock( &op->o_link_mutex );
    251     client = op->o_client;
    252     upstream = op->o_upstream;
    253 
    254     op->o_client = NULL;
    255     op->o_upstream = NULL;
    256     checked_unlock( &op->o_link_mutex );
    257 
    258     assert( client || upstream );
    259 
    260     if ( client ) {
    261         result |= operation_unlink_client( op, client );
    262         operation_update_global_rejected( op );
    263     }
    264 
    265     if ( upstream ) {
    266         result |= operation_unlink_upstream( op, upstream );
    267     }
    268 
    269     return result;
    270 }
    271 
    272 int
    273 operation_unlink_client( LloadOperation *op, LloadConnection *client )
    274 {
    275     LloadOperation *removed;
    276     int result = 0;
    277 
    278     Debug( LDAP_DEBUG_TRACE, "operation_unlink_client: "
    279             "unlinking operation op=%p msgid=%d client connid=%lu\n",
    280             op, op->o_client_msgid, op->o_client_connid );
    281 
    282     CONNECTION_LOCK(client);
    283     if ( (removed = ldap_tavl_delete(
    284                    &client->c_ops, op, operation_client_cmp )) ) {
    285         result = LLOAD_OP_DETACHING_CLIENT;
    286 
    287         assert( op == removed );
    288         client->c_n_ops_executing--;
    289 
    290         if ( op->o_restricted == LLOAD_OP_RESTRICTED_WRITE ) {
    291             if ( !--client->c_restricted_inflight &&
    292                     client->c_restricted_at >= 0 ) {
    293                 if ( lload_write_coherence < 0 ) {
    294                     client->c_restricted_at = -1;
    295                 } else if ( timerisset( &op->o_last_response ) ) {
    296                     client->c_restricted_at = op->o_last_response.tv_sec;
    297                 } else {
    298                     /* We have to default to o_start just in case we abandoned an
    299                      * operation that the backend actually processed */
    300                     client->c_restricted_at = op->o_start.tv_sec;
    301                 }
    302             }
    303         }
    304 
    305         if ( op->o_tag == LDAP_REQ_BIND &&
    306                 client->c_state == LLOAD_C_BINDING ) {
    307             client->c_state = LLOAD_C_READY;
    308             if ( !BER_BVISNULL( &client->c_auth ) ) {
    309                 ber_memfree( client->c_auth.bv_val );
    310                 BER_BVZERO( &client->c_auth );
    311             }
    312             if ( !BER_BVISNULL( &client->c_sasl_bind_mech ) ) {
    313                 ber_memfree( client->c_sasl_bind_mech.bv_val );
    314                 BER_BVZERO( &client->c_sasl_bind_mech );
    315             }
    316             if ( op->o_pin_id ) {
    317                 client->c_pin_id = 0;
    318             }
    319         }
    320     }
    321     if ( client->c_state == LLOAD_C_CLOSING && !client->c_ops ) {
    322         CONNECTION_DESTROY(client);
    323     } else {
    324         CONNECTION_UNLOCK(client);
    325     }
    326 
    327     return result;
    328 }
    329 
    330 int
    331 operation_unlink_upstream( LloadOperation *op, LloadConnection *upstream )
    332 {
    333     LloadOperation *removed;
    334     LloadBackend *b = NULL;
    335     int result = 0;
    336 
    337     Debug( LDAP_DEBUG_TRACE, "operation_unlink_upstream: "
    338             "unlinking operation op=%p msgid=%d upstream connid=%lu\n",
    339             op, op->o_upstream_msgid, op->o_upstream_connid );
    340 
    341     CONNECTION_LOCK(upstream);
    342     if ( (removed = ldap_tavl_delete(
    343                    &upstream->c_ops, op, operation_upstream_cmp )) ) {
    344         result |= LLOAD_OP_DETACHING_UPSTREAM;
    345 
    346         assert( op == removed );
    347         upstream->c_n_ops_executing--;
    348 
    349         if ( upstream->c_state == LLOAD_C_BINDING ) {
    350             assert( op->o_tag == LDAP_REQ_BIND && upstream->c_ops == NULL );
    351             upstream->c_state = LLOAD_C_READY;
    352             if ( !BER_BVISNULL( &upstream->c_sasl_bind_mech ) ) {
    353                 ber_memfree( upstream->c_sasl_bind_mech.bv_val );
    354                 BER_BVZERO( &upstream->c_sasl_bind_mech );
    355             }
    356         }
    357         operation_update_conn_counters( op, upstream );
    358         b = upstream->c_backend;
    359     }
    360     if ( upstream->c_state == LLOAD_C_CLOSING && !upstream->c_ops ) {
    361         CONNECTION_DESTROY(upstream);
    362     } else {
    363         CONNECTION_UNLOCK(upstream);
    364     }
    365 
    366     if ( b ) {
    367         checked_lock( &b->b_mutex );
    368         b->b_n_ops_executing--;
    369         operation_update_backend_counters( op, b );
    370         checked_unlock( &b->b_mutex );
    371     }
    372 
    373     return result;
    374 }
    375 
    376 int
    377 operation_send_abandon( LloadOperation *op, LloadConnection *upstream )
    378 {
    379     BerElement *ber;
    380     int rc = -1;
    381 
    382     if ( !IS_ALIVE( upstream, c_live ) ) {
    383         return rc;
    384     }
    385 
    386     checked_lock( &upstream->c_io_mutex );
    387     ber = upstream->c_pendingber;
    388     if ( ber == NULL && (ber = ber_alloc()) == NULL ) {
    389         Debug( LDAP_DEBUG_ANY, "operation_send_abandon: "
    390                 "ber_alloc failed\n" );
    391         goto done;
    392     }
    393     upstream->c_pendingber = ber;
    394 
    395     Debug( LDAP_DEBUG_TRACE, "operation_send_abandon: "
    396             "abandoning %s msgid=%d on connid=%lu\n",
    397             lload_msgtype2str( op->o_tag ), op->o_upstream_msgid,
    398             op->o_upstream_connid );
    399 
    400     if ( op->o_tag == LDAP_REQ_BIND ) {
    401         rc = ber_printf( ber, "t{tit{ist{s}}}", LDAP_TAG_MESSAGE,
    402                 LDAP_TAG_MSGID, upstream->c_next_msgid++,
    403                 LDAP_REQ_BIND, LDAP_VERSION3, "", LDAP_AUTH_SASL, "" );
    404     } else {
    405         rc = ber_printf( ber, "t{titi}", LDAP_TAG_MESSAGE,
    406                 LDAP_TAG_MSGID, upstream->c_next_msgid++,
    407                 LDAP_REQ_ABANDON, op->o_upstream_msgid );
    408     }
    409 
    410     if ( rc < 0 ) {
    411         ber_free( ber, 1 );
    412         upstream->c_pendingber = NULL;
    413         goto done;
    414     }
    415     rc = LDAP_SUCCESS;
    416 
    417 done:
    418     checked_unlock( &upstream->c_io_mutex );
    419     return rc;
    420 }
    421 
    422 /*
    423  * Will remove the operation from its upstream and if it was still there,
    424  * sends an abandon request.
    425  *
    426  * Being called from client_reset or request_abandon, the following hold:
    427  * - noone else is processing the read part of the client connection (no new
    428  *   operations come in there - relevant for the c_state checks)
    429  * - op->o_client_refcnt > op->o_client_live (and it follows that op->o_client != NULL)
    430  */
    431 void
    432 operation_abandon( LloadOperation *op )
    433 {
    434     LloadConnection *c;
    435 
    436     checked_lock( &op->o_link_mutex );
    437     c = op->o_upstream;
    438     checked_unlock( &op->o_link_mutex );
    439     if ( !c || !IS_ALIVE( c, c_live ) ) {
    440         goto done;
    441     }
    442 
    443     /* for now consider all abandoned operations completed,
    444      * perhaps add a separate counter later */
    445     op->o_res = LLOAD_OP_COMPLETED;
    446     if ( !operation_unlink_upstream( op, c ) ) {
    447         /* The operation has already been abandoned or finished */
    448         Debug( LDAP_DEBUG_TRACE, "operation_abandon: "
    449                 "%s from connid=%lu msgid=%d not present in connid=%lu any "
    450                 "more\n",
    451                 lload_msgtype2str( op->o_tag ), op->o_client_connid,
    452                 op->o_client_msgid, op->o_upstream_connid );
    453         goto done;
    454     }
    455 
    456     if ( operation_send_abandon( op, c ) == LDAP_SUCCESS ) {
    457         connection_write_cb( -1, 0, c );
    458     }
    459 
    460 done:
    461     OPERATION_UNLINK(op);
    462 }
    463 
    464 void
    465 operation_send_reject(
    466         LloadOperation *op,
    467         int result,
    468         const char *msg,
    469         int send_anyway )
    470 {
    471     LloadConnection *c;
    472     BerElement *ber;
    473     int found;
    474 
    475     Debug( LDAP_DEBUG_TRACE, "operation_send_reject: "
    476             "rejecting %s from client connid=%lu with message: \"%s\"\n",
    477             lload_msgtype2str( op->o_tag ), op->o_client_connid, msg );
    478 
    479     checked_lock( &op->o_link_mutex );
    480     c = op->o_client;
    481     checked_unlock( &op->o_link_mutex );
    482     if ( !c || !IS_ALIVE( c, c_live ) ) {
    483         Debug( LDAP_DEBUG_TRACE, "operation_send_reject: "
    484                 "not sending msgid=%d, client connid=%lu is dead\n",
    485                 op->o_client_msgid, op->o_client_connid );
    486 
    487         goto done;
    488     }
    489 
    490     found = operation_unlink_client( op, c );
    491     if ( !found && !send_anyway ) {
    492         Debug( LDAP_DEBUG_TRACE, "operation_send_reject: "
    493                 "msgid=%d not scheduled for client connid=%lu anymore, "
    494                 "not sending\n",
    495                 op->o_client_msgid, c->c_connid );
    496         goto done;
    497     }
    498 
    499     if ( op->o_client_msgid == 0 ) {
    500         assert( op->o_saved_msgid == 0 && op->o_pin_id );
    501         Debug( LDAP_DEBUG_TRACE, "operation_send_reject: "
    502                 "operation pin=%lu is just a pin, not sending\n",
    503                 op->o_pin_id );
    504         goto done;
    505     }
    506 
    507     checked_lock( &c->c_io_mutex );
    508     ber = c->c_pendingber;
    509     if ( ber == NULL && (ber = ber_alloc()) == NULL ) {
    510         checked_unlock( &c->c_io_mutex );
    511         Debug( LDAP_DEBUG_ANY, "operation_send_reject: "
    512                 "ber_alloc failed, closing connid=%lu\n",
    513                 c->c_connid );
    514         CONNECTION_LOCK_DESTROY(c);
    515         goto done;
    516     }
    517     c->c_pendingber = ber;
    518 
    519     ber_printf( ber, "t{tit{ess}}", LDAP_TAG_MESSAGE,
    520             LDAP_TAG_MSGID, op->o_client_msgid,
    521             slap_req2res( op->o_tag ), result, "", msg );
    522 
    523     checked_unlock( &c->c_io_mutex );
    524 
    525     connection_write_cb( -1, 0, c );
    526 
    527 done:
    528     OPERATION_UNLINK(op);
    529 }
    530 
    531 /*
    532  * Upstream is shutting down, signal the client if necessary, but we have to
    533  * call operation_destroy_from_upstream ourselves to detach upstream from the
    534  * op.
    535  *
    536  * Only called from upstream_destroy.
    537  */
    538 void
    539 operation_lost_upstream( LloadOperation *op )
    540 {
    541     operation_send_reject( op, LDAP_OTHER,
    542             "connection to the remote server has been severed", 0 );
    543 }
    544 
    545 int
    546 connection_timeout( LloadConnection *upstream, void *arg )
    547 {
    548     LloadOperation *op;
    549     TAvlnode *ops = NULL, *node, *next;
    550     LloadBackend *b = upstream->c_backend;
    551     struct timeval *threshold = arg;
    552     int rc, nops = 0;
    553 
    554     CONNECTION_LOCK(upstream);
    555     for ( node = ldap_tavl_end( upstream->c_ops, TAVL_DIR_LEFT );
    556             node && timercmp( &((LloadOperation *)node->avl_data)->o_start,
    557                     threshold, < ); /* shortcut */
    558             node = next ) {
    559         LloadOperation *found_op;
    560 
    561         next = ldap_tavl_next( node, TAVL_DIR_RIGHT );
    562         op = node->avl_data;
    563 
    564         /* Have we received another response since? */
    565         if ( timerisset( &op->o_last_response ) &&
    566                 !timercmp( &op->o_last_response, threshold, < ) ) {
    567             continue;
    568         }
    569 
    570         op->o_res = LLOAD_OP_FAILED;
    571         found_op = ldap_tavl_delete( &upstream->c_ops, op, operation_upstream_cmp );
    572         assert( op == found_op );
    573 
    574         if ( upstream->c_state == LLOAD_C_BINDING ) {
    575             assert( op->o_tag == LDAP_REQ_BIND && upstream->c_ops == NULL );
    576             upstream->c_state = LLOAD_C_READY;
    577             if ( !BER_BVISNULL( &upstream->c_sasl_bind_mech ) ) {
    578                 ber_memfree( upstream->c_sasl_bind_mech.bv_val );
    579                 BER_BVZERO( &upstream->c_sasl_bind_mech );
    580             }
    581         }
    582 
    583         rc = ldap_tavl_insert( &ops, op, operation_upstream_cmp, ldap_avl_dup_error );
    584         assert( rc == LDAP_SUCCESS );
    585 
    586         Debug( LDAP_DEBUG_STATS2, "connection_timeout: "
    587                 "timing out %s from connid=%lu msgid=%d sent to connid=%lu as "
    588                 "msgid=%d\n",
    589                 lload_msgtype2str( op->o_tag ), op->o_client_connid,
    590                 op->o_client_msgid, op->o_upstream_connid,
    591                 op->o_upstream_msgid );
    592         nops++;
    593     }
    594 
    595     if ( nops == 0 ) {
    596         CONNECTION_UNLOCK(upstream);
    597         return LDAP_SUCCESS;
    598     }
    599     upstream->c_n_ops_executing -= nops;
    600     upstream->c_counters.lc_ops_failed += nops;
    601     Debug( LDAP_DEBUG_STATS, "connection_timeout: "
    602             "timing out %d operations for connid=%lu\n",
    603             nops, upstream->c_connid );
    604     CONNECTION_UNLOCK(upstream);
    605 
    606     checked_lock( &b->b_mutex );
    607     b->b_n_ops_executing -= nops;
    608     checked_unlock( &b->b_mutex );
    609 
    610     for ( node = ldap_tavl_end( ops, TAVL_DIR_LEFT ); node;
    611             node = ldap_tavl_next( node, TAVL_DIR_RIGHT ) ) {
    612         op = node->avl_data;
    613 
    614         operation_send_reject( op,
    615                 op->o_tag == LDAP_REQ_SEARCH ? LDAP_TIMELIMIT_EXCEEDED :
    616                                                LDAP_ADMINLIMIT_EXCEEDED,
    617                 "upstream did not respond in time", 0 );
    618 
    619         if ( upstream->c_type != LLOAD_C_BIND && rc == LDAP_SUCCESS ) {
    620             rc = operation_send_abandon( op, upstream );
    621         }
    622         OPERATION_UNLINK(op);
    623     }
    624 
    625     if ( rc == LDAP_SUCCESS ) {
    626         connection_write_cb( -1, 0, upstream );
    627     }
    628 
    629     CONNECTION_LOCK(upstream);
    630     /* ITS#9799: If a Bind timed out, connection is in an unknown state */
    631     if ( upstream->c_type == LLOAD_C_BIND || rc != LDAP_SUCCESS ||
    632             ( upstream->c_state == LLOAD_C_CLOSING && !upstream->c_ops ) ) {
    633         CONNECTION_DESTROY(upstream);
    634     } else {
    635         CONNECTION_UNLOCK(upstream);
    636     }
    637 
    638     /* just dispose of the AVL, most operations should already be gone */
    639     ldap_tavl_free( ops, NULL );
    640     return LDAP_SUCCESS;
    641 }
    642 
    643 void
    644 operations_timeout( evutil_socket_t s, short what, void *arg )
    645 {
    646     struct event *self = arg;
    647     LloadTier *tier;
    648     time_t threshold;
    649 
    650     Debug( LDAP_DEBUG_TRACE, "operations_timeout: "
    651             "running timeout task\n" );
    652     if ( !lload_timeout_api ) goto done;
    653 
    654     threshold = slap_get_time() - lload_timeout_api->tv_sec;
    655 
    656     LDAP_STAILQ_FOREACH ( tier, &tiers, t_next ) {
    657         LloadBackend *b;
    658 
    659         LDAP_CIRCLEQ_FOREACH ( b, &tier->t_backends, b_next ) {
    660             epoch_t epoch;
    661 
    662             checked_lock( &b->b_mutex );
    663             if ( b->b_n_ops_executing == 0 ) {
    664                 checked_unlock( &b->b_mutex );
    665                 continue;
    666             }
    667 
    668             epoch = epoch_join();
    669 
    670             Debug( LDAP_DEBUG_TRACE, "operations_timeout: "
    671                     "timing out binds for backend uri=%s\n",
    672                     b->b_uri.bv_val );
    673             connections_walk_last( &b->b_mutex, &b->b_bindconns,
    674                     b->b_last_bindconn, connection_timeout, &threshold );
    675 
    676             Debug( LDAP_DEBUG_TRACE, "operations_timeout: "
    677                     "timing out other operations for backend uri=%s\n",
    678                     b->b_uri.bv_val );
    679             connections_walk_last( &b->b_mutex, &b->b_conns, b->b_last_conn,
    680                     connection_timeout, &threshold );
    681 
    682             epoch_leave( epoch );
    683             checked_unlock( &b->b_mutex );
    684         }
    685     }
    686 done:
    687     Debug( LDAP_DEBUG_TRACE, "operations_timeout: "
    688             "timeout task finished\n" );
    689     evtimer_add( self, lload_timeout_api );
    690 }
    691 
    692 void
    693 operation_update_global_rejected( LloadOperation *op )
    694 {
    695     if ( op->o_res == LLOAD_OP_REJECTED ) {
    696         assert( op->o_upstream_connid == 0 );
    697         switch ( op->o_tag ) {
    698             case LDAP_REQ_BIND:
    699                 lload_stats.counters[LLOAD_STATS_OPS_BIND].lc_ops_rejected++;
    700                 break;
    701             default:
    702                 lload_stats.counters[LLOAD_STATS_OPS_OTHER].lc_ops_rejected++;
    703                 break;
    704         }
    705     }
    706 }
    707 
    708 void
    709 operation_update_conn_counters( LloadOperation *op, LloadConnection *upstream )
    710 {
    711     if ( op->o_res == LLOAD_OP_COMPLETED ) {
    712         upstream->c_counters.lc_ops_completed++;
    713     } else {
    714         upstream->c_counters.lc_ops_failed++;
    715     }
    716 }
    717 
    718 void
    719 operation_update_backend_counters( LloadOperation *op, LloadBackend *b )
    720 {
    721     int stat_type = op->o_tag == LDAP_REQ_BIND ? LLOAD_STATS_OPS_BIND :
    722                                                  LLOAD_STATS_OPS_OTHER;
    723 
    724     assert( b != NULL );
    725     if ( op->o_res == LLOAD_OP_COMPLETED ) {
    726         b->b_counters[stat_type].lc_ops_completed++;
    727     } else {
    728         b->b_counters[stat_type].lc_ops_failed++;
    729     }
    730 }
    731