2 * $Id: peer_select.cc,v 1.149 2008/01/20 08:54:28 amosjeffries Exp $
4 * DEBUG: section 44 Peer Selection Algorithm
5 * AUTHOR: Duane Wessels
7 * SQUID Web Proxy Cache http://www.squid-cache.org/
8 * ----------------------------------------------------------
10 * Squid is the result of efforts by numerous individuals from
11 * the Internet community; see the CONTRIBUTORS file for full
12 * details. Many organizations have provided support for Squid's
13 * development; see the SPONSORS file for full details. Squid is
14 * Copyrighted (C) 2001 by the Regents of the University of
15 * California; see the COPYRIGHT file for full details. Squid
16 * incorporates software developed and/or copyrighted by other
17 * sources; see the CREDITS file for full details.
19 * This program is free software; you can redistribute it and/or modify
20 * it under the terms of the GNU General Public License as published by
21 * the Free Software Foundation; either version 2 of the License, or
22 * (at your option) any later version.
24 * This program is distributed in the hope that it will be useful,
25 * but WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27 * GNU General Public License for more details.
29 * You should have received a copy of the GNU General Public License
30 * along with this program; if not, write to the Free Software
31 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
37 #include "PeerSelectState.h"
40 #include "HttpRequest.h"
41 #include "ACLChecklist.h"
44 #include "SquidTime.h"
46 const char *hier_strings
[] =
56 "CLOSEST_PARENT_MISS",
76 static const char *DirectStr
[] =
84 static void peerSelectFoo(ps_state
*);
85 static void peerPingTimeout(void *data
);
86 static void peerSelectCallback(ps_state
* psstate
);
87 static IRCB peerHandlePingReply
;
88 static void peerSelectStateFree(ps_state
* psstate
);
89 static void peerIcpParentMiss(peer
*, icp_common_t
*, ps_state
*);
91 static void peerHtcpParentMiss(peer
*, htcpReplyData
*, ps_state
*);
92 static void peerHandleHtcpReply(peer
*, peer_t
, htcpReplyData
*, void *);
94 static int peerCheckNetdbDirect(ps_state
* psstate
);
95 static void peerGetSomeNeighbor(ps_state
*);
96 static void peerGetSomeNeighborReplies(ps_state
*);
97 static void peerGetSomeDirect(ps_state
*);
98 static void peerGetSomeParent(ps_state
*);
99 static void peerGetAllParents(ps_state
*);
100 static void peerAddFwdServer(FwdServer
**, peer
*, hier_code
);
102 CBDATA_CLASS_INIT(ps_state
);
105 peerSelectStateFree(ps_state
* psstate
)
107 if (psstate
->acl_checklist
) {
108 debugs(44, 1, "calling aclChecklistFree() from peerSelectStateFree");
109 delete (psstate
->acl_checklist
);
112 HTTPMSGUNLOCK(psstate
->request
);
114 if (psstate
->entry
) {
115 assert(psstate
->entry
->ping_status
!= PING_WAITING
);
116 psstate
->entry
->unlock();
117 psstate
->entry
= NULL
;
124 peerSelectIcpPing(HttpRequest
* request
, int direct
, StoreEntry
* entry
)
128 assert(entry
->ping_status
== PING_NONE
);
129 assert(direct
!= DIRECT_YES
);
130 debugs(44, 3, "peerSelectIcpPing: " << entry
->url() );
132 if (!request
->flags
.hierarchical
&& direct
!= DIRECT_NO
)
135 if (EBIT_TEST(entry
->flags
, KEY_PRIVATE
) && !neighbors_do_private_keys
)
136 if (direct
!= DIRECT_NO
)
139 n
= neighborsCount(request
);
141 debugs(44, 3, "peerSelectIcpPing: counted " << n
<< " neighbors");
148 peerSelect(HttpRequest
* request
,
156 debugs(44, 3, "peerSelect: " << entry
->url() );
158 debugs(44, 3, "peerSelect: " << RequestMethodStr(request
->method
));
160 psstate
= new ps_state
;
162 psstate
->request
= HTTPMSGLOCK(request
);
164 psstate
->entry
= entry
;
166 psstate
->callback
= callback
;
168 psstate
->callback_data
= cbdataReference(callback_data
);
170 psstate
->direct
= DIRECT_UNKNOWN
;
172 #if USE_CACHE_DIGESTS
174 request
->hier
.peer_select_start
= current_time
;
179 psstate
->entry
->lock();
181 peerSelectFoo(psstate
);
185 peerCheckNeverDirectDone(int answer
, void *data
)
187 ps_state
*psstate
= (ps_state
*) data
;
188 psstate
->acl_checklist
= NULL
;
189 debugs(44, 3, "peerCheckNeverDirectDone: " << answer
);
190 psstate
->never_direct
= answer
? 1 : -1;
191 peerSelectFoo(psstate
);
195 peerCheckAlwaysDirectDone(int answer
, void *data
)
197 ps_state
*psstate
= (ps_state
*)data
;
198 psstate
->acl_checklist
= NULL
;
199 debugs(44, 3, "peerCheckAlwaysDirectDone: " << answer
);
200 psstate
->always_direct
= answer
? 1 : -1;
201 peerSelectFoo(psstate
);
205 peerSelectCallback(ps_state
* psstate
)
207 StoreEntry
*entry
= psstate
->entry
;
208 FwdServer
*fs
= psstate
->servers
;
213 debugs(44, 3, "peerSelectCallback: " << entry
->url() );
215 if (entry
->ping_status
== PING_WAITING
)
216 eventDelete(peerPingTimeout
, psstate
);
218 entry
->ping_status
= PING_DONE
;
222 debugs(44, 1, "Failed to select source for '" << entry
->url() << "'" );
223 debugs(44, 1, " always_direct = " << psstate
->always_direct
);
224 debugs(44, 1, " never_direct = " << psstate
->never_direct
);
225 debugs(44, 1, " timedout = " << psstate
->ping
.timedout
);
228 psstate
->ping
.stop
= current_time
;
229 psstate
->request
->hier
.ping
= psstate
->ping
;
230 callback
= psstate
->callback
;
231 psstate
->callback
= NULL
;
233 if (cbdataReferenceValidDone(psstate
->callback_data
, &cbdata
)) {
234 psstate
->servers
= NULL
;
235 callback(fs
, cbdata
);
238 peerSelectStateFree(psstate
);
242 peerCheckNetdbDirect(ps_state
* psstate
)
248 if (psstate
->direct
== DIRECT_NO
)
251 myrtt
= netdbHostRtt(psstate
->request
->GetHost());
253 debugs(44, 3, "peerCheckNetdbDirect: MY RTT = " << myrtt
<< " msec");
254 debugs(44, 3, "peerCheckNetdbDirect: minimum_direct_rtt = " << Config
.minDirectRtt
<< " msec");
257 if (myrtt
&& myrtt
<= Config
.minDirectRtt
)
260 myhops
= netdbHostHops(psstate
->request
->GetHost());
262 debugs(44, 3, "peerCheckNetdbDirect: MY hops = " << myhops
);
263 debugs(44, 3, "peerCheckNetdbDirect: minimum_direct_hops = " << Config
.minDirectHops
);
266 if (myhops
&& myhops
<= Config
.minDirectHops
)
269 p
= whichPeer(psstate
->closest_parent_miss
);
274 debugs(44, 3, "peerCheckNetdbDirect: closest_parent_miss RTT = " << psstate
->ping
.p_rtt
<< " msec");
276 if (myrtt
&& myrtt
<= psstate
->ping
.p_rtt
)
283 peerSelectFoo(ps_state
* ps
)
285 StoreEntry
*entry
= ps
->entry
;
286 HttpRequest
*request
= ps
->request
;
287 debugs(44, 3, "peerSelectFoo: '" << RequestMethodStr(request
->method
) << " " << request
->GetHost() << "'");
289 if (ps
->direct
== DIRECT_UNKNOWN
) {
290 if (ps
->always_direct
== 0 && Config
.accessList
.AlwaysDirect
) {
291 ps
->acl_checklist
= aclChecklistCreate(
292 Config
.accessList
.AlwaysDirect
,
295 ps
->acl_checklist
->nonBlockingCheck(peerCheckAlwaysDirectDone
,
298 } else if (ps
->always_direct
> 0) {
299 ps
->direct
= DIRECT_YES
;
300 } else if (ps
->never_direct
== 0 && Config
.accessList
.NeverDirect
) {
301 ps
->acl_checklist
= aclChecklistCreate(
302 Config
.accessList
.NeverDirect
,
305 ps
->acl_checklist
->nonBlockingCheck(peerCheckNeverDirectDone
,
308 } else if (ps
->never_direct
> 0) {
309 ps
->direct
= DIRECT_NO
;
310 } else if (request
->flags
.accelerated
) {
311 ps
->direct
= DIRECT_NO
;
312 } else if (request
->flags
.loopdetect
) {
313 ps
->direct
= DIRECT_YES
;
314 } else if (peerCheckNetdbDirect(ps
)) {
315 ps
->direct
= DIRECT_YES
;
317 ps
->direct
= DIRECT_MAYBE
;
320 debugs(44, 3, "peerSelectFoo: direct = " << DirectStr
[ps
->direct
]);
325 } else if (entry
->ping_status
== PING_NONE
) {
326 peerGetSomeNeighbor(ps
);
328 if (entry
->ping_status
== PING_WAITING
)
330 } else if (entry
->ping_status
== PING_WAITING
) {
331 peerGetSomeNeighborReplies(ps
);
332 entry
->ping_status
= PING_DONE
;
335 switch (ps
->direct
) {
338 peerGetSomeDirect(ps
);
342 peerGetSomeParent(ps
);
343 peerGetAllParents(ps
);
348 if (Config
.onoff
.prefer_direct
)
349 peerGetSomeDirect(ps
);
351 if (request
->flags
.hierarchical
|| !Config
.onoff
.nonhierarchical_direct
)
352 peerGetSomeParent(ps
);
354 if (!Config
.onoff
.prefer_direct
)
355 peerGetSomeDirect(ps
);
360 peerSelectCallback(ps
);
364 * peerGetSomeNeighbor
366 * Selects a neighbor (parent or sibling) based on one of the
370 * Netdb RTT estimates
374 peerGetSomeNeighbor(ps_state
* ps
)
376 StoreEntry
*entry
= ps
->entry
;
377 HttpRequest
*request
= ps
->request
;
379 hier_code code
= HIER_NONE
;
380 assert(entry
->ping_status
== PING_NONE
);
382 if (ps
->direct
== DIRECT_YES
) {
383 entry
->ping_status
= PING_DONE
;
387 #if USE_CACHE_DIGESTS
388 if ((p
= neighborsDigestSelect(request
))) {
389 if (neighborType(p
, request
) == PEER_PARENT
)
390 code
= CD_PARENT_HIT
;
392 code
= CD_SIBLING_HIT
;
395 if ((p
= netdbClosestParent(request
))) {
396 code
= CLOSEST_PARENT
;
397 } else if (peerSelectIcpPing(request
, ps
->direct
, entry
)) {
398 debugs(44, 3, "peerSelect: Doing ICP pings");
399 ps
->ping
.start
= current_time
;
400 ps
->ping
.n_sent
= neighborsUdpPing(request
,
404 &ps
->ping
.n_replies_expected
,
407 if (ps
->ping
.n_sent
== 0)
408 debugs(44, 0, "WARNING: neighborsUdpPing returned 0");
409 debugs(44, 3, "peerSelect: " << ps
->ping
.n_replies_expected
<<
410 " ICP replies expected, RTT " << ps
->ping
.timeout
<<
414 if (ps
->ping
.n_replies_expected
> 0) {
415 entry
->ping_status
= PING_WAITING
;
416 eventAdd("peerPingTimeout",
419 0.001 * ps
->ping
.timeout
,
425 if (code
!= HIER_NONE
) {
427 debugs(44, 3, "peerSelect: " << hier_strings
[code
] << "/" << p
->host
);
428 peerAddFwdServer(&ps
->servers
, p
, code
);
431 entry
->ping_status
= PING_DONE
;
435 * peerGetSomeNeighborReplies
437 * Selects a neighbor (parent or sibling) based on ICP/HTCP replies.
440 peerGetSomeNeighborReplies(ps_state
* ps
)
442 HttpRequest
*request
= ps
->request
;
444 hier_code code
= HIER_NONE
;
445 assert(ps
->entry
->ping_status
== PING_WAITING
);
446 assert(ps
->direct
!= DIRECT_YES
);
448 if (peerCheckNetdbDirect(ps
)) {
449 code
= CLOSEST_DIRECT
;
450 debugs(44, 3, "peerSelect: " << hier_strings
[code
] << "/" << request
->GetHost());
451 peerAddFwdServer(&ps
->servers
, NULL
, code
);
456 code
= ps
->hit_type
== PEER_PARENT
? PARENT_HIT
: SIBLING_HIT
;
459 if (!ps
->closest_parent_miss
.IsAnyAddr()) {
460 p
= whichPeer(ps
->closest_parent_miss
);
461 code
= CLOSEST_PARENT_MISS
;
462 } else if (!ps
->first_parent_miss
.IsAnyAddr()) {
463 p
= whichPeer(ps
->first_parent_miss
);
464 code
= FIRST_PARENT_MISS
;
467 if (p
&& code
!= HIER_NONE
) {
468 debugs(44, 3, "peerSelect: " << hier_strings
[code
] << "/" << p
->host
);
469 peerAddFwdServer(&ps
->servers
, p
, code
);
477 * Simply adds a 'direct' entry to the FwdServers list if this
478 * request can be forwarded directly to the origin server
481 peerGetSomeDirect(ps_state
* ps
)
483 if (ps
->direct
== DIRECT_NO
)
486 /* WAIS is not implemented natively */
487 if (ps
->request
->protocol
== PROTO_WAIS
)
490 peerAddFwdServer(&ps
->servers
, NULL
, HIER_DIRECT
);
494 peerGetSomeParent(ps_state
* ps
)
497 HttpRequest
*request
= ps
->request
;
498 hier_code code
= HIER_NONE
;
499 debugs(44, 3, "peerGetSomeParent: " << RequestMethodStr(request
->method
) << " " << request
->GetHost());
501 if (ps
->direct
== DIRECT_YES
)
504 if ((p
= getDefaultParent(request
))) {
505 code
= DEFAULT_PARENT
;
506 } else if ((p
= carpSelectParent(request
))) {
508 } else if ((p
= getRoundRobinParent(request
))) {
509 code
= ROUNDROBIN_PARENT
;
510 } else if ((p
= getWeightedRoundRobinParent(request
))) {
511 code
= ROUNDROBIN_PARENT
;
512 } else if ((p
= getFirstUpParent(request
))) {
513 code
= FIRSTUP_PARENT
;
514 } else if ((p
= getAnyParent(request
))) {
515 code
= ANY_OLD_PARENT
;
518 if (code
!= HIER_NONE
) {
519 debugs(44, 3, "peerSelect: " << hier_strings
[code
] << "/" << p
->host
);
520 peerAddFwdServer(&ps
->servers
, p
, code
);
524 /* Adds alive parents. Used as a last resort for never_direct.
527 peerGetAllParents(ps_state
* ps
)
530 HttpRequest
*request
= ps
->request
;
531 /* Add all alive parents */
533 for (p
= Config
.peers
; p
; p
= p
->next
) {
534 /* XXX: neighbors.c lacks a public interface for enumerating
535 * parents to a request so we have to dig some here..
538 if (neighborType(p
, request
) != PEER_PARENT
)
541 if (!peerHTTPOkay(p
, request
))
544 debugs(15, 3, "peerGetAllParents: adding alive parent " << p
->host
);
546 peerAddFwdServer(&ps
->servers
, p
, ANY_OLD_PARENT
);
549 /* XXX: should add dead parents here, but it is currently
550 * not possible to find out which parents are dead or which
551 * simply are not configured to handle the request.
553 /* Add default parent as a last resort */
554 if ((p
= getDefaultParent(request
))) {
555 peerAddFwdServer(&ps
->servers
, p
, DEFAULT_PARENT
);
560 peerPingTimeout(void *data
)
562 ps_state
*psstate
= (ps_state
*)data
;
563 StoreEntry
*entry
= psstate
->entry
;
566 debugs(44, 3, "peerPingTimeout: '" << entry
->url() << "'" );
568 if (!cbdataReferenceValid(psstate
->callback_data
)) {
569 /* request aborted */
570 entry
->ping_status
= PING_DONE
;
571 cbdataReferenceDone(psstate
->callback_data
);
572 peerSelectStateFree(psstate
);
576 PeerStats
.timeouts
++;
577 psstate
->ping
.timedout
= 1;
578 peerSelectFoo(psstate
);
584 memset(&PeerStats
, '\0', sizeof(PeerStats
));
585 assert(sizeof(hier_strings
) == (HIER_MAX
+ 1) * sizeof(char *));
589 peerIcpParentMiss(peer
* p
, icp_common_t
* header
, ps_state
* ps
)
594 if (Config
.onoff
.query_icmp
) {
595 if (header
->flags
& ICP_FLAG_SRC_RTT
) {
596 rtt
= header
->pad
& 0xFFFF;
597 hops
= (header
->pad
>> 16) & 0xFFFF;
599 if (rtt
> 0 && rtt
< 0xFFFF)
600 netdbUpdatePeer(ps
->request
, p
, rtt
, hops
);
602 if (rtt
&& (ps
->ping
.p_rtt
== 0 || rtt
< ps
->ping
.p_rtt
)) {
603 ps
->closest_parent_miss
= p
->in_addr
;
604 ps
->ping
.p_rtt
= rtt
;
609 /* if closest-only is set, then don't allow FIRST_PARENT_MISS */
610 if (p
->options
.closest_only
)
613 /* set FIRST_MISS if there is no CLOSEST parent */
614 if (!ps
->closest_parent_miss
.IsAnyAddr())
617 rtt
= (tvSubMsec(ps
->ping
.start
, current_time
) - p
->basetime
) / p
->weight
;
622 if (ps
->first_parent_miss
.IsAnyAddr() || rtt
< ps
->ping
.w_rtt
) {
623 ps
->first_parent_miss
= p
->in_addr
;
624 ps
->ping
.w_rtt
= rtt
;
629 peerHandleIcpReply(peer
* p
, peer_t type
, icp_common_t
* header
, void *data
)
631 ps_state
*psstate
= (ps_state
*)data
;
632 icp_opcode op
= header
->getOpCode();
633 debugs(44, 3, "peerHandleIcpReply: " << icp_opcode_str
[op
] << " " << psstate
->entry
->url() );
634 #if USE_CACHE_DIGESTS && 0
635 /* do cd lookup to count false misses */
638 peerNoteDigestLookup(request
, p
,
639 peerDigestLookup(p
, request
, psstate
->entry
));
643 psstate
->ping
.n_recv
++;
645 if (op
== ICP_MISS
|| op
== ICP_DECHO
) {
646 if (type
== PEER_PARENT
)
647 peerIcpParentMiss(p
, header
, psstate
);
648 } else if (op
== ICP_HIT
) {
650 psstate
->hit_type
= type
;
651 peerSelectFoo(psstate
);
655 if (psstate
->ping
.n_recv
< psstate
->ping
.n_replies_expected
)
658 peerSelectFoo(psstate
);
663 peerHandleHtcpReply(peer
* p
, peer_t type
, htcpReplyData
* htcp
, void *data
)
665 ps_state
*psstate
= (ps_state
*)data
;
666 debugs(44, 3, "peerHandleHtcpReply: " <<
667 (htcp
->hit
? "HIT" : "MISS") << " " <<
668 psstate
->entry
->url() );
669 psstate
->ping
.n_recv
++;
673 psstate
->hit_type
= type
;
674 peerSelectFoo(psstate
);
678 if (type
== PEER_PARENT
)
679 peerHtcpParentMiss(p
, htcp
, psstate
);
681 if (psstate
->ping
.n_recv
< psstate
->ping
.n_replies_expected
)
684 peerSelectFoo(psstate
);
688 peerHtcpParentMiss(peer
* p
, htcpReplyData
* htcp
, ps_state
* ps
)
693 if (Config
.onoff
.query_icmp
) {
694 if (htcp
->cto
.rtt
> 0) {
695 rtt
= (int) htcp
->cto
.rtt
* 1000;
696 hops
= (int) htcp
->cto
.hops
* 1000;
697 netdbUpdatePeer(ps
->request
, p
, rtt
, hops
);
699 if (rtt
&& (ps
->ping
.p_rtt
== 0 || rtt
< ps
->ping
.p_rtt
)) {
700 ps
->closest_parent_miss
= p
->in_addr
;
701 ps
->ping
.p_rtt
= rtt
;
706 /* if closest-only is set, then don't allow FIRST_PARENT_MISS */
707 if (p
->options
.closest_only
)
710 /* set FIRST_MISS if there is no CLOSEST parent */
711 if (!ps
->closest_parent_miss
.IsAnyAddr())
714 rtt
= (tvSubMsec(ps
->ping
.start
, current_time
) - p
->basetime
) / p
->weight
;
719 if (ps
->first_parent_miss
.IsAnyAddr() || rtt
< ps
->ping
.w_rtt
) {
720 ps
->first_parent_miss
= p
->in_addr
;
721 ps
->ping
.w_rtt
= rtt
;
728 peerHandlePingReply(peer
* p
, peer_t type
, protocol_t proto
, void *pingdata
, void *data
)
730 if (proto
== PROTO_ICP
)
731 peerHandleIcpReply(p
, type
, (icp_common_t
*)pingdata
, data
);
735 else if (proto
== PROTO_HTCP
)
736 peerHandleHtcpReply(p
, type
, (htcpReplyData
*)pingdata
, data
);
741 debugs(44, 1, "peerHandlePingReply: unknown protocol_t " << proto
);
745 peerAddFwdServer(FwdServer
** FSVR
, peer
* p
, hier_code code
)
747 FwdServer
*fs
= (FwdServer
*)memAllocate(MEM_FWD_SERVER
);
748 debugs(44, 5, "peerAddFwdServer: adding " <<
749 (p
? p
->host
: "DIRECT") << " " <<
750 hier_strings
[code
] );
751 fs
->_peer
= cbdataReference(p
);
755 FSVR
= &(*FSVR
)->next
;
761 ps_state::operator new(size_t)
763 CBDATA_INIT_TYPE(ps_state
);
764 return cbdataAlloc(ps_state
);
767 ps_state::ps_state() : request (NULL
),
773 callback_data (NULL
),
776 closest_parent_miss(),
781 ; // no local defaults.
784 ping_data::ping_data() :
787 n_replies_expected(0),