]> git.ipfire.org Git - thirdparty/squid.git/blame - src/adaptation/icap/ServiceRep.cc
SourceFormat Enforcement
[thirdparty/squid.git] / src / adaptation / icap / ServiceRep.cc
CommitLineData
774c051c 1/*
4ac4a490 2 * Copyright (C) 1996-2017 The Squid Software Foundation and contributors
bbc27441
AJ
3 *
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
774c051c 7 */
8
bbc27441
AJ
9/* DEBUG: section 93 ICAP (RFC 3507) Client */
10
582c2af2 11#include "squid.h"
1adcebc3 12#include "adaptation/Answer.h"
3d93a84d
AJ
13#include "adaptation/icap/Config.h"
14#include "adaptation/icap/ModXact.h"
26cc52cb
AR
15#include "adaptation/icap/Options.h"
16#include "adaptation/icap/OptXact.h"
3d93a84d
AJ
17#include "adaptation/icap/ServiceRep.h"
18#include "base/TextException.h"
983983ce 19#include "comm/Connection.h"
774c051c 20#include "ConfigParser.h"
582c2af2
FC
21#include "Debug.h"
22#include "fde.h"
af69c635 23#include "globals.h"
3d93a84d 24#include "HttpReply.h"
582c2af2 25#include "ip/tools.h"
4d5904f7 26#include "SquidConfig.h"
985c86bc 27#include "SquidTime.h"
774c051c 28
1b091aec
CT
29#define DEFAULT_ICAP_PORT 1344
30#define DEFAULT_ICAPS_PORT 11344
31
26cc52cb 32CBDATA_NAMESPACED_CLASS_INIT(Adaptation::Icap, ServiceRep);
774c051c 33
6666da11 34Adaptation::Icap::ServiceRep::ServiceRep(const ServiceConfigPointer &svcCfg):
f53969cc
SM
35 AsyncJob("Adaptation::Icap::ServiceRep"), Adaptation::Service(svcCfg),
36 theOptions(NULL), theOptionsFetcher(0), theLastUpdate(0),
37 theBusyConns(0),
38 theAllWaiters(0),
39 connOverloadReported(false),
40 theIdleConns(NULL),
41 isSuspended(0), notifying(false),
42 updateScheduled(false),
43 wasAnnouncedUp(true), // do not announce an "up" service at startup
44 isDetached(false)
2dba5b8e
CT
45{
46 setMaxConnections();
fb505fa1 47 theIdleConns = new IdleConnList("ICAP Service", NULL);
2dba5b8e 48}
774c051c 49
26cc52cb 50Adaptation::Icap::ServiceRep::~ServiceRep()
774c051c 51{
fb505fa1 52 delete theIdleConns;
98a74c05 53 Must(!theOptionsFetcher);
76fc7e57 54 delete theOptions;
62c7f90e 55}
774c051c 56
62c7f90e 57void
26cc52cb 58Adaptation::Icap::ServiceRep::finalize()
62c7f90e 59{
9e008dda 60 Adaptation::Service::finalize();
774c051c 61
a68cf076 62 // use /etc/services or default port if needed
9e008dda 63 const bool have_port = cfg().port >= 0;
a68cf076 64 if (!have_port) {
1b091aec
CT
65 struct servent *serv;
66 if (cfg().protocol.caseCmp("icaps") == 0)
67 serv = getservbyname("icaps", "tcp");
68 else
69 serv = getservbyname("icap", "tcp");
774c051c 70
71 if (serv) {
d81a31f1 72 writeableCfg().port = htons(serv->s_port);
774c051c 73 } else {
1b091aec 74 writeableCfg().port = cfg().protocol.caseCmp("icaps") == 0 ? DEFAULT_ICAPS_PORT : DEFAULT_ICAP_PORT;
774c051c 75 }
76 }
8277060a 77
1b091aec
CT
78 if (cfg().protocol.caseCmp("icaps") == 0)
79 writeableCfg().secure.encryptTransport = true;
80
81 if (cfg().secure.encryptTransport) {
82 debugs(3, DBG_IMPORTANT, "Initializing service " << cfg().resource << " SSL context");
585c27eb 83 sslContext = writeableCfg().secure.createClientContext(true);
c50b35b5
AJ
84 }
85
86 if (!cfg().connectionEncryption.configured())
87 writeableCfg().connectionEncryption.defaultTo(cfg().secure.encryptTransport);
1b091aec 88
8277060a 89 theSessionFailures.configure(TheConfig.oldest_service_failure > 0 ?
a459e80a 90 TheConfig.oldest_service_failure : -1);
a68cf076 91}
774c051c 92
26cc52cb 93void Adaptation::Icap::ServiceRep::noteFailure()
9e008dda 94{
8277060a
CT
95 const int failures = theSessionFailures.count(1);
96 debugs(93,4, HERE << " failure " << failures << " out of " <<
97 TheConfig.service_failure_limit << " allowed in " <<
98 TheConfig.oldest_service_failure << "sec " << status());
1299ecbf 99
100 if (isSuspended)
101 return;
c99de607 102
26cc52cb 103 if (TheConfig.service_failure_limit >= 0 &&
8277060a 104 failures > TheConfig.service_failure_limit)
c99de607 105 suspend("too many failures");
106
107 // TODO: Should bypass setting affect how much Squid tries to talk to
9e008dda
AJ
108 // the ICAP service that is currently unusable and is likely to remain
109 // so for some time? The current code says "no". Perhaps the answer
c99de607 110 // should be configurable.
111}
112
2dba5b8e 113// returns a persistent or brand new connection; negative int on failures
983983ce
AJ
114Comm::ConnectionPointer
115Adaptation::Icap::ServiceRep::getConnection(bool retriableXact, bool &reused)
2dba5b8e 116{
9815f129 117 Comm::ConnectionPointer connection;
f915be8d
AJ
118
119 /* 2011-06-17: rousskov:
120 * There are two things that happen at the same time in pop(). Both are important.
121 * 1) Ensure that we can use a pconn for this transaction.
122 * 2) Ensure that the number of idle pconns does not grow without bounds.
123 *
124 * Both happen in the beginning of the transaction. Both are dictated by real-world problems.
125 * retriable means you can repeat the request if you suspect the first try failed due to a pconn race.
126 * HTTP and ICAP rules prohibit the use of pconns for non-retriable requests.
127 *
128 * If there are zero idle connections, (2) is irrelevant. (2) is only relevant when there are many
129 * idle connections and we should not open more connections without closing some idle ones,
130 * or instead of just opening a new connection and leaving idle connections as is.
131 * In other words, (2) tells us to close one FD for each new one we open due to retriable.
132 */
133 if (retriableXact)
fb505fa1 134 connection = theIdleConns->pop();
f915be8d 135 else
fb505fa1 136 theIdleConns->closeN(1);
2dba5b8e 137
fb505fa1
CT
138 reused = Comm::IsConnOpen(connection);
139 ++theBusyConns;
140 debugs(93,3, HERE << "got connection: " << connection);
2dba5b8e
CT
141 return connection;
142}
143
144// pools connection if it is reusable or closes it
a32c060f 145void Adaptation::Icap::ServiceRep::putConnection(const Comm::ConnectionPointer &conn, bool isReusable, bool sendReset, const char *comment)
2dba5b8e 146{
983983ce 147 Must(Comm::IsConnOpen(conn));
2dba5b8e
CT
148 // do not pool an idle connection if we owe connections
149 if (isReusable && excessConnections() == 0) {
150 debugs(93, 3, HERE << "pushing pconn" << comment);
983983ce 151 commUnsetConnTimeout(conn);
fb505fa1 152 theIdleConns->push(conn);
2dba5b8e 153 } else {
a32c060f
AR
154 debugs(93, 3, HERE << (sendReset ? "RST" : "FIN") << "-closing " <<
155 comment);
156 // comm_close called from Connection::close will clear timeout
157 // TODO: add "bool sendReset = false" to Connection::close()?
158 if (sendReset)
159 comm_reset_close(conn);
160 else
161 conn->close();
2dba5b8e
CT
162 }
163
164 Must(theBusyConns > 0);
165 --theBusyConns;
166 // a connection slot released. Check if there are waiters....
167 busyCheckpoint();
168}
169
170// a wrapper to avoid exposing theIdleConns
983983ce 171void Adaptation::Icap::ServiceRep::noteConnectionUse(const Comm::ConnectionPointer &conn)
2dba5b8e 172{
983983ce 173 Must(Comm::IsConnOpen(conn));
e8dca475 174 fd_table[conn->fd].noteUse(); // pconn re-use, albeit not via PconnPool API
2dba5b8e
CT
175}
176
fb505fa1
CT
177void Adaptation::Icap::ServiceRep::noteConnectionFailed(const char *comment)
178{
179 debugs(93, 3, HERE << "Connection failed: " << comment);
180 --theBusyConns;
181}
182
2dba5b8e
CT
183void Adaptation::Icap::ServiceRep::setMaxConnections()
184{
185 if (cfg().maxConn >= 0)
186 theMaxConnections = cfg().maxConn;
187 else if (theOptions && theOptions->max_connections >= 0)
188 theMaxConnections = theOptions->max_connections;
189 else {
190 theMaxConnections = -1;
191 return;
192 }
193
194 if (::Config.workers > 1 )
195 theMaxConnections /= ::Config.workers;
196}
197
198int Adaptation::Icap::ServiceRep::availableConnections() const
199{
200 if (theMaxConnections < 0)
201 return -1;
202
203 // we are available if we can open or reuse connections
204 // in other words, if we will not create debt
205 int available = max(0, theMaxConnections - theBusyConns);
206
207 if (!available && !connOverloadReported) {
208 debugs(93, DBG_IMPORTANT, "WARNING: ICAP Max-Connections limit " <<
209 "exceeded for service " << cfg().uri << ". Open connections now: " <<
fb505fa1
CT
210 theBusyConns + theIdleConns->count() << ", including " <<
211 theIdleConns->count() << " idle persistent connections.");
2dba5b8e
CT
212 connOverloadReported = true;
213 }
214
215 if (cfg().onOverload == srvForce)
216 return -1;
217
218 return available;
219}
220
221// The number of connections which excess the Max-Connections limit
222int Adaptation::Icap::ServiceRep::excessConnections() const
223{
224 if (theMaxConnections < 0)
225 return 0;
226
227 // Waiters affect the number of needed connections but a needed
228 // connection may still be excessive from Max-Connections p.o.v.
229 // so we should not account for waiting transaction needs here.
fb505fa1 230 const int debt = theBusyConns + theIdleConns->count() - theMaxConnections;
2dba5b8e
CT
231 if (debt > 0)
232 return debt;
233 else
234 return 0;
235}
236
237void Adaptation::Icap::ServiceRep::noteGoneWaiter()
238{
a2f5277a 239 --theAllWaiters;
2dba5b8e
CT
240
241 // in case the notified transaction did not take the connection slot
242 busyCheckpoint();
243}
244
245// called when a connection slot may become available
246void Adaptation::Icap::ServiceRep::busyCheckpoint()
247{
248 if (theNotificationWaiters.empty()) // nobody is waiting for a slot
249 return;
250
251 int freed = 0;
252 int available = availableConnections();
253
254 if (available < 0) {
255 // It is possible to have waiters when no limit on connections exist in
256 // case of reconfigure or because new Options received.
257 // In this case, notify all waiting transactions.
258 freed = theNotificationWaiters.size();
259 } else {
260 // avoid notifying more waiters than there will be available slots
261 const int notifiedWaiters = theAllWaiters - theNotificationWaiters.size();
262 freed = available - notifiedWaiters;
263 }
264
265 debugs(93,7, HERE << "Available connections: " << available <<
266 " freed slots: " << freed <<
267 " waiting in queue: " << theNotificationWaiters.size());
268
269 while (freed > 0 && !theNotificationWaiters.empty()) {
270 Client i = theNotificationWaiters.front();
271 theNotificationWaiters.pop_front();
272 ScheduleCallHere(i.callback);
273 i.callback = NULL;
274 --freed;
275 }
276}
277
26cc52cb 278void Adaptation::Icap::ServiceRep::suspend(const char *reason)
9e008dda 279{
c99de607 280 if (isSuspended) {
192378eb 281 debugs(93,4, HERE << "keeping suspended, also for " << reason);
c99de607 282 } else {
283 isSuspended = reason;
e0236918 284 debugs(93, DBG_IMPORTANT, "suspending ICAP service for " << reason);
26cc52cb 285 scheduleUpdate(squid_curtime + TheConfig.service_revival_delay);
c99de607 286 announceStatusChange("suspended", true);
287 }
288}
289
26cc52cb 290bool Adaptation::Icap::ServiceRep::probed() const
c99de607 291{
292 return theLastUpdate != 0;
293}
294
26cc52cb 295bool Adaptation::Icap::ServiceRep::hasOptions() const
9e008dda 296{
c99de607 297 return theOptions && theOptions->valid() && theOptions->fresh();
298}
299
26cc52cb 300bool Adaptation::Icap::ServiceRep::up() const
774c051c 301{
76fc7e57 302 return !isSuspended && hasOptions();
c99de607 303}
304
2dba5b8e
CT
305bool Adaptation::Icap::ServiceRep::availableForNew() const
306{
307 Must(up());
308 int available = availableConnections();
309 if (available < 0)
310 return true;
311 else
312 return (available - theAllWaiters > 0);
313}
314
315bool Adaptation::Icap::ServiceRep::availableForOld() const
316{
317 Must(up());
318
319 int available = availableConnections();
320 return (available != 0); // it is -1 (no limit) or has available slots
321}
322
51b5dcf5 323bool Adaptation::Icap::ServiceRep::wantsUrl(const SBuf &urlPath) const
c99de607 324{
325 Must(hasOptions());
26cc52cb 326 return theOptions->transferKind(urlPath) != Adaptation::Icap::Options::xferIgnore;
774c051c 327}
328
51b5dcf5 329bool Adaptation::Icap::ServiceRep::wantsPreview(const SBuf &urlPath, size_t &wantedSize) const
774c051c 330{
c99de607 331 Must(hasOptions());
774c051c 332
333 if (theOptions->preview < 0)
334 return false;
335
26cc52cb 336 if (theOptions->transferKind(urlPath) != Adaptation::Icap::Options::xferPreview)
c99de607 337 return false;
338
774c051c 339 wantedSize = theOptions->preview;
340
341 return true;
342}
343
26cc52cb 344bool Adaptation::Icap::ServiceRep::allows204() const
774c051c 345{
c99de607 346 Must(hasOptions());
774c051c 347 return true; // in the future, we may have ACLs to prevent 204s
348}
349
83c51da9
CT
350bool Adaptation::Icap::ServiceRep::allows206() const
351{
352 Must(hasOptions());
353 if (theOptions->allow206)
354 return true; // in the future, we may have ACLs to prevent 206s
355 return false;
356}
357
774c051c 358static
26cc52cb 359void ServiceRep_noteTimeToUpdate(void *data)
774c051c 360{
26cc52cb 361 Adaptation::Icap::ServiceRep *service = static_cast<Adaptation::Icap::ServiceRep*>(data);
774c051c 362 Must(service);
363 service->noteTimeToUpdate();
364}
365
26cc52cb 366void Adaptation::Icap::ServiceRep::noteTimeToUpdate()
774c051c 367{
76fc7e57 368 if (!detached())
c99de607 369 updateScheduled = false;
370
4299f876 371 if (detached() || theOptionsFetcher.set()) {
192378eb 372 debugs(93,5, HERE << "ignores options update " << status());
774c051c 373 return;
374 }
375
192378eb 376 debugs(93,5, HERE << "performs a regular options update " << status());
774c051c 377 startGettingOptions();
378}
379
bd7f2ede 380#if 0
774c051c 381static
26cc52cb 382void Adaptation::Icap::ServiceRep_noteTimeToNotify(void *data)
774c051c 383{
26cc52cb 384 Adaptation::Icap::ServiceRep *service = static_cast<Adaptation::Icap::ServiceRep*>(data);
774c051c 385 Must(service);
386 service->noteTimeToNotify();
387}
bd7f2ede 388#endif
774c051c 389
26cc52cb 390void Adaptation::Icap::ServiceRep::noteTimeToNotify()
774c051c 391{
392 Must(!notifying);
393 notifying = true;
192378eb 394 debugs(93,7, HERE << "notifies " << theClients.size() << " clients " <<
774c051c 395 status());
396
397 // note: we must notify even if we are invalidated
398
399 Pointer us = NULL;
400
401 while (!theClients.empty()) {
38c22baf
FC
402 Client i = theClients.back();
403 theClients.pop_back();
9e008dda
AJ
404 ScheduleCallHere(i.callback);
405 i.callback = 0;
774c051c 406 }
407
408 notifying = false;
409}
410
2dba5b8e
CT
411void Adaptation::Icap::ServiceRep::callWhenAvailable(AsyncCall::Pointer &cb, bool priority)
412{
413 debugs(93,8, "ICAPServiceRep::callWhenAvailable");
414 Must(cb!=NULL);
415 Must(up());
fb505fa1 416 Must(!theIdleConns->count()); // or we should not be waiting
2dba5b8e
CT
417
418 Client i;
419 i.service = Pointer(this);
420 i.callback = cb;
421 if (priority)
422 theNotificationWaiters.push_front(i);
423 else
424 theNotificationWaiters.push_back(i);
425
426 busyCheckpoint();
427}
428
26cc52cb 429void Adaptation::Icap::ServiceRep::callWhenReady(AsyncCall::Pointer &cb)
774c051c 430{
bd7f2ede 431 Must(cb!=NULL);
432
26cc52cb 433 debugs(93,5, HERE << "Adaptation::Icap::Service is asked to call " << *cb <<
9e008dda 434 " when ready " << status());
5f8252d2 435
c99de607 436 Must(!broken()); // we do not wait for a broken service
774c051c 437
438 Client i;
76fc7e57 439 i.service = Pointer(this); // TODO: is this really needed?
774c051c 440 i.callback = cb;
774c051c 441 theClients.push_back(i);
442
4299f876 443 if (theOptionsFetcher.set() || notifying)
774c051c 444 return; // do nothing, we will be picked up in noteTimeToNotify()
445
446 if (needNewOptions())
447 startGettingOptions();
448 else
449 scheduleNotification();
450}
451
26cc52cb 452void Adaptation::Icap::ServiceRep::scheduleNotification()
774c051c 453{
192378eb 454 debugs(93,7, HERE << "will notify " << theClients.size() << " clients");
4299f876 455 CallJobHere(93, 5, this, Adaptation::Icap::ServiceRep, noteTimeToNotify);
774c051c 456}
457
26cc52cb 458bool Adaptation::Icap::ServiceRep::needNewOptions() const
774c051c 459{
76fc7e57 460 return !detached() && !up();
774c051c 461}
462
26cc52cb 463void Adaptation::Icap::ServiceRep::changeOptions(Adaptation::Icap::Options *newOptions)
774c051c 464{
192378eb 465 debugs(93,8, HERE << "changes options from " << theOptions << " to " <<
5f8252d2 466 newOptions << ' ' << status());
c99de607 467
774c051c 468 delete theOptions;
469 theOptions = newOptions;
8277060a 470 theSessionFailures.clear();
c99de607 471 isSuspended = 0;
472 theLastUpdate = squid_curtime;
473
474 checkOptions();
475 announceStatusChange("down after an options fetch failure", true);
476}
ffddf96c 477
26cc52cb 478void Adaptation::Icap::ServiceRep::checkOptions()
c99de607 479{
9d0cdbb9 480 if (theOptions == NULL)
481 return;
482
eafe3f72 483 if (!theOptions->valid()) {
e0236918 484 debugs(93, DBG_IMPORTANT, "WARNING: Squid got an invalid ICAP OPTIONS response " <<
9e008dda 485 "from service " << cfg().uri << "; error: " << theOptions->error);
eafe3f72 486 return;
487 }
488
ffddf96c 489 /*
eadded2e 490 * Issue a warning if the ICAP server returned methods in the
491 * options response that don't match the method from squid.conf.
ffddf96c 492 */
493
eadded2e 494 if (!theOptions->methods.empty()) {
495 bool method_found = false;
30abd221 496 String method_list;
81481ec0 497 std::vector <ICAP::Method>::iterator iter = theOptions->methods.begin();
eadded2e 498
499 while (iter != theOptions->methods.end()) {
eadded2e 500
d81a31f1 501 if (*iter == cfg().method) {
eadded2e 502 method_found = true;
503 break;
504 }
505
506 method_list.append(ICAP::methodStr(*iter));
507 method_list.append(" ", 1);
742a021b 508 ++iter;
eadded2e 509 }
510
eadded2e 511 if (!method_found) {
e0236918 512 debugs(93, DBG_IMPORTANT, "WARNING: Squid is configured to use ICAP method " <<
d81a31f1 513 cfg().methodStr() <<
a7a42b14 514 " for service " << cfg().uri <<
5b4117d8 515 " but OPTIONS response declares the methods are " << method_list);
eadded2e 516 }
517 }
8eeb99bf 518
8eeb99bf 519 /*
520 * Check the ICAP server's date header for clock skew
521 */
5f8252d2 522 const int skew = (int)(theOptions->timestamp() - squid_curtime);
523 if (abs(skew) > theOptions->ttl()) {
524 // TODO: If skew is negative, the option will be considered down
525 // because of stale options. We should probably change this.
e0236918 526 debugs(93, DBG_IMPORTANT, "ICAP service's clock is skewed by " << skew <<
a7a42b14 527 " seconds: " << cfg().uri);
5f8252d2 528 }
c99de607 529}
8eeb99bf 530
26cc52cb 531void Adaptation::Icap::ServiceRep::announceStatusChange(const char *downPhrase, bool important) const
c99de607 532{
533 if (wasAnnouncedUp == up()) // no significant changes to announce
534 return;
9d0cdbb9 535
d81a31f1 536 const char *what = cfg().bypass ? "optional" : "essential";
c99de607 537 const char *state = wasAnnouncedUp ? downPhrase : "up";
9e008dda 538 const int level = important ? 1 :2;
d81a31f1 539 debugs(93,level, what << " ICAP service is " << state << ": " <<
9e008dda 540 cfg().uri << ' ' << status());
9d0cdbb9 541
c99de607 542 wasAnnouncedUp = !wasAnnouncedUp;
774c051c 543}
544
c824c43b 545// we are receiving ICAP OPTIONS response headers here or NULL on failures
3af10ac0 546void Adaptation::Icap::ServiceRep::noteAdaptationAnswer(const Answer &answer)
774c051c 547{
4299f876 548 Must(initiated(theOptionsFetcher));
d81a31f1 549 clearAdaptation(theOptionsFetcher);
774c051c 550
3af10ac0
AR
551 if (answer.kind == Answer::akError) {
552 debugs(93,3, HERE << "failed to fetch options " << status());
553 handleNewOptions(0);
554 return;
555 }
556
557 Must(answer.kind == Answer::akForward); // no akBlock for OPTIONS requests
b248c2a3 558 const HttpMsg *msg = answer.message.getRaw();
c824c43b 559 Must(msg);
560
192378eb 561 debugs(93,5, HERE << "is interpreting new options " << status());
5f8252d2 562
26cc52cb 563 Adaptation::Icap::Options *newOptions = NULL;
b248c2a3 564 if (const HttpReply *r = dynamic_cast<const HttpReply*>(msg)) {
26cc52cb 565 newOptions = new Adaptation::Icap::Options;
9e008dda 566 newOptions->configure(r);
c824c43b 567 } else {
e0236918 568 debugs(93, DBG_IMPORTANT, "ICAP service got wrong options message " << status());
c824c43b 569 }
570
571 handleNewOptions(newOptions);
572}
573
4299f876
AR
574// we (a) must keep trying to get OPTIONS and (b) are RefCounted so we
575// must keep our job alive (XXX: until nobody needs us)
576void Adaptation::Icap::ServiceRep::callException(const std::exception &e)
577{
578 clearAdaptation(theOptionsFetcher);
579 debugs(93,2, "ICAP probably failed to fetch options (" << e.what() <<
4cb2536f 580 ")" << status());
4299f876
AR
581 handleNewOptions(0);
582}
583
26cc52cb 584void Adaptation::Icap::ServiceRep::handleNewOptions(Adaptation::Icap::Options *newOptions)
c824c43b 585{
586 // new options may be NULL
5f8252d2 587 changeOptions(newOptions);
774c051c 588
192378eb 589 debugs(93,3, HERE << "got new options and is now " << status());
774c051c 590
1299ecbf 591 scheduleUpdate(optionsFetchTime());
2dba5b8e 592
983983ce 593 // XXX: this whole feature bases on the false assumption a service only has one IP
2dba5b8e
CT
594 setMaxConnections();
595 const int excess = excessConnections();
596 // if we owe connections and have idle pconns, close the latter
fb505fa1
CT
597 if (excess && theIdleConns->count() > 0) {
598 const int n = min(excess, theIdleConns->count());
2dba5b8e 599 debugs(93,5, HERE << "closing " << n << " pconns to relief debt");
fb505fa1 600 theIdleConns->closeN(n);
2dba5b8e
CT
601 }
602
774c051c 603 scheduleNotification();
604}
605
26cc52cb 606void Adaptation::Icap::ServiceRep::startGettingOptions()
774c051c 607{
98a74c05 608 Must(!theOptionsFetcher);
192378eb 609 debugs(93,6, HERE << "will get new options " << status());
774c051c 610
4299f876
AR
611 // XXX: "this" here is "self"; works until refcounting API changes
612 theOptionsFetcher = initiateAdaptation(
4cb2536f 613 new Adaptation::Icap::OptXactLauncher(this));
26cc52cb 614 // TODO: timeout in case Adaptation::Icap::OptXact never calls us back?
c824c43b 615 // Such a timeout should probably be a generic AsyncStart feature.
774c051c 616}
617
26cc52cb 618void Adaptation::Icap::ServiceRep::scheduleUpdate(time_t when)
774c051c 619{
1299ecbf 620 if (updateScheduled) {
192378eb 621 debugs(93,7, HERE << "reschedules update");
1299ecbf 622 // XXX: check whether the event is there because AR saw
04d76948 623 // an unreproducible eventDelete assertion on 2007/06/18
26cc52cb
AR
624 if (eventFind(&ServiceRep_noteTimeToUpdate, this))
625 eventDelete(&ServiceRep_noteTimeToUpdate, this);
c99de607 626 else
e0236918 627 debugs(93, DBG_IMPORTANT, "XXX: ICAP service lost an update event.");
1299ecbf 628 updateScheduled = false;
774c051c 629 }
630
1299ecbf 631 debugs(93,7, HERE << "raw OPTIONS fetch at " << when << " or in " <<
9e008dda 632 (when - squid_curtime) << " sec");
1299ecbf 633 debugs(93,9, HERE << "last fetched at " << theLastUpdate << " or " <<
9e008dda 634 (squid_curtime - theLastUpdate) << " sec ago");
5f8252d2 635
636 /* adjust update time to prevent too-frequent updates */
637
c99de607 638 if (when < squid_curtime)
639 when = squid_curtime;
640
26cc52cb 641 // XXX: move hard-coded constants from here to Adaptation::Icap::TheConfig
1299ecbf 642 const int minUpdateGap = 30; // seconds
c99de607 643 if (when < theLastUpdate + minUpdateGap)
644 when = theLastUpdate + minUpdateGap;
774c051c 645
c99de607 646 const int delay = when - squid_curtime;
192378eb 647 debugs(93,5, HERE << "will fetch OPTIONS in " << delay << " sec");
1299ecbf 648
26cc52cb
AR
649 eventAdd("Adaptation::Icap::ServiceRep::noteTimeToUpdate",
650 &ServiceRep_noteTimeToUpdate, this, delay, 0, true);
c99de607 651 updateScheduled = true;
774c051c 652}
653
1299ecbf 654// returns absolute time when OPTIONS should be fetched
655time_t
26cc52cb 656Adaptation::Icap::ServiceRep::optionsFetchTime() const
1299ecbf 657{
658 if (theOptions && theOptions->valid()) {
659 const time_t expire = theOptions->expire();
192378eb 660 debugs(93,7, HERE << "options expire on " << expire << " >= " << squid_curtime);
1299ecbf 661
662 // conservative estimate of how long the OPTIONS transaction will take
26cc52cb 663 // XXX: move hard-coded constants from here to Adaptation::Icap::TheConfig
1299ecbf 664 const int expectedWait = 20; // seconds
665
666 // Unknown or invalid (too small) expiration times should not happen.
26cc52cb 667 // Adaptation::Icap::Options should use the default TTL, and ICAP servers should not
1299ecbf 668 // send invalid TTLs, but bugs and attacks happen.
669 if (expire < expectedWait)
670 return squid_curtime;
671 else
672 return expire - expectedWait; // before the current options expire
673 }
674
675 // use revival delay as "expiration" time for a service w/o valid options
26cc52cb 676 return squid_curtime + TheConfig.service_revival_delay;
1299ecbf 677}
678
d81a31f1 679Adaptation::Initiate *
4299f876 680Adaptation::Icap::ServiceRep::makeXactLauncher(HttpMsg *virgin,
af0ded40 681 HttpRequest *cause, AccessLogEntry::Pointer &alp)
d81a31f1 682{
af0ded40 683 return new Adaptation::Icap::ModXactLauncher(virgin, cause, alp, this);
d81a31f1
AR
684}
685
c99de607 686// returns a temporary string depicting service status, for debugging
26cc52cb 687const char *Adaptation::Icap::ServiceRep::status() const
774c051c 688{
c99de607 689 static MemBuf buf;
690
691 buf.reset();
692 buf.append("[", 1);
693
694 if (up())
695 buf.append("up", 2);
5f8252d2 696 else {
c99de607 697 buf.append("down", 4);
5f8252d2 698 if (isSuspended)
699 buf.append(",susp", 5);
c99de607 700
5f8252d2 701 if (!theOptions)
702 buf.append(",!opt", 5);
e1381638
AJ
703 else if (!theOptions->valid())
704 buf.append(",!valid", 7);
705 else if (!theOptions->fresh())
706 buf.append(",stale", 6);
5f8252d2 707 }
774c051c 708
76fc7e57
AJ
709 if (detached())
710 buf.append(",detached", 9);
711
4299f876 712 if (theOptionsFetcher.set())
98a74c05 713 buf.append(",fetch", 6);
774c051c 714
c99de607 715 if (notifying)
716 buf.append(",notif", 6);
774c051c 717
8277060a 718 if (const int failures = theSessionFailures.remembered())
4391cd15 719 buf.appendf(",fail%d", failures);
774c051c 720
c99de607 721 buf.append("]", 1);
722 buf.terminate();
774c051c 723
c99de607 724 return buf.content();
774c051c 725}
76fc7e57
AJ
726
727void Adaptation::Icap::ServiceRep::detach()
728{
729 debugs(93,3, HERE << "detaching ICAP service: " << cfg().uri <<
d090e020 730 ' ' << status());
76fc7e57
AJ
731 isDetached = true;
732}
733
734bool Adaptation::Icap::ServiceRep::detached() const
735{
736 return isDetached;
737}
2dba5b8e 738
d6d0eb11 739Adaptation::Icap::ConnWaiterDialer::ConnWaiterDialer(const CbcPointer<Adaptation::Icap::ModXact> &xact,
2dba5b8e 740 Adaptation::Icap::ConnWaiterDialer::Parent::Method aHandler):
f53969cc 741 Parent(xact, aHandler)
2dba5b8e
CT
742{
743 theService = &xact->service();
744 theService->noteNewWaiter();
745}
746
747Adaptation::Icap::ConnWaiterDialer::ConnWaiterDialer(const Adaptation::Icap::ConnWaiterDialer &aConnWaiter): Parent(aConnWaiter)
748{
749 theService = aConnWaiter.theService;
750 theService->noteNewWaiter();
751}
752
753Adaptation::Icap::ConnWaiterDialer::~ConnWaiterDialer()
754{
755 theService->noteGoneWaiter();
756}
f53969cc 757