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