2 * DEBUG: section 93 ICAP (RFC 3507) Client
6 #include "adaptation/Answer.h"
7 #include "adaptation/icap/Config.h"
8 #include "adaptation/icap/ModXact.h"
9 #include "adaptation/icap/Options.h"
10 #include "adaptation/icap/OptXact.h"
11 #include "adaptation/icap/ServiceRep.h"
12 #include "base/TextException.h"
13 #include "comm/Connection.h"
14 #include "ConfigParser.h"
17 #include "HttpReply.h"
20 #include "SquidTime.h"
22 CBDATA_NAMESPACED_CLASS_INIT(Adaptation::Icap
, ServiceRep
);
24 Adaptation::Icap::ServiceRep::ServiceRep(const ServiceConfigPointer
&svcCfg
):
25 AsyncJob("Adaptation::Icap::ServiceRep"), Adaptation::Service(svcCfg
),
26 theOptions(NULL
), theOptionsFetcher(0), theLastUpdate(0),
29 connOverloadReported(false),
31 isSuspended(0), notifying(false),
32 updateScheduled(false),
33 wasAnnouncedUp(true), // do not announce an "up" service at startup
37 theIdleConns
= new IdleConnList("ICAP Service", NULL
);
40 Adaptation::Icap::ServiceRep::~ServiceRep()
43 Must(!theOptionsFetcher
);
48 Adaptation::Icap::ServiceRep::finalize()
50 Adaptation::Service::finalize();
52 // use /etc/services or default port if needed
53 const bool have_port
= cfg().port
>= 0;
55 struct servent
*serv
= getservbyname("icap", "tcp");
58 writeableCfg().port
= htons(serv
->s_port
);
60 writeableCfg().port
= 1344;
64 theSessionFailures
.configure(TheConfig
.oldest_service_failure
> 0 ?
65 TheConfig
.oldest_service_failure
: -1);
68 void Adaptation::Icap::ServiceRep::noteFailure()
70 const int failures
= theSessionFailures
.count(1);
71 debugs(93,4, HERE
<< " failure " << failures
<< " out of " <<
72 TheConfig
.service_failure_limit
<< " allowed in " <<
73 TheConfig
.oldest_service_failure
<< "sec " << status());
78 if (TheConfig
.service_failure_limit
>= 0 &&
79 failures
> TheConfig
.service_failure_limit
)
80 suspend("too many failures");
82 // TODO: Should bypass setting affect how much Squid tries to talk to
83 // the ICAP service that is currently unusable and is likely to remain
84 // so for some time? The current code says "no". Perhaps the answer
85 // should be configurable.
88 // returns a persistent or brand new connection; negative int on failures
89 Comm::ConnectionPointer
90 Adaptation::Icap::ServiceRep::getConnection(bool retriableXact
, bool &reused
)
92 Comm::ConnectionPointer connection
;
94 /* 2011-06-17: rousskov:
95 * There are two things that happen at the same time in pop(). Both are important.
96 * 1) Ensure that we can use a pconn for this transaction.
97 * 2) Ensure that the number of idle pconns does not grow without bounds.
99 * Both happen in the beginning of the transaction. Both are dictated by real-world problems.
100 * retriable means you can repeat the request if you suspect the first try failed due to a pconn race.
101 * HTTP and ICAP rules prohibit the use of pconns for non-retriable requests.
103 * If there are zero idle connections, (2) is irrelevant. (2) is only relevant when there are many
104 * idle connections and we should not open more connections without closing some idle ones,
105 * or instead of just opening a new connection and leaving idle connections as is.
106 * In other words, (2) tells us to close one FD for each new one we open due to retriable.
109 connection
= theIdleConns
->pop();
111 theIdleConns
->closeN(1);
113 reused
= Comm::IsConnOpen(connection
);
115 debugs(93,3, HERE
<< "got connection: " << connection
);
119 // pools connection if it is reusable or closes it
120 void Adaptation::Icap::ServiceRep::putConnection(const Comm::ConnectionPointer
&conn
, bool isReusable
, bool sendReset
, const char *comment
)
122 Must(Comm::IsConnOpen(conn
));
123 // do not pool an idle connection if we owe connections
124 if (isReusable
&& excessConnections() == 0) {
125 debugs(93, 3, HERE
<< "pushing pconn" << comment
);
126 commUnsetConnTimeout(conn
);
127 theIdleConns
->push(conn
);
129 debugs(93, 3, HERE
<< (sendReset
? "RST" : "FIN") << "-closing " <<
131 // comm_close called from Connection::close will clear timeout
132 // TODO: add "bool sendReset = false" to Connection::close()?
134 comm_reset_close(conn
);
139 Must(theBusyConns
> 0);
141 // a connection slot released. Check if there are waiters....
145 // a wrapper to avoid exposing theIdleConns
146 void Adaptation::Icap::ServiceRep::noteConnectionUse(const Comm::ConnectionPointer
&conn
)
148 Must(Comm::IsConnOpen(conn
));
149 fd_table
[conn
->fd
].noteUse(NULL
); // pconn re-use but not via PconnPool API
152 void Adaptation::Icap::ServiceRep::noteConnectionFailed(const char *comment
)
154 debugs(93, 3, HERE
<< "Connection failed: " << comment
);
158 void Adaptation::Icap::ServiceRep::setMaxConnections()
160 if (cfg().maxConn
>= 0)
161 theMaxConnections
= cfg().maxConn
;
162 else if (theOptions
&& theOptions
->max_connections
>= 0)
163 theMaxConnections
= theOptions
->max_connections
;
165 theMaxConnections
= -1;
169 if (::Config
.workers
> 1 )
170 theMaxConnections
/= ::Config
.workers
;
173 int Adaptation::Icap::ServiceRep::availableConnections() const
175 if (theMaxConnections
< 0)
178 // we are available if we can open or reuse connections
179 // in other words, if we will not create debt
180 int available
= max(0, theMaxConnections
- theBusyConns
);
182 if (!available
&& !connOverloadReported
) {
183 debugs(93, DBG_IMPORTANT
, "WARNING: ICAP Max-Connections limit " <<
184 "exceeded for service " << cfg().uri
<< ". Open connections now: " <<
185 theBusyConns
+ theIdleConns
->count() << ", including " <<
186 theIdleConns
->count() << " idle persistent connections.");
187 connOverloadReported
= true;
190 if (cfg().onOverload
== srvForce
)
196 // The number of connections which excess the Max-Connections limit
197 int Adaptation::Icap::ServiceRep::excessConnections() const
199 if (theMaxConnections
< 0)
202 // Waiters affect the number of needed connections but a needed
203 // connection may still be excessive from Max-Connections p.o.v.
204 // so we should not account for waiting transaction needs here.
205 const int debt
= theBusyConns
+ theIdleConns
->count() - theMaxConnections
;
212 void Adaptation::Icap::ServiceRep::noteGoneWaiter()
216 // in case the notified transaction did not take the connection slot
220 // called when a connection slot may become available
221 void Adaptation::Icap::ServiceRep::busyCheckpoint()
223 if (theNotificationWaiters
.empty()) // nobody is waiting for a slot
227 int available
= availableConnections();
230 // It is possible to have waiters when no limit on connections exist in
231 // case of reconfigure or because new Options received.
232 // In this case, notify all waiting transactions.
233 freed
= theNotificationWaiters
.size();
235 // avoid notifying more waiters than there will be available slots
236 const int notifiedWaiters
= theAllWaiters
- theNotificationWaiters
.size();
237 freed
= available
- notifiedWaiters
;
240 debugs(93,7, HERE
<< "Available connections: " << available
<<
241 " freed slots: " << freed
<<
242 " waiting in queue: " << theNotificationWaiters
.size());
244 while (freed
> 0 && !theNotificationWaiters
.empty()) {
245 Client i
= theNotificationWaiters
.front();
246 theNotificationWaiters
.pop_front();
247 ScheduleCallHere(i
.callback
);
253 void Adaptation::Icap::ServiceRep::suspend(const char *reason
)
256 debugs(93,4, HERE
<< "keeping suspended, also for " << reason
);
258 isSuspended
= reason
;
259 debugs(93, DBG_IMPORTANT
, "suspending ICAP service for " << reason
);
260 scheduleUpdate(squid_curtime
+ TheConfig
.service_revival_delay
);
261 announceStatusChange("suspended", true);
265 bool Adaptation::Icap::ServiceRep::probed() const
267 return theLastUpdate
!= 0;
270 bool Adaptation::Icap::ServiceRep::hasOptions() const
272 return theOptions
&& theOptions
->valid() && theOptions
->fresh();
275 bool Adaptation::Icap::ServiceRep::up() const
277 return !isSuspended
&& hasOptions();
280 bool Adaptation::Icap::ServiceRep::availableForNew() const
283 int available
= availableConnections();
287 return (available
- theAllWaiters
> 0);
290 bool Adaptation::Icap::ServiceRep::availableForOld() const
294 int available
= availableConnections();
295 return (available
!= 0); // it is -1 (no limit) or has available slots
298 bool Adaptation::Icap::ServiceRep::wantsUrl(const String
&urlPath
) const
301 return theOptions
->transferKind(urlPath
) != Adaptation::Icap::Options::xferIgnore
;
304 bool Adaptation::Icap::ServiceRep::wantsPreview(const String
&urlPath
, size_t &wantedSize
) const
308 if (theOptions
->preview
< 0)
311 if (theOptions
->transferKind(urlPath
) != Adaptation::Icap::Options::xferPreview
)
314 wantedSize
= theOptions
->preview
;
319 bool Adaptation::Icap::ServiceRep::allows204() const
322 return true; // in the future, we may have ACLs to prevent 204s
325 bool Adaptation::Icap::ServiceRep::allows206() const
328 if (theOptions
->allow206
)
329 return true; // in the future, we may have ACLs to prevent 206s
334 void ServiceRep_noteTimeToUpdate(void *data
)
336 Adaptation::Icap::ServiceRep
*service
= static_cast<Adaptation::Icap::ServiceRep
*>(data
);
338 service
->noteTimeToUpdate();
341 void Adaptation::Icap::ServiceRep::noteTimeToUpdate()
344 updateScheduled
= false;
346 if (detached() || theOptionsFetcher
.set()) {
347 debugs(93,5, HERE
<< "ignores options update " << status());
351 debugs(93,5, HERE
<< "performs a regular options update " << status());
352 startGettingOptions();
357 void Adaptation::Icap::ServiceRep_noteTimeToNotify(void *data
)
359 Adaptation::Icap::ServiceRep
*service
= static_cast<Adaptation::Icap::ServiceRep
*>(data
);
361 service
->noteTimeToNotify();
365 void Adaptation::Icap::ServiceRep::noteTimeToNotify()
369 debugs(93,7, HERE
<< "notifies " << theClients
.size() << " clients " <<
372 // note: we must notify even if we are invalidated
376 while (!theClients
.empty()) {
377 Client i
= theClients
.pop_back();
378 ScheduleCallHere(i
.callback
);
385 void Adaptation::Icap::ServiceRep::callWhenAvailable(AsyncCall::Pointer
&cb
, bool priority
)
387 debugs(93,8, "ICAPServiceRep::callWhenAvailable");
390 Must(!theIdleConns
->count()); // or we should not be waiting
393 i
.service
= Pointer(this);
396 theNotificationWaiters
.push_front(i
);
398 theNotificationWaiters
.push_back(i
);
403 void Adaptation::Icap::ServiceRep::callWhenReady(AsyncCall::Pointer
&cb
)
407 debugs(93,5, HERE
<< "Adaptation::Icap::Service is asked to call " << *cb
<<
408 " when ready " << status());
410 Must(!broken()); // we do not wait for a broken service
413 i
.service
= Pointer(this); // TODO: is this really needed?
415 theClients
.push_back(i
);
417 if (theOptionsFetcher
.set() || notifying
)
418 return; // do nothing, we will be picked up in noteTimeToNotify()
420 if (needNewOptions())
421 startGettingOptions();
423 scheduleNotification();
426 void Adaptation::Icap::ServiceRep::scheduleNotification()
428 debugs(93,7, HERE
<< "will notify " << theClients
.size() << " clients");
429 CallJobHere(93, 5, this, Adaptation::Icap::ServiceRep
, noteTimeToNotify
);
432 bool Adaptation::Icap::ServiceRep::needNewOptions() const
434 return !detached() && !up();
437 void Adaptation::Icap::ServiceRep::changeOptions(Adaptation::Icap::Options
*newOptions
)
439 debugs(93,8, HERE
<< "changes options from " << theOptions
<< " to " <<
440 newOptions
<< ' ' << status());
443 theOptions
= newOptions
;
444 theSessionFailures
.clear();
446 theLastUpdate
= squid_curtime
;
449 announceStatusChange("down after an options fetch failure", true);
452 void Adaptation::Icap::ServiceRep::checkOptions()
454 if (theOptions
== NULL
)
457 if (!theOptions
->valid()) {
458 debugs(93, DBG_IMPORTANT
, "WARNING: Squid got an invalid ICAP OPTIONS response " <<
459 "from service " << cfg().uri
<< "; error: " << theOptions
->error
);
464 * Issue a warning if the ICAP server returned methods in the
465 * options response that don't match the method from squid.conf.
468 if (!theOptions
->methods
.empty()) {
469 bool method_found
= false;
471 Vector
<ICAP::Method
>::iterator iter
= theOptions
->methods
.begin();
473 while (iter
!= theOptions
->methods
.end()) {
475 if (*iter
== cfg().method
) {
480 method_list
.append(ICAP::methodStr(*iter
));
481 method_list
.append(" ", 1);
486 debugs(93, DBG_IMPORTANT
, "WARNING: Squid is configured to use ICAP method " <<
488 " for service " << cfg().uri
<<
489 " but OPTIONS response declares the methods are " << method_list
);
494 * Check the ICAP server's date header for clock skew
496 const int skew
= (int)(theOptions
->timestamp() - squid_curtime
);
497 if (abs(skew
) > theOptions
->ttl()) {
498 // TODO: If skew is negative, the option will be considered down
499 // because of stale options. We should probably change this.
500 debugs(93, DBG_IMPORTANT
, "ICAP service's clock is skewed by " << skew
<<
501 " seconds: " << cfg().uri
);
505 void Adaptation::Icap::ServiceRep::announceStatusChange(const char *downPhrase
, bool important
) const
507 if (wasAnnouncedUp
== up()) // no significant changes to announce
510 const char *what
= cfg().bypass
? "optional" : "essential";
511 const char *state
= wasAnnouncedUp
? downPhrase
: "up";
512 const int level
= important
? 1 :2;
513 debugs(93,level
, what
<< " ICAP service is " << state
<< ": " <<
514 cfg().uri
<< ' ' << status());
516 wasAnnouncedUp
= !wasAnnouncedUp
;
519 // we are receiving ICAP OPTIONS response headers here or NULL on failures
520 void Adaptation::Icap::ServiceRep::noteAdaptationAnswer(const Answer
&answer
)
522 Must(initiated(theOptionsFetcher
));
523 clearAdaptation(theOptionsFetcher
);
525 if (answer
.kind
== Answer::akError
) {
526 debugs(93,3, HERE
<< "failed to fetch options " << status());
531 Must(answer
.kind
== Answer::akForward
); // no akBlock for OPTIONS requests
532 HttpMsg
*msg
= answer
.message
;
535 debugs(93,5, HERE
<< "is interpreting new options " << status());
537 Adaptation::Icap::Options
*newOptions
= NULL
;
538 if (HttpReply
*r
= dynamic_cast<HttpReply
*>(msg
)) {
539 newOptions
= new Adaptation::Icap::Options
;
540 newOptions
->configure(r
);
542 debugs(93, DBG_IMPORTANT
, "ICAP service got wrong options message " << status());
545 handleNewOptions(newOptions
);
548 // we (a) must keep trying to get OPTIONS and (b) are RefCounted so we
549 // must keep our job alive (XXX: until nobody needs us)
550 void Adaptation::Icap::ServiceRep::callException(const std::exception
&e
)
552 clearAdaptation(theOptionsFetcher
);
553 debugs(93,2, "ICAP probably failed to fetch options (" << e
.what() <<
558 void Adaptation::Icap::ServiceRep::handleNewOptions(Adaptation::Icap::Options
*newOptions
)
560 // new options may be NULL
561 changeOptions(newOptions
);
563 debugs(93,3, HERE
<< "got new options and is now " << status());
565 scheduleUpdate(optionsFetchTime());
567 // XXX: this whole feature bases on the false assumption a service only has one IP
569 const int excess
= excessConnections();
570 // if we owe connections and have idle pconns, close the latter
571 if (excess
&& theIdleConns
->count() > 0) {
572 const int n
= min(excess
, theIdleConns
->count());
573 debugs(93,5, HERE
<< "closing " << n
<< " pconns to relief debt");
574 theIdleConns
->closeN(n
);
577 scheduleNotification();
580 void Adaptation::Icap::ServiceRep::startGettingOptions()
582 Must(!theOptionsFetcher
);
583 debugs(93,6, HERE
<< "will get new options " << status());
585 // XXX: "this" here is "self"; works until refcounting API changes
586 theOptionsFetcher
= initiateAdaptation(
587 new Adaptation::Icap::OptXactLauncher(this));
588 // TODO: timeout in case Adaptation::Icap::OptXact never calls us back?
589 // Such a timeout should probably be a generic AsyncStart feature.
592 void Adaptation::Icap::ServiceRep::scheduleUpdate(time_t when
)
594 if (updateScheduled
) {
595 debugs(93,7, HERE
<< "reschedules update");
596 // XXX: check whether the event is there because AR saw
597 // an unreproducible eventDelete assertion on 2007/06/18
598 if (eventFind(&ServiceRep_noteTimeToUpdate
, this))
599 eventDelete(&ServiceRep_noteTimeToUpdate
, this);
601 debugs(93, DBG_IMPORTANT
, "XXX: ICAP service lost an update event.");
602 updateScheduled
= false;
605 debugs(93,7, HERE
<< "raw OPTIONS fetch at " << when
<< " or in " <<
606 (when
- squid_curtime
) << " sec");
607 debugs(93,9, HERE
<< "last fetched at " << theLastUpdate
<< " or " <<
608 (squid_curtime
- theLastUpdate
) << " sec ago");
610 /* adjust update time to prevent too-frequent updates */
612 if (when
< squid_curtime
)
613 when
= squid_curtime
;
615 // XXX: move hard-coded constants from here to Adaptation::Icap::TheConfig
616 const int minUpdateGap
= 30; // seconds
617 if (when
< theLastUpdate
+ minUpdateGap
)
618 when
= theLastUpdate
+ minUpdateGap
;
620 const int delay
= when
- squid_curtime
;
621 debugs(93,5, HERE
<< "will fetch OPTIONS in " << delay
<< " sec");
623 eventAdd("Adaptation::Icap::ServiceRep::noteTimeToUpdate",
624 &ServiceRep_noteTimeToUpdate
, this, delay
, 0, true);
625 updateScheduled
= true;
628 // returns absolute time when OPTIONS should be fetched
630 Adaptation::Icap::ServiceRep::optionsFetchTime() const
632 if (theOptions
&& theOptions
->valid()) {
633 const time_t expire
= theOptions
->expire();
634 debugs(93,7, HERE
<< "options expire on " << expire
<< " >= " << squid_curtime
);
636 // conservative estimate of how long the OPTIONS transaction will take
637 // XXX: move hard-coded constants from here to Adaptation::Icap::TheConfig
638 const int expectedWait
= 20; // seconds
640 // Unknown or invalid (too small) expiration times should not happen.
641 // Adaptation::Icap::Options should use the default TTL, and ICAP servers should not
642 // send invalid TTLs, but bugs and attacks happen.
643 if (expire
< expectedWait
)
644 return squid_curtime
;
646 return expire
- expectedWait
; // before the current options expire
649 // use revival delay as "expiration" time for a service w/o valid options
650 return squid_curtime
+ TheConfig
.service_revival_delay
;
653 Adaptation::Initiate
*
654 Adaptation::Icap::ServiceRep::makeXactLauncher(HttpMsg
*virgin
,
657 return new Adaptation::Icap::ModXactLauncher(virgin
, cause
, this);
660 // returns a temporary string depicting service status, for debugging
661 const char *Adaptation::Icap::ServiceRep::status() const
671 buf
.append("down", 4);
673 buf
.append(",susp", 5);
676 buf
.append(",!opt", 5);
677 else if (!theOptions
->valid())
678 buf
.append(",!valid", 7);
679 else if (!theOptions
->fresh())
680 buf
.append(",stale", 6);
684 buf
.append(",detached", 9);
686 if (theOptionsFetcher
.set())
687 buf
.append(",fetch", 6);
690 buf
.append(",notif", 6);
692 if (const int failures
= theSessionFailures
.remembered())
693 buf
.Printf(",fail%d", failures
);
698 return buf
.content();
701 void Adaptation::Icap::ServiceRep::detach()
703 debugs(93,3, HERE
<< "detaching ICAP service: " << cfg().uri
<<
708 bool Adaptation::Icap::ServiceRep::detached() const
713 Adaptation::Icap::ConnWaiterDialer::ConnWaiterDialer(const CbcPointer
<ModXact
> &xact
,
714 Adaptation::Icap::ConnWaiterDialer::Parent::Method aHandler
):
715 Parent(xact
, aHandler
)
717 theService
= &xact
->service();
718 theService
->noteNewWaiter();
721 Adaptation::Icap::ConnWaiterDialer::ConnWaiterDialer(const Adaptation::Icap::ConnWaiterDialer
&aConnWaiter
): Parent(aConnWaiter
)
723 theService
= aConnWaiter
.theService
;
724 theService
->noteNewWaiter();
727 Adaptation::Icap::ConnWaiterDialer::~ConnWaiterDialer()
729 theService
->noteGoneWaiter();