ngx_int_t
ngx_tcp_upstream_get_round_robin_peer(ngx_peer_connection_t *pc, void *data)
{
    ngx_tcp_upstream_rr_peer_data_t  *rrp = data;

    time_t                         now;
    uintptr_t                      m;
    ngx_int_t                      rc;
    ngx_uint_t                     i, n;
    ngx_connection_t              *c;
    ngx_tcp_upstream_rr_peer_t    *peer;
    ngx_tcp_upstream_rr_peers_t   *peers;

    ngx_log_debug1(NGX_LOG_DEBUG_TCP, pc->log, 0,
                   "get rr peer, try: %ui", pc->tries);

    now = ngx_time();

    /* ngx_lock_mutex(rrp->peers->mutex); */

    if (rrp->peers->last_cached) {

        /* cached connection */

        c = rrp->peers->cached[rrp->peers->last_cached];
        rrp->peers->last_cached--;

        /* ngx_unlock_mutex(ppr->peers->mutex); */

#if (NGX_THREADS)
        c->read->lock = c->read->own_lock;
        c->write->lock = c->write->own_lock;
#endif

        pc->connection = c;
        pc->cached = 1;

        return NGX_OK;
    }

    pc->cached = 0;
    pc->connection = NULL;

    if (rrp->peers->single) {
        peer = &rrp->peers->peer[0];
        if (ngx_tcp_check_peer_down(peer->check_index)) {
            return NGX_BUSY;
        }
    } else {

        /* there are several peers */

        if (pc->tries == rrp->peers->number) {

            /* it's a first try - get a current peer */

            i = pc->tries;

            for ( ;; ) {
                rrp->current = ngx_tcp_upstream_get_peer(rrp->peers);

                ngx_log_debug3(NGX_LOG_DEBUG_TCP, pc->log, 0,
                               "get rr peer, current: %ui %i, tries: %ui",
                               rrp->current,
                               rrp->peers->peer[rrp->current].current_weight,
                               pc->tries);

                n = rrp->current / (8 * sizeof(uintptr_t));
                m = (uintptr_t) 1 << rrp->current % (8 * sizeof(uintptr_t));

                if (!(rrp->tried[n] & m)) {
                    peer = &rrp->peers->peer[rrp->current];

                    if (!peer->down) {

                        ngx_log_debug1(NGX_LOG_DEBUG_TCP, pc->log, 0,
                                       "get rr peer, down: %ui",
                                       ngx_tcp_check_peer_down(peer->check_index));

                        if (!ngx_tcp_check_peer_down(peer->check_index)) {
                            if (peer->max_fails == 0
                                    || peer->fails < peer->max_fails)
                            {
                                break;
                            }

                            if (now - peer->accessed > peer->fail_timeout) {
                                peer->fails = 0;
                                break;
                            }
                        }

                        peer->current_weight = 0;

                    } else {
                        rrp->tried[n] |= m;
                    }

                    pc->tries--;
                }

                if (pc->tries == 0) {
                    goto failed;
                }

                if (--i == 0) {
                    ngx_log_error(NGX_LOG_ALERT, pc->log, 0,
                                  "round robin upstream stuck on %ui tries",
                                  pc->tries);
                    goto failed;
                }
            }

            peer->current_weight--;

        } else {

            i = pc->tries;

            for ( ;; ) {
                n = rrp->current / (8 * sizeof(uintptr_t));
                m = (uintptr_t) 1 << rrp->current % (8 * sizeof(uintptr_t));

                if (!(rrp->tried[n] & m)) {

                    peer = &rrp->peers->peer[rrp->current];

                    if (!peer->down) {

                        if (!ngx_tcp_check_peer_down(peer->check_index)) {

                            if (peer->max_fails == 0
                                    || peer->fails < peer->max_fails)
                            {
                                break;
                            }

                            if (now - peer->accessed > peer->fail_timeout) {
                                peer->fails = 0;
                                break;
                            }
                        }

                        peer->current_weight = 0;

                    } else {
                        rrp->tried[n] |= m;
                    }

                    pc->tries--;
                }

                rrp->current++;

                if (rrp->current >= rrp->peers->number) {
                    rrp->current = 0;
                }

                if (pc->tries == 0) {
                    goto failed;
                }

                if (--i == 0) {
                    ngx_log_error(NGX_LOG_ALERT, pc->log, 0,
                                  "round robin upstream stuck on %ui tries",
                                  pc->tries);
                    goto failed;
                }
            }

            peer->current_weight--;
        }

        rrp->tried[n] |= m;
    }

    pc->sockaddr = peer->sockaddr;
    pc->socklen = peer->socklen;
    pc->name = &peer->name;
    pc->check_index = peer->check_index;

    /* ngx_unlock_mutex(rrp->peers->mutex); */

    if (pc->tries == 1 && rrp->peers->next) {
        pc->tries += rrp->peers->next->number;

        n = rrp->peers->next->number / (8 * sizeof(uintptr_t)) + 1;
        for (i = 0; i < n; i++) {
            rrp->tried[i] = 0;
        }
    }

    return NGX_OK;

failed:

    peers = rrp->peers;

    ngx_log_debug0(NGX_LOG_DEBUG_TCP, pc->log, 0, "backup servers1");

    if (peers->next) {

        /* ngx_unlock_mutex(peers->mutex); */

        ngx_log_debug0(NGX_LOG_DEBUG_TCP, pc->log, 0, "backup servers");

        rrp->peers = peers->next;
        pc->tries = rrp->peers->number;

        n = rrp->peers->number / (8 * sizeof(uintptr_t)) + 1;
        for (i = 0; i < n; i++) {
            rrp->tried[i] = 0;
        }

        rc = ngx_tcp_upstream_get_round_robin_peer(pc, rrp);

        if (rc != NGX_BUSY) {
            return rc;
        }

        /* ngx_lock_mutex(peers->mutex); */
    }

    /* all peers failed, mark them as live for quick recovery */

    for (i = 0; i < peers->number; i++) {
        peers->peer[i].fails = 0;
    }

    ngx_log_debug0(NGX_LOG_DEBUG_TCP, pc->log, 0, "backup servers2");

    /* ngx_unlock_mutex(peers->mutex); */

    pc->name = peers->name;

    return NGX_BUSY;
}
static ngx_int_t
ngx_tcp_upstream_get_ip_hash_peer(ngx_peer_connection_t *pc, void *data)
{
    ngx_tcp_upstream_ip_hash_peer_data_t  *iphp = data;

    time_t                        now;
    uintptr_t                     m;
    ngx_uint_t                    i, n, p, hash;
    ngx_tcp_upstream_rr_peer_t   *peer;

    ngx_log_debug1(NGX_LOG_DEBUG_TCP, pc->log, 0,
                   "get ip hash peer, try: %ui", pc->tries);

    /* TODO: cached */

    if (iphp->tries > 20 || iphp->rrp.peers->single) {
        return iphp->get_rr_peer(pc, &iphp->rrp);
    }

    now = ngx_time();

    pc->cached = 0;
    pc->connection = NULL;

    hash = iphp->hash;

    for ( ;; ) {

        for (i = 0; i < 3; i++) {
            hash = (hash * 113 + iphp->addr[i]) % 6271;
        }

        p = hash % iphp->rrp.peers->number;

        n = p / (8 * sizeof(uintptr_t));
        m = (uintptr_t) 1 << p % (8 * sizeof(uintptr_t));

        if (!(iphp->rrp.tried[n] & m)) {

            ngx_log_debug4(NGX_LOG_DEBUG_TCP, pc->log, 0,
                           "get ip hash peer, hash: %d, %ui, %04XA, num: %d",
                           hash, p, m, iphp->rrp.peers->number);

            peer = &iphp->rrp.peers->peer[p];

            /* ngx_lock_mutex(iphp->rrp.peers->mutex); */

            if (!peer->down) {
                if (!ngx_tcp_check_peer_down(peer->check_index)) {

                    if (peer->max_fails == 0 || peer->fails < peer->max_fails) {
                        break;
                    }

                    if (now - peer->accessed > peer->fail_timeout) {
                        peer->fails = 0;
                        break;
                    }
                }
            }

            iphp->rrp.tried[n] |= m;

            /* ngx_unlock_mutex(iphp->rrp.peers->mutex); */

            pc->tries--;
        }

        if (++iphp->tries >= 20) {
            return iphp->get_rr_peer(pc, &iphp->rrp);
        }
    }

    iphp->rrp.current = p;

    pc->sockaddr = peer->sockaddr;
    pc->socklen = peer->socklen;
    pc->name = &peer->name;
    pc->check_index = peer->check_index;

    /* ngx_unlock_mutex(iphp->rrp.peers->mutex); */

    iphp->rrp.tried[n] |= m;
    iphp->hash = hash;

    return NGX_OK;
}