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