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