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