This is a forward port of the sourcehash and userhash peering
algorithms.

These are effectively just copies of carp.cc with the request key
changed accordingly.
# Bazaar merge directive format 2 (Bazaar 0.90)
# revision_id: [EMAIL PROTECTED]
#   jhqo27vfnrb0080u
# target_branch: file:///data/bzr/squid3/trunk/
# testament_sha1: c3b574b953b0e22d77a8a35b435b908e1b1ac646
# timestamp: 2008-07-11 22:44:44 +0200
# base_revision_id: [EMAIL PROTECTED]
#   0lhqbyg4izx30e4b
# 
# Begin patch
=== modified file 'src/Makefile.am'
--- src/Makefile.am	2008-07-04 12:09:33 +0000
+++ src/Makefile.am	2008-07-11 20:43:43 +0000
@@ -593,6 +593,8 @@
 	PeerDigest.h \
 	peer_digest.cc \
 	peer_select.cc \
+	peer_sourcehash.cc \
+	peer_userhash.cc \
 	PeerSelectState.h \
 	PingData.h \
 	protos.h \
@@ -890,6 +892,8 @@
 	pconn.cc \
 	peer_digest.cc \
 	peer_select.cc \
+	peer_sourcehash.cc \
+	peer_userhash.cc \
 	protos.h \
 	redirect.cc \
 	referer.cc \
@@ -1435,6 +1439,8 @@
 	pconn.cc \
 	peer_digest.cc \
 	peer_select.cc \
+	peer_sourcehash.cc \
+	peer_userhash.cc \
 	redirect.cc \
 	referer.cc \
 	refresh.cc \
@@ -1606,6 +1612,8 @@
 	pconn.cc \
 	peer_digest.cc \
 	peer_select.cc \
+	peer_sourcehash.cc \
+	peer_userhash.cc \
 	redirect.cc \
 	referer.cc \
 	refresh.cc \
@@ -1762,6 +1770,8 @@
 	pconn.cc \
 	peer_digest.cc \
 	peer_select.cc \
+	peer_sourcehash.cc \
+	peer_userhash.cc \
 	redirect.cc \
 	referer.cc \
 	refresh.cc \
@@ -1907,6 +1917,8 @@
 	Parsing.cc \
 	peer_digest.cc \
 	peer_select.cc \
+	peer_sourcehash.cc \
+	peer_userhash.cc \
 	pconn.cc \
 	redirect.cc \
 	referer.cc \
@@ -2068,6 +2080,8 @@
 	pconn.cc \
 	peer_digest.cc \
 	peer_select.cc \
+	peer_sourcehash.cc \
+	peer_userhash.cc \
 	redirect.cc \
 	referer.cc \
 	refresh.cc \
@@ -2430,6 +2444,8 @@
 	pconn.cc \
 	peer_digest.cc \
 	peer_select.cc \
+	peer_sourcehash.cc \
+	peer_userhash.cc \
 	redirect.cc \
 	referer.cc \
 	refresh.cc \

=== modified file 'src/cache_cf.cc'
--- src/cache_cf.cc	2008-07-11 19:32:10 +0000
+++ src/cache_cf.cc	2008-07-11 20:20:31 +0000
@@ -1740,6 +1740,18 @@
 
             p->options.carp = 1;
 
+        } else if (!strcasecmp(token, "userhash")) {
+            if (p->type != PEER_PARENT)
+                fatalf("parse_peer: non-parent userhash peer %s/%d\n", p->host, p->http_port);
+
+            p->options.userhash = 1;
+
+        } else if (!strcasecmp(token, "sourcehash")) {
+            if (p->type != PEER_PARENT)
+                fatalf("parse_peer: non-parent sourcehash peer %s/%d\n", p->host, p->http_port);
+
+            p->options.sourcehash = 1;
+
 #if DELAY_POOLS
 
         } else if (!strcasecmp(token, "no-delay")) {

=== modified file 'src/cf.data.pre'
--- src/cf.data.pre	2008-06-30 16:27:12 +0000
+++ src/cf.data.pre	2008-07-11 20:20:31 +0000
@@ -1571,6 +1571,8 @@
 		     round-robin
 		     weighted-round-robin
 		     carp
+		     userhash
+		     sourcehash
 		     multicast-responder
 		     closest-only
 		     no-digest
@@ -1645,6 +1647,12 @@
 		     distributed among the parents based on the CARP load
 		     balancing hash function based on their weight.
 
+                     use 'userhash' to load-balance amongst a set of parents
+                     based on the client proxy_auth or ident username.
+
+                     use 'sourcehash' to load-balance amongst a set of parents
+                     based on the client source ip.
+
 		     'multicast-responder' indicates the named peer
 		     is a member of a multicast group.  ICP queries will
 		     not be sent directly to the peer, but ICP replies

=== modified file 'src/enums.h'
--- src/enums.h	2008-07-11 19:32:10 +0000
+++ src/enums.h	2008-07-11 20:43:43 +0000
@@ -175,6 +175,8 @@
 #endif
     CARP,
     ANY_OLD_PARENT,
+    USERHASH_PARENT,
+    SOURCEHASH_PARENT,
     HIER_MAX
 } hier_code;
 

=== modified file 'src/main.cc'
--- src/main.cc	2008-07-11 20:14:45 +0000
+++ src/main.cc	2008-07-11 20:20:31 +0000
@@ -628,6 +628,8 @@
     peerSelectInit();
 
     carpInit();
+    peerUserHashInit();
+    peerSourceHashInit();
 }
 
 void
@@ -969,6 +971,8 @@
         asnRegisterWithCacheManager(manager);
         authenticateRegisterWithCacheManager(&Config.authConfiguration, manager);
         carpRegisterWithCacheManager(manager);
+	peerUserHashRegisterWithCacheManager(manager);
+	peerSourceHashRegisterWithCacheManager(manager);
         cbdataRegisterWithCacheManager(manager);
         /* These use separate calls so that the comm loops can eventually
          * coexist.

=== modified file 'src/neighbors.cc'
--- src/neighbors.cc	2008-06-12 12:49:29 +0000
+++ src/neighbors.cc	2008-07-11 20:20:31 +0000
@@ -1538,6 +1538,15 @@
     if (p->options.roundrobin)
         storeAppendPrintf(sentry, " round-robin");
 
+    if (p->options.carp)
+        storeAppendPrintf(sentry, " carp");
+
+    if (p->options.userhash)
+        storeAppendPrintf(sentry, " userhash");
+
+    if (p->options.userhash)
+        storeAppendPrintf(sentry, " sourcehash");
+
     if (p->options.weighted_roundrobin)
         storeAppendPrintf(sentry, " weighted-round-robin");
 

=== modified file 'src/peer_select.cc'
--- src/peer_select.cc	2008-07-11 19:32:10 +0000
+++ src/peer_select.cc	2008-07-11 20:43:43 +0000
@@ -65,6 +65,8 @@
 #endif
         "CARP",
         "ANY_PARENT",
+	"USERHASH",
+	"SOURCEHASH",
         "INVALID CODE"
     };
 
@@ -503,6 +505,10 @@
 
     if ((p = getDefaultParent(request))) {
         code = DEFAULT_PARENT;
+    } else if ((p = peerUserHashSelectParent(request))) {
+        code = USERHASH_PARENT;
+    } else if ((p = peerSourceHashSelectParent(request))) {
+        code = SOURCEHASH_PARENT;
     } else if ((p = carpSelectParent(request))) {
         code = CARP;
     } else if ((p = getRoundRobinParent(request))) {

=== added file 'src/peer_sourcehash.cc'
--- src/peer_sourcehash.cc	1970-01-01 00:00:00 +0000
+++ src/peer_sourcehash.cc	2008-07-11 20:43:43 +0000
@@ -0,0 +1,229 @@
+
+/*
+ * $Id: carp.cc,v 1.27 2008/01/14 12:13:49 hno Exp $
+ *
+ * DEBUG: section 39    Peer source hash based selection
+ * AUTHOR: Henrik Nordstrom
+ * BASED ON: carp.cc
+ *
+ * SQUID Web Proxy Cache          http://www.squid-cache.org/
+ * ----------------------------------------------------------
+ *
+ *  Squid is the result of efforts by numerous individuals from
+ *  the Internet community; see the CONTRIBUTORS file for full
+ *  details.   Many organizations have provided support for Squid's
+ *  development; see the SPONSORS file for full details.  Squid is
+ *  Copyrighted (C) 2001 by the Regents of the University of
+ *  California; see the COPYRIGHT file for full details.  Squid
+ *  incorporates software developed and/or copyrighted by other
+ *  sources; see the CREDITS file for full details.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
+ *
+ */
+
+#include "squid.h"
+#include "CacheManager.h"
+#include "Store.h"
+#include "HttpRequest.h"
+
+#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n))))
+
+static int n_sourcehash_peers = 0;
+static peer **sourcehash_peers = NULL;
+static OBJH peerSourceHashCachemgr;
+
+static int
+peerSortWeight(const void *a, const void *b)
+{
+    const peer *const *p1 = (const peer *const *)a;
+    const peer *const *p2 = (const peer *const *)b;
+    return (*p1)->weight - (*p2)->weight;
+}
+
+void
+peerSourceHashInit(void)
+{
+    int W = 0;
+    int K;
+    int k;
+    double P_last, X_last, Xn;
+    peer *p;
+    peer **P;
+    char *t;
+    /* Clean up */
+
+    for (k = 0; k < n_sourcehash_peers; k++) {
+        cbdataReferenceDone(sourcehash_peers[k]);
+    }
+
+    safe_free(sourcehash_peers);
+    n_sourcehash_peers = 0;
+    /* find out which peers we have */
+
+    for (p = Config.peers; p; p = p->next) {
+        if (!p->options.sourcehash)
+            continue;
+
+        assert(p->type == PEER_PARENT);
+
+        if (p->weight == 0)
+            continue;
+
+        n_sourcehash_peers++;
+
+        W += p->weight;
+    }
+
+    if (n_sourcehash_peers == 0)
+        return;
+
+    sourcehash_peers = (peer **)xcalloc(n_sourcehash_peers, sizeof(*sourcehash_peers));
+
+    /* Build a list of the found peers and calculate hashes and load factors */
+    for (P = sourcehash_peers, p = Config.peers; p; p = p->next) {
+        if (!p->options.sourcehash)
+            continue;
+
+        if (p->weight == 0)
+            continue;
+
+        /* calculate this peers hash */
+        p->sourcehash.hash = 0;
+
+        for (t = p->name; *t != 0; t++)
+            p->sourcehash.hash += ROTATE_LEFT(p->sourcehash.hash, 19) + (unsigned int) *t;
+
+        p->sourcehash.hash += p->sourcehash.hash * 0x62531965;
+
+        p->sourcehash.hash = ROTATE_LEFT(p->sourcehash.hash, 21);
+
+        /* and load factor */
+        p->sourcehash.load_factor = ((double) p->weight) / (double) W;
+
+        if (floor(p->sourcehash.load_factor * 1000.0) == 0.0)
+            p->sourcehash.load_factor = 0.0;
+
+        /* add it to our list of peers */
+        *P++ = cbdataReference(p);
+    }
+
+    /* Sort our list on weight */
+    qsort(sourcehash_peers, n_sourcehash_peers, sizeof(*sourcehash_peers), peerSortWeight);
+
+    /* Calculate the load factor multipliers X_k
+     *
+     * X_1 = pow ((K*p_1), (1/K))
+     * X_k = ([K-k+1] * [P_k - P_{k-1}])/(X_1 * X_2 * ... * X_{k-1})
+     * X_k += pow ((X_{k-1}, {K-k+1})
+     * X_k = pow (X_k, {1/(K-k+1)})
+     * simplified to have X_1 part of the loop
+     */
+    K = n_sourcehash_peers;
+
+    P_last = 0.0;		/* Empty P_0 */
+
+    Xn = 1.0;			/* Empty starting point of X_1 * X_2 * ... * X_{x-1} */
+
+    X_last = 0.0;		/* Empty X_0, nullifies the first pow statement */
+
+    for (k = 1; k <= K; k++) {
+        double Kk1 = (double) (K - k + 1);
+        p = sourcehash_peers[k - 1];
+        p->sourcehash.load_multiplier = (Kk1 * (p->sourcehash.load_factor - P_last)) / Xn;
+        p->sourcehash.load_multiplier += pow(X_last, Kk1);
+        p->sourcehash.load_multiplier = pow(p->sourcehash.load_multiplier, 1.0 / Kk1);
+        Xn *= p->sourcehash.load_multiplier;
+        X_last = p->sourcehash.load_multiplier;
+        P_last = p->sourcehash.load_factor;
+    }
+}
+
+void
+peerSourceHashRegisterWithCacheManager(CacheManager & manager)
+{
+    manager.registerAction("sourcehash", "peer sourcehash information", peerSourceHashCachemgr, 0, 1);
+}
+
+peer *
+peerSourceHashSelectParent(HttpRequest * request)
+{
+    int k;
+    const char *c;
+    peer *p = NULL;
+    peer *tp;
+    unsigned int user_hash = 0;
+    unsigned int combined_hash;
+    double score;
+    double high_score = 0;
+    const char *key = NULL;
+    char ntoabuf[MAX_IPSTRLEN];
+
+    if (n_sourcehash_peers == 0)
+        return NULL;
+
+    key = request->client_addr.NtoA(ntoabuf, sizeof(ntoabuf));
+
+    /* calculate hash key */
+    debugs(39, 2, "peerSourceHashSelectParent: Calculating hash for " << key);
+
+    for (c = key; *c != 0; c++)
+        user_hash += ROTATE_LEFT(user_hash, 19) + *c;
+
+    /* select peer */
+    for (k = 0; k < n_sourcehash_peers; k++) {
+        tp = sourcehash_peers[k];
+        combined_hash = (user_hash ^ tp->sourcehash.hash);
+        combined_hash += combined_hash * 0x62531965;
+        combined_hash = ROTATE_LEFT(combined_hash, 21);
+        score = combined_hash * tp->sourcehash.load_multiplier;
+        debugs(39, 3, "peerSourceHashSelectParent: " << tp->name << " combined_hash " << combined_hash  << 
+               " score " << std::setprecision(0) << score);
+
+        if ((score > high_score) && peerHTTPOkay(tp, request)) {
+            p = tp;
+            high_score = score;
+        }
+    }
+
+    if (p)
+        debugs(39, 2, "peerSourceHashSelectParent: selected " << p->name);
+
+    return p;
+}
+
+static void
+peerSourceHashCachemgr(StoreEntry * sentry)
+{
+    peer *p;
+    int sumfetches = 0;
+    storeAppendPrintf(sentry, "%24s %10s %10s %10s %10s\n",
+                      "Hostname",
+                      "Hash",
+                      "Multiplier",
+                      "Factor",
+                      "Actual");
+
+    for (p = Config.peers; p; p = p->next)
+        sumfetches += p->stats.fetches;
+
+    for (p = Config.peers; p; p = p->next) {
+        storeAppendPrintf(sentry, "%24s %10x %10f %10f %10f\n",
+                          p->name, p->sourcehash.hash,
+                          p->sourcehash.load_multiplier,
+                          p->sourcehash.load_factor,
+                          sumfetches ? (double) p->stats.fetches / sumfetches : -1.0);
+    }
+}

=== added file 'src/peer_userhash.cc'
--- src/peer_userhash.cc	1970-01-01 00:00:00 +0000
+++ src/peer_userhash.cc	2008-07-11 20:43:43 +0000
@@ -0,0 +1,233 @@
+
+/*
+ * $Id: carp.cc,v 1.27 2008/01/14 12:13:49 hno Exp $
+ *
+ * DEBUG: section 39    Peer user hash based selection
+ * AUTHOR: Henrik Nordstrom
+ * BASED ON: carp.cc
+ *
+ * SQUID Web Proxy Cache          http://www.squid-cache.org/
+ * ----------------------------------------------------------
+ *
+ *  Squid is the result of efforts by numerous individuals from
+ *  the Internet community; see the CONTRIBUTORS file for full
+ *  details.   Many organizations have provided support for Squid's
+ *  development; see the SPONSORS file for full details.  Squid is
+ *  Copyrighted (C) 2001 by the Regents of the University of
+ *  California; see the COPYRIGHT file for full details.  Squid
+ *  incorporates software developed and/or copyrighted by other
+ *  sources; see the CREDITS file for full details.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
+ *
+ */
+
+#include "squid.h"
+#include "CacheManager.h"
+#include "Store.h"
+#include "HttpRequest.h"
+#include "AuthUserRequest.h"
+
+#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n))))
+
+static int n_userhash_peers = 0;
+static peer **userhash_peers = NULL;
+static OBJH peerUserHashCachemgr;
+
+static int
+peerSortWeight(const void *a, const void *b)
+{
+    const peer *const *p1 = (const peer *const *)a;
+    const peer *const *p2 = (const peer *const *)b;
+    return (*p1)->weight - (*p2)->weight;
+}
+
+void
+peerUserHashInit(void)
+{
+    int W = 0;
+    int K;
+    int k;
+    double P_last, X_last, Xn;
+    peer *p;
+    peer **P;
+    char *t;
+    /* Clean up */
+
+    for (k = 0; k < n_userhash_peers; k++) {
+        cbdataReferenceDone(userhash_peers[k]);
+    }
+
+    safe_free(userhash_peers);
+    n_userhash_peers = 0;
+    /* find out which peers we have */
+
+    for (p = Config.peers; p; p = p->next) {
+        if (!p->options.userhash)
+            continue;
+
+        assert(p->type == PEER_PARENT);
+
+        if (p->weight == 0)
+            continue;
+
+        n_userhash_peers++;
+
+        W += p->weight;
+    }
+
+    if (n_userhash_peers == 0)
+        return;
+
+    userhash_peers = (peer **)xcalloc(n_userhash_peers, sizeof(*userhash_peers));
+
+    /* Build a list of the found peers and calculate hashes and load factors */
+    for (P = userhash_peers, p = Config.peers; p; p = p->next) {
+        if (!p->options.userhash)
+            continue;
+
+        if (p->weight == 0)
+            continue;
+
+        /* calculate this peers hash */
+        p->userhash.hash = 0;
+
+        for (t = p->name; *t != 0; t++)
+            p->userhash.hash += ROTATE_LEFT(p->userhash.hash, 19) + (unsigned int) *t;
+
+        p->userhash.hash += p->userhash.hash * 0x62531965;
+
+        p->userhash.hash = ROTATE_LEFT(p->userhash.hash, 21);
+
+        /* and load factor */
+        p->userhash.load_factor = ((double) p->weight) / (double) W;
+
+        if (floor(p->userhash.load_factor * 1000.0) == 0.0)
+            p->userhash.load_factor = 0.0;
+
+        /* add it to our list of peers */
+        *P++ = cbdataReference(p);
+    }
+
+    /* Sort our list on weight */
+    qsort(userhash_peers, n_userhash_peers, sizeof(*userhash_peers), peerSortWeight);
+
+    /* Calculate the load factor multipliers X_k
+     *
+     * X_1 = pow ((K*p_1), (1/K))
+     * X_k = ([K-k+1] * [P_k - P_{k-1}])/(X_1 * X_2 * ... * X_{k-1})
+     * X_k += pow ((X_{k-1}, {K-k+1})
+     * X_k = pow (X_k, {1/(K-k+1)})
+     * simplified to have X_1 part of the loop
+     */
+    K = n_userhash_peers;
+
+    P_last = 0.0;		/* Empty P_0 */
+
+    Xn = 1.0;			/* Empty starting point of X_1 * X_2 * ... * X_{x-1} */
+
+    X_last = 0.0;		/* Empty X_0, nullifies the first pow statement */
+
+    for (k = 1; k <= K; k++) {
+        double Kk1 = (double) (K - k + 1);
+        p = userhash_peers[k - 1];
+        p->userhash.load_multiplier = (Kk1 * (p->userhash.load_factor - P_last)) / Xn;
+        p->userhash.load_multiplier += pow(X_last, Kk1);
+        p->userhash.load_multiplier = pow(p->userhash.load_multiplier, 1.0 / Kk1);
+        Xn *= p->userhash.load_multiplier;
+        X_last = p->userhash.load_multiplier;
+        P_last = p->userhash.load_factor;
+    }
+}
+
+void
+peerUserHashRegisterWithCacheManager(CacheManager & manager)
+{
+    manager.registerAction("userhash", "peer userhash information", peerUserHashCachemgr, 0, 1);
+}
+
+peer *
+peerUserHashSelectParent(HttpRequest * request)
+{
+    int k;
+    const char *c;
+    peer *p = NULL;
+    peer *tp;
+    unsigned int user_hash = 0;
+    unsigned int combined_hash;
+    double score;
+    double high_score = 0;
+    const char *key = NULL;
+
+    if (n_userhash_peers == 0)
+        return NULL;
+
+    if (request->auth_user_request)
+	key = request->auth_user_request->username();
+
+    if (!key)
+	return NULL;
+
+    /* calculate hash key */
+    debugs(39, 2, "peerUserHashSelectParent: Calculating hash for " << key);
+
+    for (c = key; *c != 0; c++)
+        user_hash += ROTATE_LEFT(user_hash, 19) + *c;
+
+    /* select peer */
+    for (k = 0; k < n_userhash_peers; k++) {
+        tp = userhash_peers[k];
+        combined_hash = (user_hash ^ tp->userhash.hash);
+        combined_hash += combined_hash * 0x62531965;
+        combined_hash = ROTATE_LEFT(combined_hash, 21);
+        score = combined_hash * tp->userhash.load_multiplier;
+        debugs(39, 3, "peerUserHashSelectParent: " << tp->name << " combined_hash " << combined_hash  << 
+               " score " << std::setprecision(0) << score);
+
+        if ((score > high_score) && peerHTTPOkay(tp, request)) {
+            p = tp;
+            high_score = score;
+        }
+    }
+
+    if (p)
+        debugs(39, 2, "peerUserHashSelectParent: selected " << p->name);
+
+    return p;
+}
+
+static void
+peerUserHashCachemgr(StoreEntry * sentry)
+{
+    peer *p;
+    int sumfetches = 0;
+    storeAppendPrintf(sentry, "%24s %10s %10s %10s %10s\n",
+                      "Hostname",
+                      "Hash",
+                      "Multiplier",
+                      "Factor",
+                      "Actual");
+
+    for (p = Config.peers; p; p = p->next)
+        sumfetches += p->stats.fetches;
+
+    for (p = Config.peers; p; p = p->next) {
+        storeAppendPrintf(sentry, "%24s %10x %10f %10f %10f\n",
+                          p->name, p->userhash.hash,
+                          p->userhash.load_multiplier,
+                          p->userhash.load_factor,
+                          sumfetches ? (double) p->stats.fetches / sumfetches : -1.0);
+    }
+}

=== modified file 'src/protos.h'
--- src/protos.h	2008-07-11 19:32:10 +0000
+++ src/protos.h	2008-07-11 20:26:11 +0000
@@ -729,9 +729,16 @@
 SQUIDCEXTERN int internalHostnameIs(const char *);
 
 SQUIDCEXTERN void carpInit(void);
-extern void carpRegisterWithCacheManager(CacheManager & manager);
+SQUIDCEXTERN void carpRegisterWithCacheManager(CacheManager & manager);
 SQUIDCEXTERN peer *carpSelectParent(HttpRequest *);
 
+SQUIDCEXTERN void peerUserHashInit(void);
+SQUIDCEXTERN void peerUserHashRegisterWithCacheManager(CacheManager & manager);
+SQUIDCEXTERN peer * peerUserHashSelectParent(HttpRequest * request);
+
+SQUIDCEXTERN void peerSourceHashInit(void);
+SQUIDCEXTERN void peerSourceHashRegisterWithCacheManager(CacheManager & manager);
+SQUIDCEXTERN peer * peerSourceHashSelectParent(HttpRequest * request);
 
 #if USE_LEAKFINDER
 SQUIDCEXTERN void leakInit(void);

=== modified file 'src/structs.h'
--- src/structs.h	2008-07-11 19:32:10 +0000
+++ src/structs.h	2008-07-11 20:20:31 +0000
@@ -930,6 +930,8 @@
 #endif
         unsigned int allow_miss:1;
         unsigned int carp:1;
+        unsigned int userhash:1;
+        unsigned int sourcehash:1;
         unsigned int originserver:1;
     } options;
 
@@ -972,6 +974,20 @@
         double load_factor;	/* normalized weight value */
     } carp;
 
+    struct
+    {
+        unsigned int hash;
+        double load_multiplier;
+        double load_factor;	/* normalized weight value */
+    } userhash;
+
+    struct
+    {
+        unsigned int hash;
+        double load_multiplier;
+        double load_factor;	/* normalized weight value */
+    } sourcehash;
+
     char *login;		/* Proxy authorization */
     time_t connect_timeout;
     int max_conn;

# Begin bundle
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
YcMfMafna0086P/i7kinChIM9s5xYA==

Reply via email to