]>
Commit | Line | Data |
---|---|---|
482dcd01 AJ |
1 | /* |
2 | * DEBUG: section 05 Socket Connection Opener | |
3 | */ | |
4 | ||
f7f3304a | 5 | #include "squid.h" |
a011edee | 6 | #include "CachePeer.h" |
602d9612 | 7 | #include "comm.h" |
aed188fd | 8 | #include "comm/Connection.h" |
602d9612 | 9 | #include "comm/ConnOpener.h" |
8bbb16e3 | 10 | #include "comm/Loops.h" |
c4ad1349 | 11 | #include "fd.h" |
aed188fd | 12 | #include "fde.h" |
582c2af2 | 13 | #include "globals.h" |
aed188fd | 14 | #include "icmp/net_db.h" |
37abc165 | 15 | #include "ip/tools.h" |
602d9612 | 16 | #include "ipcache.h" |
c6f168c1 | 17 | #include "ip/QosConfig.h" |
4d5904f7 | 18 | #include "SquidConfig.h" |
aed188fd AJ |
19 | #include "SquidTime.h" |
20 | ||
1a30fdf5 | 21 | #include <cerrno> |
21d845b1 | 22 | |
a011edee FC |
23 | class CachePeer; |
24 | ||
a016163c | 25 | CBDATA_NAMESPACED_CLASS_INIT(Comm, ConnOpener); |
aed188fd | 26 | |
4accd6d8 AJ |
27 | Comm::ConnOpener::ConnOpener(Comm::ConnectionPointer &c, AsyncCall::Pointer &handler, time_t ctimeout) : |
28 | AsyncJob("Comm::ConnOpener"), | |
5229395c | 29 | host_(NULL), |
a95ff429 | 30 | temporaryFd_(-1), |
5229395c AJ |
31 | conn_(c), |
32 | callback_(handler), | |
33 | totalTries_(0), | |
34 | failRetries_(0), | |
923b75ce | 35 | deadline_(squid_curtime + static_cast<time_t>(ctimeout)) |
5229395c | 36 | {} |
aed188fd | 37 | |
4accd6d8 | 38 | Comm::ConnOpener::~ConnOpener() |
aed188fd | 39 | { |
5229395c | 40 | safe_free(host_); |
aed188fd AJ |
41 | } |
42 | ||
294775b5 | 43 | bool |
4accd6d8 | 44 | Comm::ConnOpener::doneAll() const |
294775b5 | 45 | { |
5229395c | 46 | // is the conn_ to be opened still waiting? |
e3a4aecc AJ |
47 | if (conn_ == NULL) { |
48 | return AsyncJob::doneAll(); | |
40d9f0fc | 49 | } |
294775b5 AJ |
50 | |
51 | // is the callback still to be called? | |
e3a4aecc AJ |
52 | if (callback_ == NULL || callback_->canceled()) { |
53 | return AsyncJob::doneAll(); | |
40d9f0fc | 54 | } |
294775b5 | 55 | |
923b75ce AR |
56 | // otherwise, we must be waiting for something |
57 | Must(temporaryFd_ >= 0 || calls_.sleep_); | |
e3a4aecc | 58 | return false; |
294775b5 AJ |
59 | } |
60 | ||
e52e78c4 | 61 | void |
4accd6d8 | 62 | Comm::ConnOpener::swanSong() |
e52e78c4 | 63 | { |
418b3087 | 64 | if (callback_ != NULL) { |
923b75ce | 65 | // inform the still-waiting caller we are dying |
c8407295 | 66 | sendAnswer(Comm::ERR_CONNECT, 0, "Comm::ConnOpener::swanSong"); |
4590cc7d | 67 | } |
d146d17e | 68 | |
6ca3e20d | 69 | // did we abort with a temporary FD assigned? |
923b75ce AR |
70 | if (temporaryFd_ >= 0) |
71 | closeFd(); | |
72 | ||
6ca3e20d | 73 | // did we abort while waiting between retries? |
923b75ce AR |
74 | if (calls_.sleep_) |
75 | cancelSleep(); | |
a95ff429 | 76 | |
d146d17e | 77 | AsyncJob::swanSong(); |
e52e78c4 AJ |
78 | } |
79 | ||
aed188fd | 80 | void |
4accd6d8 | 81 | Comm::ConnOpener::setHost(const char * new_host) |
aed188fd | 82 | { |
85d2870d | 83 | // unset and erase if already set. |
5229395c AJ |
84 | if (host_ != NULL) |
85 | safe_free(host_); | |
85d2870d AJ |
86 | |
87 | // set the new one if given. | |
88 | if (new_host != NULL) | |
5229395c | 89 | host_ = xstrdup(new_host); |
aed188fd AJ |
90 | } |
91 | ||
92 | const char * | |
4accd6d8 | 93 | Comm::ConnOpener::getHost() const |
aed188fd | 94 | { |
5229395c | 95 | return host_; |
aed188fd AJ |
96 | } |
97 | ||
5229395c AJ |
98 | /** |
99 | * Connection attempt are completed. One way or the other. | |
100 | * Pass the results back to the external handler. | |
101 | */ | |
aed188fd | 102 | void |
c8407295 | 103 | Comm::ConnOpener::sendAnswer(Comm::Flag errFlag, int xerrno, const char *why) |
aed188fd | 104 | { |
e884bbde AJ |
105 | // only mark the address good/bad AFTER connect is finished. |
106 | if (host_ != NULL) { | |
923b75ce | 107 | if (xerrno == 0) // XXX: should not we use errFlag instead? |
e884bbde AJ |
108 | ipcacheMarkGoodAddr(host_, conn_->remote); |
109 | else { | |
110 | ipcacheMarkBadAddr(host_, conn_->remote); | |
111 | #if USE_ICMP | |
112 | if (Config.onoff.test_reachability) | |
113 | netdbDeleteAddrNetwork(conn_->remote); | |
114 | #endif | |
115 | } | |
116 | } | |
117 | ||
5229395c | 118 | if (callback_ != NULL) { |
923b75ce AR |
119 | // avoid scheduling cancelled callbacks, assuming they are common |
120 | // enough to make this extra check an optimization | |
121 | if (callback_->canceled()) { | |
122 | debugs(5, 4, conn_ << " not calling canceled " << *callback_ << | |
123 | " [" << callback_->id << ']' ); | |
6ca3e20d | 124 | // TODO save the pconn to the pconnPool ? |
923b75ce AR |
125 | } else { |
126 | typedef CommConnectCbParams Params; | |
127 | Params ¶ms = GetCommParams<Params>(callback_); | |
128 | params.conn = conn_; | |
129 | params.flag = errFlag; | |
130 | params.xerrno = xerrno; | |
131 | ScheduleCallHere(callback_); | |
132 | } | |
5229395c | 133 | callback_ = NULL; |
294775b5 AJ |
134 | } |
135 | ||
923b75ce AR |
136 | // The job will stop without this call because nil callback_ makes |
137 | // doneAll() true, but this explicit call creates nicer debugging. | |
138 | mustStop(why); | |
139 | } | |
140 | ||
141 | /// cleans up this job I/O state without closing temporaryFd | |
142 | /// required before closing temporaryFd or keeping it in conn_ | |
143 | /// leaves FD bare so must only be called via closeFd() or keepFd() | |
144 | void | |
145 | Comm::ConnOpener::cleanFd() | |
146 | { | |
147 | debugs(5, 4, HERE << conn_ << " closing temp FD " << temporaryFd_); | |
148 | ||
149 | Must(temporaryFd_ >= 0); | |
150 | fde &f = fd_table[temporaryFd_]; | |
151 | ||
152 | // Our write_handler was set without using Comm::Write API, so we cannot | |
153 | // use a cancellable Pointer-free job callback and simply cancel it here. | |
154 | if (f.write_handler) { | |
155 | ||
156 | /* XXX: We are about to remove write_handler, which was responsible | |
157 | * for deleting write_data, so we have to delete write_data | |
158 | * ourselves. Comm currently calls SetSelect handlers synchronously | |
159 | * so if write_handler is set, we know it has not been called yet. | |
160 | * ConnOpener converts that sync call into an async one, but only | |
161 | * after deleting ptr, so that is not a problem. | |
162 | */ | |
163 | ||
164 | delete static_cast<Pointer*>(f.write_data); | |
165 | f.write_data = NULL; | |
166 | f.write_handler = NULL; | |
167 | } | |
168 | // Comm::DoSelect does not do this when calling and resetting write_handler | |
3c862035 | 169 | // (because it expects more writes to come?). We could mimic that |
923b75ce AR |
170 | // optimization by resetting Comm "Select" state only when the FD is |
171 | // actually closed. | |
172 | Comm::SetSelect(temporaryFd_, COMM_SELECT_WRITE, NULL, NULL, 0); | |
3c862035 | 173 | |
923b75ce AR |
174 | if (calls_.timeout_ != NULL) { |
175 | calls_.timeout_->cancel("Comm::ConnOpener::cleanFd"); | |
176 | calls_.timeout_ = NULL; | |
177 | } | |
3c862035 | 178 | // Comm checkTimeouts() and commCloseAllSockets() do not clear .timeout |
923b75ce AR |
179 | // when calling timeoutHandler (XXX fix them), so we clear unconditionally. |
180 | f.timeoutHandler = NULL; | |
181 | f.timeout = 0; | |
182 | ||
183 | if (calls_.earlyAbort_ != NULL) { | |
184 | comm_remove_close_handler(temporaryFd_, calls_.earlyAbort_); | |
a95ff429 | 185 | calls_.earlyAbort_ = NULL; |
a95ff429 | 186 | } |
923b75ce AR |
187 | } |
188 | ||
189 | /// cleans I/O state and ends I/O for temporaryFd_ | |
190 | void | |
191 | Comm::ConnOpener::closeFd() | |
192 | { | |
193 | if (temporaryFd_ < 0) | |
194 | return; | |
195 | ||
196 | cleanFd(); | |
a95ff429 | 197 | |
923b75ce AR |
198 | // comm_close() below uses COMMIO_FD_WRITECB(fd)->active() to clear Comm |
199 | // "Select" state. It will not clear ours. XXX: It should always clear | |
200 | // because a callback may have been active but was called before comm_close | |
201 | // Update: we now do this in cleanFd() | |
202 | // Comm::SetSelect(temporaryFd_, COMM_SELECT_WRITE, NULL, NULL, 0); | |
203 | ||
204 | comm_close(temporaryFd_); | |
205 | temporaryFd_ = -1; | |
206 | } | |
207 | ||
208 | /// cleans I/O state and moves temporaryFd_ to the conn_ for long-term use | |
209 | void | |
210 | Comm::ConnOpener::keepFd() | |
211 | { | |
212 | Must(conn_ != NULL); | |
213 | Must(temporaryFd_ >= 0); | |
214 | ||
215 | cleanFd(); | |
216 | ||
217 | conn_->fd = temporaryFd_; | |
218 | temporaryFd_ = -1; | |
aed188fd AJ |
219 | } |
220 | ||
a9870624 AJ |
221 | void |
222 | Comm::ConnOpener::start() | |
aed188fd | 223 | { |
5229395c | 224 | Must(conn_ != NULL); |
482dcd01 | 225 | |
923b75ce | 226 | /* outbound sockets have no need to be protocol agnostic. */ |
4dd643d5 AJ |
227 | if (!(Ip::EnableIpv6&IPV6_SPECIAL_V4MAPPING) && conn_->remote.isIPv4()) { |
228 | conn_->local.setIPv4(); | |
923b75ce AR |
229 | } |
230 | ||
8aec3e1b | 231 | conn_->noteStart(); |
923b75ce | 232 | if (createFd()) |
3d7ecaff | 233 | doConnect(); |
923b75ce AR |
234 | } |
235 | ||
236 | /// called at the end of Comm::ConnOpener::DelayedConnectRetry event | |
237 | void | |
3c862035 A |
238 | Comm::ConnOpener::restart() |
239 | { | |
923b75ce AR |
240 | debugs(5, 5, conn_ << " restarting after sleep"); |
241 | calls_.sleep_ = false; | |
242 | ||
243 | if (createFd()) | |
3d7ecaff | 244 | doConnect(); |
923b75ce AR |
245 | } |
246 | ||
3c862035 | 247 | /// Create a socket for the future connection or return false. |
923b75ce AR |
248 | /// If false is returned, done() is guaranteed to return true and end the job. |
249 | bool | |
250 | Comm::ConnOpener::createFd() | |
251 | { | |
252 | Must(temporaryFd_ < 0); | |
253 | ||
254 | // our initators signal abort by cancelling their callbacks | |
255 | if (callback_ == NULL || callback_->canceled()) | |
256 | return false; | |
257 | ||
c6f168c1 | 258 | temporaryFd_ = comm_openex(SOCK_STREAM, IPPROTO_TCP, conn_->local, conn_->flags, host_); |
a95ff429 | 259 | if (temporaryFd_ < 0) { |
c8407295 | 260 | sendAnswer(Comm::ERR_CONNECT, 0, "Comm::ConnOpener::createFd"); |
923b75ce | 261 | return false; |
aed188fd AJ |
262 | } |
263 | ||
c6f168c1 CT |
264 | // Set TOS if needed. |
265 | if (conn_->tos && | |
266 | Ip::Qos::setSockTos(temporaryFd_, conn_->tos, conn_->remote.isIPv4() ? AF_INET : AF_INET6) < 0) | |
267 | conn_->tos = 0; | |
268 | #if SO_MARK | |
269 | if (conn_->nfmark && | |
270 | Ip::Qos::setSockNfmark(temporaryFd_, conn_->nfmark) < 0) | |
271 | conn_->nfmark = 0; | |
272 | #endif | |
273 | ||
274 | fd_table[conn_->fd].tosToServer = conn_->tos; | |
275 | fd_table[conn_->fd].nfmarkToServer = conn_->nfmark; | |
276 | ||
2832d7c0 | 277 | typedef CommCbMemFunT<Comm::ConnOpener, CommCloseCbParams> abortDialer; |
802540f2 | 278 | calls_.earlyAbort_ = JobCallback(5, 4, abortDialer, this, Comm::ConnOpener::earlyAbort); |
a95ff429 | 279 | comm_add_close_handler(temporaryFd_, calls_.earlyAbort_); |
418b3087 | 280 | |
802540f2 AJ |
281 | typedef CommCbMemFunT<Comm::ConnOpener, CommTimeoutCbParams> timeoutDialer; |
282 | calls_.timeout_ = JobCallback(5, 4, timeoutDialer, this, Comm::ConnOpener::timeout); | |
923b75ce | 283 | debugs(5, 3, conn_ << " will timeout in " << (deadline_ - squid_curtime)); |
a95ff429 | 284 | |
923b75ce | 285 | // Update the fd_table directly because commSetConnTimeout() needs open conn_ |
a95ff429 AJ |
286 | assert(temporaryFd_ < Squid_MaxFD); |
287 | assert(fd_table[temporaryFd_].flags.open); | |
288 | typedef CommTimeoutCbParams Params; | |
289 | Params ¶ms = GetCommParams<Params>(calls_.timeout_); | |
290 | params.conn = conn_; | |
291 | fd_table[temporaryFd_].timeoutHandler = calls_.timeout_; | |
923b75ce | 292 | fd_table[temporaryFd_].timeout = deadline_; |
418b3087 | 293 | |
923b75ce | 294 | return true; |
418b3087 AJ |
295 | } |
296 | ||
297 | void | |
298 | Comm::ConnOpener::connected() | |
299 | { | |
923b75ce AR |
300 | Must(temporaryFd_ >= 0); |
301 | keepFd(); | |
a95ff429 | 302 | |
418b3087 AJ |
303 | /* |
304 | * stats.conn_open is used to account for the number of | |
a3c6762c | 305 | * connections that we have open to the CachePeer, so we can limit |
418b3087 AJ |
306 | * based on the max-conn option. We need to increment here, |
307 | * even if the connection may fail. | |
308 | */ | |
a3c6762c | 309 | if (CachePeer *peer=(conn_->getPeer())) |
b79bfaae | 310 | ++peer->stats.conn_open; |
418b3087 AJ |
311 | |
312 | lookupLocalAddress(); | |
313 | ||
314 | /* TODO: remove these fd_table accesses. But old code still depends on fd_table flags to | |
315 | * indicate the state of a raw fd object being passed around. | |
316 | * Also, legacy code still depends on comm_local_port() with no access to Comm::Connection | |
317 | * when those are done comm_local_port can become one of our member functions to do the below. | |
318 | */ | |
923b75ce | 319 | Must(fd_table[conn_->fd].flags.open); |
418b3087 | 320 | fd_table[conn_->fd].local_addr = conn_->local; |
923b75ce | 321 | |
c8407295 | 322 | sendAnswer(Comm::OK, 0, "Comm::ConnOpener::connected"); |
8968fd45 AJ |
323 | } |
324 | ||
923b75ce | 325 | /// Make an FD connection attempt. |
8968fd45 | 326 | void |
3d7ecaff | 327 | Comm::ConnOpener::doConnect() |
8968fd45 AJ |
328 | { |
329 | Must(conn_ != NULL); | |
923b75ce | 330 | Must(temporaryFd_ >= 0); |
878bfa63 | 331 | |
a2f5277a | 332 | ++ totalTries_; |
aed188fd | 333 | |
a95ff429 | 334 | switch (comm_connect_addr(temporaryFd_, conn_->remote) ) { |
aed188fd | 335 | |
c8407295 AJ |
336 | case Comm::INPROGRESS: |
337 | debugs(5, 5, HERE << conn_ << ": Comm::INPROGRESS"); | |
923b75ce | 338 | Comm::SetSelect(temporaryFd_, COMM_SELECT_WRITE, Comm::ConnOpener::InProgressConnectRetry, new Pointer(this), 0); |
aed188fd AJ |
339 | break; |
340 | ||
c8407295 AJ |
341 | case Comm::OK: |
342 | debugs(5, 5, HERE << conn_ << ": Comm::OK - connected"); | |
418b3087 | 343 | connected(); |
aed188fd AJ |
344 | break; |
345 | ||
923b75ce AR |
346 | default: { |
347 | const int xerrno = errno; | |
348 | ||
a2f5277a | 349 | ++failRetries_; |
923b75ce AR |
350 | debugs(5, 7, conn_ << ": failure #" << failRetries_ << " <= " << |
351 | Config.connect_retries << ": " << xstrerr(xerrno)); | |
aed188fd | 352 | |
923b75ce | 353 | if (failRetries_ < Config.connect_retries) { |
e884bbde | 354 | debugs(5, 5, HERE << conn_ << ": * - try again"); |
f6c0d1ab | 355 | retrySleep(); |
e884bbde | 356 | return; |
aed188fd AJ |
357 | } else { |
358 | // send ERROR back to the upper layer. | |
5b67dfa4 | 359 | debugs(5, 5, HERE << conn_ << ": * - ERR tried too many times already."); |
294e1994 | 360 | sendAnswer(Comm::ERR_CONNECT, xerrno, "Comm::ConnOpener::doConnect"); |
aed188fd AJ |
361 | } |
362 | } | |
923b75ce AR |
363 | } |
364 | } | |
365 | ||
366 | /// Close and wait a little before trying to open and connect again. | |
367 | void | |
f6c0d1ab | 368 | Comm::ConnOpener::retrySleep() |
3c862035 | 369 | { |
923b75ce AR |
370 | Must(!calls_.sleep_); |
371 | closeFd(); | |
372 | calls_.sleep_ = true; | |
373 | eventAdd("Comm::ConnOpener::DelayedConnectRetry", | |
374 | Comm::ConnOpener::DelayedConnectRetry, | |
375 | new Pointer(this), 0.05, 0, false); | |
376 | } | |
377 | ||
378 | /// cleans up this job sleep state | |
379 | void | |
380 | Comm::ConnOpener::cancelSleep() | |
381 | { | |
382 | if (calls_.sleep_) { | |
3c862035 A |
383 | // It would be nice to delete the sleep event, but it might be out of |
384 | // the event queue and in the async queue already, so (a) we do not know | |
385 | // whether we can safely delete the call ptr here and (b) eventDelete() | |
386 | // will assert if the event went async. Thus, we let the event run so | |
387 | // that it deletes the call ptr [after this job is gone]. Note that we | |
388 | // are called only when the job ends so this "hanging event" will do | |
389 | // nothing but deleting the call ptr. TODO: Revise eventDelete() API. | |
390 | // eventDelete(Comm::ConnOpener::DelayedConnectRetry, calls_.sleep); | |
391 | calls_.sleep_ = false; | |
392 | debugs(5, 9, conn_ << " stops sleeping"); | |
923b75ce | 393 | } |
aed188fd AJ |
394 | } |
395 | ||
dd829807 AJ |
396 | /** |
397 | * Lookup local-end address and port of the TCP link just opened. | |
398 | * This ensure the connection local details are set correctly | |
399 | */ | |
400 | void | |
401 | Comm::ConnOpener::lookupLocalAddress() | |
402 | { | |
403 | struct addrinfo *addr = NULL; | |
4dd643d5 | 404 | Ip::Address::InitAddrInfo(addr); |
dd829807 AJ |
405 | |
406 | if (getsockname(conn_->fd, addr->ai_addr, &(addr->ai_addrlen)) != 0) { | |
5b67dfa4 | 407 | debugs(50, DBG_IMPORTANT, "ERROR: Failed to retrieve TCP/UDP details for socket: " << conn_ << ": " << xstrerror()); |
4dd643d5 | 408 | Ip::Address::FreeAddrInfo(addr); |
dd829807 AJ |
409 | return; |
410 | } | |
411 | ||
412 | conn_->local = *addr; | |
4dd643d5 | 413 | Ip::Address::FreeAddrInfo(addr); |
5b67dfa4 | 414 | debugs(5, 6, HERE << conn_); |
dd829807 AJ |
415 | } |
416 | ||
5229395c AJ |
417 | /** Abort connection attempt. |
418 | * Handles the case(s) when a partially setup connection gets closed early. | |
419 | */ | |
aed188fd | 420 | void |
2832d7c0 | 421 | Comm::ConnOpener::earlyAbort(const CommCloseCbParams &io) |
aed188fd | 422 | { |
5b67dfa4 | 423 | debugs(5, 3, HERE << io.conn); |
923b75ce AR |
424 | calls_.earlyAbort_ = NULL; |
425 | // NP: is closing or shutdown better? | |
c8407295 | 426 | sendAnswer(Comm::ERR_CLOSING, io.xerrno, "Comm::ConnOpener::earlyAbort"); |
aed188fd AJ |
427 | } |
428 | ||
5229395c AJ |
429 | /** |
430 | * Handles the case(s) when a partially setup connection gets timed out. | |
8d77a37c | 431 | * NP: When commSetConnTimeout accepts generic CommCommonCbParams this can die. |
5229395c | 432 | */ |
aed188fd | 433 | void |
418b3087 | 434 | Comm::ConnOpener::timeout(const CommTimeoutCbParams &) |
aed188fd | 435 | { |
923b75ce AR |
436 | debugs(5, 5, HERE << conn_ << ": * - ERR took too long to receive response."); |
437 | calls_.timeout_ = NULL; | |
c8407295 | 438 | sendAnswer(Comm::TIMEOUT, ETIMEDOUT, "Comm::ConnOpener::timeout"); |
aed188fd AJ |
439 | } |
440 | ||
c8407295 | 441 | /* Legacy Wrapper for the retry event after Comm::INPROGRESS |
294e1994 | 442 | * XXX: As soon as Comm::SetSelect() accepts Async calls we can use a ConnOpener::doConnect call |
5229395c | 443 | */ |
aed188fd | 444 | void |
418b3087 | 445 | Comm::ConnOpener::InProgressConnectRetry(int fd, void *data) |
aed188fd | 446 | { |
9e64d84e AR |
447 | Pointer *ptr = static_cast<Pointer*>(data); |
448 | assert(ptr); | |
449 | if (ConnOpener *cs = ptr->valid()) { | |
42628300 A |
450 | // Ew. we are now outside the all AsyncJob protections. |
451 | // get back inside by scheduling another call... | |
452 | typedef NullaryMemFunT<Comm::ConnOpener> Dialer; | |
294e1994 | 453 | AsyncCall::Pointer call = JobCallback(5, 4, Dialer, cs, Comm::ConnOpener::doConnect); |
42628300 | 454 | ScheduleCallHere(call); |
9e64d84e AR |
455 | } |
456 | delete ptr; | |
418b3087 AJ |
457 | } |
458 | ||
459 | /* Legacy Wrapper for the retry event with small delay after errors. | |
923b75ce | 460 | * XXX: As soon as eventAdd() accepts Async calls we can use a ConnOpener::restart call |
418b3087 AJ |
461 | */ |
462 | void | |
463 | Comm::ConnOpener::DelayedConnectRetry(void *data) | |
464 | { | |
9e64d84e AR |
465 | Pointer *ptr = static_cast<Pointer*>(data); |
466 | assert(ptr); | |
467 | if (ConnOpener *cs = ptr->valid()) { | |
42628300 A |
468 | // Ew. we are now outside the all AsyncJob protections. |
469 | // get back inside by scheduling another call... | |
470 | typedef NullaryMemFunT<Comm::ConnOpener> Dialer; | |
923b75ce | 471 | AsyncCall::Pointer call = JobCallback(5, 4, Dialer, cs, Comm::ConnOpener::restart); |
42628300 | 472 | ScheduleCallHere(call); |
9e64d84e AR |
473 | } |
474 | delete ptr; | |
aed188fd | 475 | } |