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