2 * DEBUG: section 84 Helper process maintenance
3 * AUTHOR: Harvest Derived?
5 * SQUID Web Proxy Cache http://www.squid-cache.org/
6 * ----------------------------------------------------------
8 * Squid is the result of efforts by numerous individuals from
9 * the Internet community; see the CONTRIBUTORS file for full
10 * details. Many organizations have provided support for Squid's
11 * development; see the SPONSORS file for full details. Squid is
12 * Copyrighted (C) 2001 by the Regents of the University of
13 * California; see the COPYRIGHT file for full details. Squid
14 * incorporates software developed and/or copyrighted by other
15 * sources; see the CREDITS file for full details.
17 * This program is free software; you can redistribute it and/or modify
18 * it under the terms of the GNU General Public License as published by
19 * the Free Software Foundation; either version 2 of the License, or
20 * (at your option) any later version.
22 * This program is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
27 * You should have received a copy of the GNU General Public License
28 * along with this program; if not, write to the Free Software
29 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
34 #include "base/AsyncCbdataCalls.h"
36 #include "comm/Connection.h"
37 #include "comm/Write.h"
40 #include "format/Quoting.h"
45 #include "SquidMath.h"
46 #include "SquidTime.h"
50 #define HELPER_MAX_ARGS 64
52 /** Initial Squid input buffer size. Helper responses may exceed this, and
53 * Squid will grow the input buffer as needed, up to ReadBufMaxSize.
55 const size_t ReadBufMinSize(4*1024);
57 /** Maximum safe size of a helper-to-Squid response message plus one.
58 * Squid will warn and close the stream if a helper sends a too-big response.
59 * ssl_crtd helper is known to produce responses of at least 10KB in size.
60 * Some undocumented helpers are known to produce responses exceeding 8KB.
62 const size_t ReadBufMaxSize(32*1024);
64 static IOCB helperHandleRead
;
65 static IOCB helperStatefulHandleRead
;
66 static void helperServerFree(helper_server
*srv
);
67 static void helperStatefulServerFree(helper_stateful_server
*srv
);
68 static void Enqueue(helper
* hlp
, helper_request
*);
69 static helper_request
*Dequeue(helper
* hlp
);
70 static helper_stateful_request
*StatefulDequeue(statefulhelper
* hlp
);
71 static helper_server
*GetFirstAvailable(helper
* hlp
);
72 static helper_stateful_server
*StatefulGetFirstAvailable(statefulhelper
* hlp
);
73 static void helperDispatch(helper_server
* srv
, helper_request
* r
);
74 static void helperStatefulDispatch(helper_stateful_server
* srv
, helper_stateful_request
* r
);
75 static void helperKickQueue(helper
* hlp
);
76 static void helperStatefulKickQueue(statefulhelper
* hlp
);
77 static void helperStatefulServerDone(helper_stateful_server
* srv
);
78 static void helperRequestFree(helper_request
* r
);
79 static void helperStatefulRequestFree(helper_stateful_request
* r
);
80 static void StatefulEnqueue(statefulhelper
* hlp
, helper_stateful_request
* r
);
81 static bool helperStartStats(StoreEntry
*sentry
, void *hlp
, const char *label
);
83 CBDATA_CLASS_INIT(helper
);
84 CBDATA_TYPE(helper_server
);
85 CBDATA_CLASS_INIT(statefulhelper
);
86 CBDATA_TYPE(helper_stateful_server
);
89 HelperServerBase::initStats()
98 HelperServerBase::closePipesSafely()
103 shutdown(writePipe
->fd
, SD_BOTH
);
106 flags
.closing
= true;
107 if (readPipe
->fd
== writePipe
->fd
)
115 if (WaitForSingleObject(hIpc
, 5000) != WAIT_OBJECT_0
) {
117 debugs(84, DBG_IMPORTANT
, "WARNING: " << hlp
->id_name
<<
118 " #" << no
<< " (" << hlp
->cmdline
->key
<< "," <<
119 (long int)pid
<< ") didn't exit in 5 seconds");
127 HelperServerBase::closeWritePipeSafely()
132 shutdown(writePipe
->fd
, (readPipe
->fd
== writePipe
->fd
? SD_BOTH
: SD_SEND
));
135 flags
.closing
= true;
136 if (readPipe
->fd
== writePipe
->fd
)
142 if (WaitForSingleObject(hIpc
, 5000) != WAIT_OBJECT_0
) {
144 debugs(84, DBG_IMPORTANT
, "WARNING: " << hlp
->id_name
<<
145 " #" << no
<< " (" << hlp
->cmdline
->key
<< "," <<
146 (long int)pid
<< ") didn't exit in 5 seconds");
154 helperOpenServers(helper
* hlp
)
160 const char *args
[HELPER_MAX_ARGS
+1]; // save space for a NULL terminator
161 char fd_note_buf
[FD_DESC_SZ
];
171 if (hlp
->cmdline
== NULL
)
174 progname
= hlp
->cmdline
->key
;
176 if ((s
= strrchr(progname
, '/')))
177 shortname
= xstrdup(s
+ 1);
179 shortname
= xstrdup(progname
);
181 /* figure out how many new child are actually needed. */
182 int need_new
= hlp
->childs
.needNew();
184 debugs(84, DBG_IMPORTANT
, "helperOpenServers: Starting " << need_new
<< "/" << hlp
->childs
.n_max
<< " '" << shortname
<< "' processes");
187 debugs(84, DBG_IMPORTANT
, "helperOpenServers: No '" << shortname
<< "' processes needed.");
190 procname
= (char *)xmalloc(strlen(shortname
) + 3);
192 snprintf(procname
, strlen(shortname
) + 3, "(%s)", shortname
);
194 args
[nargs
] = procname
;
197 for (w
= hlp
->cmdline
->next
; w
&& nargs
< HELPER_MAX_ARGS
; w
= w
->next
) {
198 args
[nargs
] = w
->key
;
205 assert(nargs
<= HELPER_MAX_ARGS
);
207 for (k
= 0; k
< need_new
; ++k
) {
210 pid
= ipcCreate(hlp
->ipc_type
,
220 debugs(84, DBG_IMPORTANT
, "WARNING: Cannot run '" << progname
<< "' process.");
224 ++ hlp
->childs
.n_running
;
225 ++ hlp
->childs
.n_active
;
226 CBDATA_INIT_TYPE(helper_server
);
227 srv
= cbdataAlloc(helper_server
);
232 srv
->addr
= hlp
->addr
;
233 srv
->readPipe
= new Comm::Connection
;
234 srv
->readPipe
->fd
= rfd
;
235 srv
->writePipe
= new Comm::Connection
;
236 srv
->writePipe
->fd
= wfd
;
237 srv
->rbuf
= (char *)memAllocBuf(ReadBufMinSize
, &srv
->rbuf_sz
);
238 srv
->wqueue
= new MemBuf
;
240 srv
->requests
= (helper_request
**)xcalloc(hlp
->childs
.concurrency
? hlp
->childs
.concurrency
: 1, sizeof(*srv
->requests
));
241 srv
->parent
= cbdataReference(hlp
);
242 dlinkAddTail(srv
, &srv
->link
, &hlp
->servers
);
245 snprintf(fd_note_buf
, FD_DESC_SZ
, "%s #%d", shortname
, k
+ 1);
246 fd_note(rfd
, fd_note_buf
);
248 snprintf(fd_note_buf
, FD_DESC_SZ
, "reading %s #%d", shortname
, k
+ 1);
249 fd_note(rfd
, fd_note_buf
);
250 snprintf(fd_note_buf
, FD_DESC_SZ
, "writing %s #%d", shortname
, k
+ 1);
251 fd_note(wfd
, fd_note_buf
);
254 commSetNonBlocking(rfd
);
257 commSetNonBlocking(wfd
);
259 AsyncCall::Pointer closeCall
= asyncCall(5,4, "helperServerFree", cbdataDialer(helperServerFree
, srv
));
260 comm_add_close_handler(rfd
, closeCall
);
262 AsyncCall::Pointer call
= commCbCall(5,4, "helperHandleRead",
263 CommIoCbPtrFun(helperHandleRead
, srv
));
264 comm_read(srv
->readPipe
, srv
->rbuf
, srv
->rbuf_sz
- 1, call
);
267 hlp
->last_restart
= squid_curtime
;
268 safe_free(shortname
);
270 helperKickQueue(hlp
);
276 * helperStatefulOpenServers: create the stateful child helper processes
279 helperStatefulOpenServers(statefulhelper
* hlp
)
282 const char *args
[HELPER_MAX_ARGS
+1]; // save space for a NULL terminator
283 char fd_note_buf
[FD_DESC_SZ
];
286 if (hlp
->cmdline
== NULL
)
289 if (hlp
->childs
.concurrency
)
290 debugs(84, DBG_CRITICAL
, "ERROR: concurrency= is not yet supported for stateful helpers ('" << hlp
->cmdline
<< "')");
292 char *progname
= hlp
->cmdline
->key
;
295 if ((s
= strrchr(progname
, '/')))
296 shortname
= xstrdup(s
+ 1);
298 shortname
= xstrdup(progname
);
300 /* figure out haw mant new helpers are needed. */
301 int need_new
= hlp
->childs
.needNew();
303 debugs(84, DBG_IMPORTANT
, "helperOpenServers: Starting " << need_new
<< "/" << hlp
->childs
.n_max
<< " '" << shortname
<< "' processes");
306 debugs(84, DBG_IMPORTANT
, "helperStatefulOpenServers: No '" << shortname
<< "' processes needed.");
309 char *procname
= (char *)xmalloc(strlen(shortname
) + 3);
311 snprintf(procname
, strlen(shortname
) + 3, "(%s)", shortname
);
313 args
[nargs
] = procname
;
316 for (wordlist
*w
= hlp
->cmdline
->next
; w
&& nargs
< HELPER_MAX_ARGS
; w
= w
->next
) {
317 args
[nargs
] = w
->key
;
324 assert(nargs
<= HELPER_MAX_ARGS
);
326 for (int k
= 0; k
< need_new
; ++k
) {
331 pid_t pid
= ipcCreate(hlp
->ipc_type
,
341 debugs(84, DBG_IMPORTANT
, "WARNING: Cannot run '" << progname
<< "' process.");
345 ++ hlp
->childs
.n_running
;
346 ++ hlp
->childs
.n_active
;
347 CBDATA_INIT_TYPE(helper_stateful_server
);
348 helper_stateful_server
*srv
= cbdataAlloc(helper_stateful_server
);
351 srv
->flags
.reserved
= false;
354 srv
->addr
= hlp
->addr
;
355 srv
->readPipe
= new Comm::Connection
;
356 srv
->readPipe
->fd
= rfd
;
357 srv
->writePipe
= new Comm::Connection
;
358 srv
->writePipe
->fd
= wfd
;
359 srv
->rbuf
= (char *)memAllocBuf(ReadBufMinSize
, &srv
->rbuf_sz
);
361 srv
->parent
= cbdataReference(hlp
);
363 if (hlp
->datapool
!= NULL
)
364 srv
->data
= hlp
->datapool
->alloc();
366 dlinkAddTail(srv
, &srv
->link
, &hlp
->servers
);
369 snprintf(fd_note_buf
, FD_DESC_SZ
, "%s #%d", shortname
, k
+ 1);
370 fd_note(rfd
, fd_note_buf
);
372 snprintf(fd_note_buf
, FD_DESC_SZ
, "reading %s #%d", shortname
, k
+ 1);
373 fd_note(rfd
, fd_note_buf
);
374 snprintf(fd_note_buf
, FD_DESC_SZ
, "writing %s #%d", shortname
, k
+ 1);
375 fd_note(wfd
, fd_note_buf
);
378 commSetNonBlocking(rfd
);
381 commSetNonBlocking(wfd
);
383 AsyncCall::Pointer closeCall
= asyncCall(5,4, "helperStatefulServerFree", cbdataDialer(helperStatefulServerFree
, srv
));
384 comm_add_close_handler(rfd
, closeCall
);
386 AsyncCall::Pointer call
= commCbCall(5,4, "helperStatefulHandleRead",
387 CommIoCbPtrFun(helperStatefulHandleRead
, srv
));
388 comm_read(srv
->readPipe
, srv
->rbuf
, srv
->rbuf_sz
- 1, call
);
391 hlp
->last_restart
= squid_curtime
;
392 safe_free(shortname
);
394 helperStatefulKickQueue(hlp
);
398 helperSubmit(helper
* hlp
, const char *buf
, HLPCB
* callback
, void *data
)
401 debugs(84, 3, "helperSubmit: hlp == NULL");
402 HelperReply nilReply
;
403 callback(data
, nilReply
);
407 helper_request
*r
= new helper_request
;
410 r
->callback
= callback
;
411 r
->data
= cbdataReference(data
);
412 r
->buf
= xstrdup(buf
);
414 if ((srv
= GetFirstAvailable(hlp
)))
415 helperDispatch(srv
, r
);
419 debugs(84, DBG_DATA
, Raw("buf", buf
, strlen(buf
)));
422 /// lastserver = "server last used as part of a reserved request sequence"
424 helperStatefulSubmit(statefulhelper
* hlp
, const char *buf
, HLPCB
* callback
, void *data
, helper_stateful_server
* lastserver
)
427 debugs(84, 3, "helperStatefulSubmit: hlp == NULL");
428 HelperReply nilReply
;
429 callback(data
, nilReply
);
433 helper_stateful_request
*r
= new helper_stateful_request
;
435 r
->callback
= callback
;
436 r
->data
= cbdataReference(data
);
439 r
->buf
= xstrdup(buf
);
446 if ((buf
!= NULL
) && lastserver
) {
447 debugs(84, 5, "StatefulSubmit with lastserver " << lastserver
);
448 assert(lastserver
->flags
.reserved
);
449 assert(!(lastserver
->request
));
451 debugs(84, 5, "StatefulSubmit dispatching");
452 helperStatefulDispatch(lastserver
, r
);
454 helper_stateful_server
*srv
;
455 if ((srv
= StatefulGetFirstAvailable(hlp
))) {
456 helperStatefulDispatch(srv
, r
);
458 StatefulEnqueue(hlp
, r
);
461 debugs(84, DBG_DATA
, "placeholder: '" << r
->placeholder
<<
462 "', " << Raw("buf", buf
, strlen(buf
)));
468 * helperStatefulReleaseServer tells the helper that whoever was
469 * using it no longer needs its services.
472 helperStatefulReleaseServer(helper_stateful_server
* srv
)
474 debugs(84, 3, HERE
<< "srv-" << srv
->index
<< " flags.reserved = " << srv
->flags
.reserved
);
475 if (!srv
->flags
.reserved
)
478 ++ srv
->stats
.releases
;
480 srv
->flags
.reserved
= false;
481 if (srv
->parent
->OnEmptyQueue
!= NULL
&& srv
->data
)
482 srv
->parent
->OnEmptyQueue(srv
->data
);
484 helperStatefulServerDone(srv
);
487 /** return a pointer to the stateful routines data area */
489 helperStatefulServerGetData(helper_stateful_server
* srv
)
495 * Dump some stats about the helper states to a StoreEntry
498 helperStats(StoreEntry
* sentry
, helper
* hlp
, const char *label
)
500 if (!helperStartStats(sentry
, hlp
, label
))
503 storeAppendPrintf(sentry
, "program: %s\n",
505 storeAppendPrintf(sentry
, "number active: %d of %d (%d shutting down)\n",
506 hlp
->childs
.n_active
, hlp
->childs
.n_max
, (hlp
->childs
.n_running
- hlp
->childs
.n_active
) );
507 storeAppendPrintf(sentry
, "requests sent: %d\n",
508 hlp
->stats
.requests
);
509 storeAppendPrintf(sentry
, "replies received: %d\n",
511 storeAppendPrintf(sentry
, "queue length: %d\n",
512 hlp
->stats
.queue_size
);
513 storeAppendPrintf(sentry
, "avg service time: %d msec\n",
514 hlp
->stats
.avg_svc_time
);
515 storeAppendPrintf(sentry
, "\n");
516 storeAppendPrintf(sentry
, "%7s\t%7s\t%7s\t%11s\t%11s\t%s\t%7s\t%7s\t%7s\n",
527 for (dlink_node
*link
= hlp
->servers
.head
; link
; link
= link
->next
) {
528 helper_server
*srv
= (helper_server
*)link
->data
;
529 double tt
= 0.001 * (srv
->requests
[0] ? tvSubMsec(srv
->requests
[0]->dispatch_time
, current_time
) : tvSubMsec(srv
->dispatch_time
, srv
->answer_time
));
530 storeAppendPrintf(sentry
, "%7d\t%7d\t%7d\t%11" PRIu64
"\t%11" PRIu64
"\t%c%c%c%c\t%7.3f\t%7d\t%s\n",
536 srv
->stats
.pending
? 'B' : ' ',
537 srv
->flags
.writing
? 'W' : ' ',
538 srv
->flags
.closing
? 'C' : ' ',
539 srv
->flags
.shutdown
? 'S' : ' ',
542 srv
->requests
[0] ? Format::QuoteMimeBlob(srv
->requests
[0]->buf
) : "(none)");
545 storeAppendPrintf(sentry
, "\nFlags key:\n\n");
546 storeAppendPrintf(sentry
, " B = BUSY\n");
547 storeAppendPrintf(sentry
, " W = WRITING\n");
548 storeAppendPrintf(sentry
, " C = CLOSING\n");
549 storeAppendPrintf(sentry
, " S = SHUTDOWN PENDING\n");
553 helperStatefulStats(StoreEntry
* sentry
, statefulhelper
* hlp
, const char *label
)
555 if (!helperStartStats(sentry
, hlp
, label
))
558 storeAppendPrintf(sentry
, "program: %s\n",
560 storeAppendPrintf(sentry
, "number active: %d of %d (%d shutting down)\n",
561 hlp
->childs
.n_active
, hlp
->childs
.n_max
, (hlp
->childs
.n_running
- hlp
->childs
.n_active
) );
562 storeAppendPrintf(sentry
, "requests sent: %d\n",
563 hlp
->stats
.requests
);
564 storeAppendPrintf(sentry
, "replies received: %d\n",
566 storeAppendPrintf(sentry
, "queue length: %d\n",
567 hlp
->stats
.queue_size
);
568 storeAppendPrintf(sentry
, "avg service time: %d msec\n",
569 hlp
->stats
.avg_svc_time
);
570 storeAppendPrintf(sentry
, "\n");
571 storeAppendPrintf(sentry
, "%7s\t%7s\t%7s\t%11s\t%11s\t%6s\t%7s\t%7s\t%7s\n",
582 for (dlink_node
*link
= hlp
->servers
.head
; link
; link
= link
->next
) {
583 helper_stateful_server
*srv
= (helper_stateful_server
*)link
->data
;
584 double tt
= 0.001 * tvSubMsec(srv
->dispatch_time
, srv
->flags
.busy
? current_time
: srv
->answer_time
);
585 storeAppendPrintf(sentry
, "%7d\t%7d\t%7d\t%11" PRIu64
"\t%11" PRIu64
"\t%c%c%c%c%c\t%7.3f\t%7d\t%s\n",
591 srv
->flags
.busy
? 'B' : ' ',
592 srv
->flags
.closing
? 'C' : ' ',
593 srv
->flags
.reserved
? 'R' : ' ',
594 srv
->flags
.shutdown
? 'S' : ' ',
595 srv
->request
? (srv
->request
->placeholder
? 'P' : ' ') : ' ',
598 srv
->request
? Format::QuoteMimeBlob(srv
->request
->buf
) : "(none)");
601 storeAppendPrintf(sentry
, "\nFlags key:\n\n");
602 storeAppendPrintf(sentry
, " B = BUSY\n");
603 storeAppendPrintf(sentry
, " C = CLOSING\n");
604 storeAppendPrintf(sentry
, " R = RESERVED\n");
605 storeAppendPrintf(sentry
, " S = SHUTDOWN PENDING\n");
606 storeAppendPrintf(sentry
, " P = PLACEHOLDER\n");
610 helperShutdown(helper
* hlp
)
612 dlink_node
*link
= hlp
->servers
.head
;
616 srv
= (helper_server
*)link
->data
;
619 if (srv
->flags
.shutdown
) {
620 debugs(84, 3, "helperShutdown: " << hlp
->id_name
<< " #" << srv
->index
+ 1 << " has already SHUT DOWN.");
624 assert(hlp
->childs
.n_active
> 0);
625 -- hlp
->childs
.n_active
;
626 srv
->flags
.shutdown
= true; /* request it to shut itself down */
628 if (srv
->flags
.closing
) {
629 debugs(84, 3, "helperShutdown: " << hlp
->id_name
<< " #" << srv
->index
+ 1 << " is CLOSING.");
633 if (srv
->stats
.pending
) {
634 debugs(84, 3, "helperShutdown: " << hlp
->id_name
<< " #" << srv
->index
+ 1 << " is BUSY.");
638 debugs(84, 3, "helperShutdown: " << hlp
->id_name
<< " #" << srv
->index
+ 1 << " shutting down.");
639 /* the rest of the details is dealt with in the helperServerFree
642 srv
->closePipesSafely();
647 helperStatefulShutdown(statefulhelper
* hlp
)
649 dlink_node
*link
= hlp
->servers
.head
;
650 helper_stateful_server
*srv
;
653 srv
= (helper_stateful_server
*)link
->data
;
656 if (srv
->flags
.shutdown
) {
657 debugs(84, 3, "helperStatefulShutdown: " << hlp
->id_name
<< " #" << srv
->index
+ 1 << " has already SHUT DOWN.");
661 assert(hlp
->childs
.n_active
> 0);
662 -- hlp
->childs
.n_active
;
663 srv
->flags
.shutdown
= true; /* request it to shut itself down */
665 if (srv
->flags
.busy
) {
666 debugs(84, 3, "helperStatefulShutdown: " << hlp
->id_name
<< " #" << srv
->index
+ 1 << " is BUSY.");
670 if (srv
->flags
.closing
) {
671 debugs(84, 3, "helperStatefulShutdown: " << hlp
->id_name
<< " #" << srv
->index
+ 1 << " is CLOSING.");
675 if (srv
->flags
.reserved
) {
677 debugs(84, 3, "helperStatefulShutdown: " << hlp
->id_name
<< " #" << srv
->index
+ 1 << " is RESERVED. Closing anyway.");
679 debugs(84, 3, "helperStatefulShutdown: " << hlp
->id_name
<< " #" << srv
->index
+ 1 << " is RESERVED. Not Shutting Down Yet.");
684 debugs(84, 3, "helperStatefulShutdown: " << hlp
->id_name
<< " #" << srv
->index
+ 1 << " shutting down.");
686 /* the rest of the details is dealt with in the helperStatefulServerFree
689 srv
->closePipesSafely();
695 /* note, don't free id_name, it probably points to static memory */
698 debugs(84, DBG_CRITICAL
, "WARNING: freeing " << id_name
<< " helper with " << stats
.queue_size
<< " requests queued");
701 /* ====================================================================== */
702 /* LOCAL FUNCTIONS */
703 /* ====================================================================== */
706 helperServerFree(helper_server
*srv
)
708 helper
*hlp
= srv
->parent
;
710 int i
, concurrency
= hlp
->childs
.concurrency
;
716 memFreeBuf(srv
->rbuf_sz
, srv
->rbuf
);
720 srv
->wqueue
->clean();
724 srv
->writebuf
->clean();
725 delete srv
->writebuf
;
726 srv
->writebuf
= NULL
;
729 if (Comm::IsConnOpen(srv
->writePipe
))
730 srv
->closeWritePipeSafely();
732 dlinkDelete(&srv
->link
, &hlp
->servers
);
734 assert(hlp
->childs
.n_running
> 0);
735 -- hlp
->childs
.n_running
;
737 if (!srv
->flags
.shutdown
) {
738 assert(hlp
->childs
.n_active
> 0);
739 -- hlp
->childs
.n_active
;
740 debugs(84, DBG_CRITICAL
, "WARNING: " << hlp
->id_name
<< " #" << srv
->index
+ 1 << " exited");
742 if (hlp
->childs
.needNew() > 0) {
743 debugs(80, DBG_IMPORTANT
, "Too few " << hlp
->id_name
<< " processes are running (need " << hlp
->childs
.needNew() << "/" << hlp
->childs
.n_max
<< ")");
745 if (hlp
->childs
.n_active
< hlp
->childs
.n_startup
&& hlp
->last_restart
> squid_curtime
- 30) {
746 if (srv
->stats
.replies
< 1)
747 fatalf("The %s helpers are crashing too rapidly, need help!\n", hlp
->id_name
);
749 debugs(80, DBG_CRITICAL
, "ERROR: The " << hlp
->id_name
<< " helpers are crashing too rapidly, need help!");
752 debugs(80, DBG_IMPORTANT
, "Starting new helpers");
753 helperOpenServers(hlp
);
757 for (i
= 0; i
< concurrency
; ++i
) {
758 // XXX: re-schedule these on another helper?
759 if ((r
= srv
->requests
[i
])) {
762 if (cbdataReferenceValidDone(r
->data
, &cbdata
)) {
763 HelperReply nilReply
;
764 r
->callback(cbdata
, nilReply
);
767 helperRequestFree(r
);
769 srv
->requests
[i
] = NULL
;
772 safe_free(srv
->requests
);
774 cbdataReferenceDone(srv
->parent
);
779 helperStatefulServerFree(helper_stateful_server
*srv
)
781 statefulhelper
*hlp
= srv
->parent
;
782 helper_stateful_request
*r
;
785 memFreeBuf(srv
->rbuf_sz
, srv
->rbuf
);
790 srv
->wqueue
->clean();
796 /* TODO: walk the local queue of requests and carry them all out */
797 if (Comm::IsConnOpen(srv
->writePipe
))
798 srv
->closeWritePipeSafely();
800 dlinkDelete(&srv
->link
, &hlp
->servers
);
802 assert(hlp
->childs
.n_running
> 0);
803 -- hlp
->childs
.n_running
;
805 if (!srv
->flags
.shutdown
) {
806 assert( hlp
->childs
.n_active
> 0);
807 -- hlp
->childs
.n_active
;
808 debugs(84, DBG_CRITICAL
, "WARNING: " << hlp
->id_name
<< " #" << srv
->index
+ 1 << " exited");
810 if (hlp
->childs
.needNew() > 0) {
811 debugs(80, DBG_IMPORTANT
, "Too few " << hlp
->id_name
<< " processes are running (need " << hlp
->childs
.needNew() << "/" << hlp
->childs
.n_max
<< ")");
813 if (hlp
->childs
.n_active
< hlp
->childs
.n_startup
&& hlp
->last_restart
> squid_curtime
- 30) {
814 if (srv
->stats
.replies
< 1)
815 fatalf("The %s helpers are crashing too rapidly, need help!\n", hlp
->id_name
);
817 debugs(80, DBG_CRITICAL
, "ERROR: The " << hlp
->id_name
<< " helpers are crashing too rapidly, need help!");
820 debugs(80, DBG_IMPORTANT
, "Starting new helpers");
821 helperStatefulOpenServers(hlp
);
825 if ((r
= srv
->request
)) {
828 if (cbdataReferenceValidDone(r
->data
, &cbdata
)) {
829 HelperReply nilReply
;
830 nilReply
.whichServer
= srv
;
831 r
->callback(cbdata
, nilReply
);
834 helperStatefulRequestFree(r
);
839 if (srv
->data
!= NULL
)
840 hlp
->datapool
->freeOne(srv
->data
);
842 cbdataReferenceDone(srv
->parent
);
847 /// Calls back with a pointer to the buffer with the helper output
849 helperReturnBuffer(int request_number
, helper_server
* srv
, helper
* hlp
, char * msg
, char * msg_end
)
851 helper_request
*r
= srv
->requests
[request_number
];
853 HLPCB
*callback
= r
->callback
;
855 srv
->requests
[request_number
] = NULL
;
860 if (cbdataReferenceValidDone(r
->data
, &cbdata
)) {
861 HelperReply
response(msg
, (msg_end
-msg
));
862 callback(cbdata
, response
);
865 -- srv
->stats
.pending
;
866 ++ srv
->stats
.replies
;
868 ++ hlp
->stats
.replies
;
870 srv
->answer_time
= current_time
;
872 srv
->dispatch_time
= r
->dispatch_time
;
874 hlp
->stats
.avg_svc_time
=
875 Math::intAverage(hlp
->stats
.avg_svc_time
,
876 tvSubMsec(r
->dispatch_time
, current_time
),
877 hlp
->stats
.replies
, REDIRECT_AV_FACTOR
);
879 helperRequestFree(r
);
881 debugs(84, DBG_IMPORTANT
, "helperHandleRead: unexpected reply on channel " <<
882 request_number
<< " from " << hlp
->id_name
<< " #" << srv
->index
+ 1 <<
883 " '" << srv
->rbuf
<< "'");
886 if (!srv
->flags
.shutdown
) {
887 helperKickQueue(hlp
);
888 } else if (!srv
->flags
.closing
&& !srv
->stats
.pending
) {
889 srv
->flags
.closing
=true;
890 srv
->writePipe
->close();
895 helperHandleRead(const Comm::ConnectionPointer
&conn
, char *buf
, size_t len
, comm_err_t flag
, int xerrno
, void *data
)
898 helper_server
*srv
= (helper_server
*)data
;
899 helper
*hlp
= srv
->parent
;
900 assert(cbdataReferenceValid(data
));
902 /* Bail out early on COMM_ERR_CLOSING - close handlers will tidy up for us */
904 if (flag
== COMM_ERR_CLOSING
) {
908 assert(conn
->fd
== srv
->readPipe
->fd
);
910 debugs(84, 5, "helperHandleRead: " << len
<< " bytes from " << hlp
->id_name
<< " #" << srv
->index
+ 1);
912 if (flag
!= COMM_OK
|| len
== 0) {
913 srv
->closePipesSafely();
918 srv
->rbuf
[srv
->roffset
] = '\0';
919 debugs(84, DBG_DATA
, Raw("accumulated", srv
->rbuf
, srv
->roffset
));
921 if (!srv
->stats
.pending
) {
922 /* someone spoke without being spoken to */
923 debugs(84, DBG_IMPORTANT
, "helperHandleRead: unexpected read from " <<
924 hlp
->id_name
<< " #" << srv
->index
+ 1 << ", " << (int)len
<<
925 " bytes '" << srv
->rbuf
<< "'");
931 while ((t
= strchr(srv
->rbuf
, hlp
->eom
))) {
932 /* end of reply found */
933 char *msg
= srv
->rbuf
;
936 debugs(84, 3, "helperHandleRead: end of reply found");
938 if (t
> srv
->rbuf
&& t
[-1] == '\r' && hlp
->eom
== '\n') {
940 // rewind to the \r octet which is the real terminal now
941 // and remember that we have to skip forward 2 places now.
948 if (hlp
->childs
.concurrency
) {
949 i
= strtol(msg
, &msg
, 10);
951 while (*msg
&& xisspace(*msg
))
955 helperReturnBuffer(i
, srv
, hlp
, msg
, t
);
956 srv
->roffset
-= (t
- srv
->rbuf
) + skip
;
957 memmove(srv
->rbuf
, t
+ skip
, srv
->roffset
);
958 srv
->rbuf
[srv
->roffset
] = '\0';
961 if (Comm::IsConnOpen(srv
->readPipe
) && !fd_table
[srv
->readPipe
->fd
].closing()) {
962 int spaceSize
= srv
->rbuf_sz
- srv
->roffset
- 1;
963 assert(spaceSize
>= 0);
965 // grow the input buffer if needed and possible
966 if (!spaceSize
&& srv
->rbuf_sz
+ 4096 <= ReadBufMaxSize
) {
967 srv
->rbuf
= (char *)memReallocBuf(srv
->rbuf
, srv
->rbuf_sz
+ 4096, &srv
->rbuf_sz
);
968 debugs(84, 3, HERE
<< "Grew read buffer to " << srv
->rbuf_sz
);
969 spaceSize
= srv
->rbuf_sz
- srv
->roffset
- 1;
970 assert(spaceSize
>= 0);
973 // quit reading if there is no space left
975 debugs(84, DBG_IMPORTANT
, "ERROR: Disconnecting from a " <<
976 "helper that overflowed " << srv
->rbuf_sz
<< "-byte " <<
977 "Squid input buffer: " << hlp
->id_name
<< " #" <<
979 srv
->closePipesSafely();
983 AsyncCall::Pointer call
= commCbCall(5,4, "helperHandleRead",
984 CommIoCbPtrFun(helperHandleRead
, srv
));
985 comm_read(srv
->readPipe
, srv
->rbuf
+ srv
->roffset
, spaceSize
, call
);
990 helperStatefulHandleRead(const Comm::ConnectionPointer
&conn
, char *buf
, size_t len
, comm_err_t flag
, int xerrno
, void *data
)
993 helper_stateful_server
*srv
= (helper_stateful_server
*)data
;
994 helper_stateful_request
*r
;
995 statefulhelper
*hlp
= srv
->parent
;
996 assert(cbdataReferenceValid(data
));
998 /* Bail out early on COMM_ERR_CLOSING - close handlers will tidy up for us */
1000 if (flag
== COMM_ERR_CLOSING
) {
1004 assert(conn
->fd
== srv
->readPipe
->fd
);
1006 debugs(84, 5, "helperStatefulHandleRead: " << len
<< " bytes from " <<
1007 hlp
->id_name
<< " #" << srv
->index
+ 1);
1009 if (flag
!= COMM_OK
|| len
== 0) {
1010 srv
->closePipesSafely();
1014 srv
->roffset
+= len
;
1015 srv
->rbuf
[srv
->roffset
] = '\0';
1017 debugs(84, DBG_DATA
, Raw("accumulated", srv
->rbuf
, srv
->roffset
));
1020 /* someone spoke without being spoken to */
1021 debugs(84, DBG_IMPORTANT
, "helperStatefulHandleRead: unexpected read from " <<
1022 hlp
->id_name
<< " #" << srv
->index
+ 1 << ", " << (int)len
<<
1023 " bytes '" << srv
->rbuf
<< "'");
1028 if ((t
= strchr(srv
->rbuf
, hlp
->eom
))) {
1029 /* end of reply found */
1032 debugs(84, 3, "helperStatefulHandleRead: end of reply found");
1034 if (t
> srv
->rbuf
&& t
[-1] == '\r' && hlp
->eom
== '\n') {
1036 // rewind to the \r octet which is the real terminal now
1037 // and remember that we have to skip forward 2 places now.
1044 if (r
&& cbdataReferenceValid(r
->data
)) {
1045 HelperReply
res(srv
->rbuf
, (t
- srv
->rbuf
));
1046 res
.whichServer
= srv
;
1047 r
->callback(r
->data
, res
);
1049 debugs(84, DBG_IMPORTANT
, "StatefulHandleRead: no callback data registered");
1052 // only skip off the \0's _after_ passing its location in HelperReply above
1055 srv
->flags
.busy
= false;
1057 * BUG: the below assumes that only one response per read() was received and discards any octets remaining.
1058 * Doing this prohibits concurrency support with multiple replies per read().
1059 * TODO: check that read() setup on these buffers pays attention to roffest!=0
1060 * TODO: check that replies bigger than the buffer are discarded and do not to affect future replies
1063 helperStatefulRequestFree(r
);
1064 srv
->request
= NULL
;
1066 -- srv
->stats
.pending
;
1067 ++ srv
->stats
.replies
;
1069 ++ hlp
->stats
.replies
;
1070 srv
->answer_time
= current_time
;
1071 hlp
->stats
.avg_svc_time
=
1072 Math::intAverage(hlp
->stats
.avg_svc_time
,
1073 tvSubMsec(srv
->dispatch_time
, current_time
),
1074 hlp
->stats
.replies
, REDIRECT_AV_FACTOR
);
1077 helperStatefulServerDone(srv
);
1079 helperStatefulReleaseServer(srv
);
1082 if (Comm::IsConnOpen(srv
->readPipe
) && !fd_table
[srv
->readPipe
->fd
].closing()) {
1083 int spaceSize
= srv
->rbuf_sz
- srv
->roffset
- 1;
1084 assert(spaceSize
>= 0);
1086 // grow the input buffer if needed and possible
1087 if (!spaceSize
&& srv
->rbuf_sz
+ 4096 <= ReadBufMaxSize
) {
1088 srv
->rbuf
= (char *)memReallocBuf(srv
->rbuf
, srv
->rbuf_sz
+ 4096, &srv
->rbuf_sz
);
1089 debugs(84, 3, HERE
<< "Grew read buffer to " << srv
->rbuf_sz
);
1090 spaceSize
= srv
->rbuf_sz
- srv
->roffset
- 1;
1091 assert(spaceSize
>= 0);
1094 // quit reading if there is no space left
1096 debugs(84, DBG_IMPORTANT
, "ERROR: Disconnecting from a " <<
1097 "helper that overflowed " << srv
->rbuf_sz
<< "-byte " <<
1098 "Squid input buffer: " << hlp
->id_name
<< " #" <<
1100 srv
->closePipesSafely();
1104 AsyncCall::Pointer call
= commCbCall(5,4, "helperStatefulHandleRead",
1105 CommIoCbPtrFun(helperStatefulHandleRead
, srv
));
1106 comm_read(srv
->readPipe
, srv
->rbuf
+ srv
->roffset
, spaceSize
, call
);
1111 Enqueue(helper
* hlp
, helper_request
* r
)
1113 dlink_node
*link
= (dlink_node
*)memAllocate(MEM_DLINK_NODE
);
1114 dlinkAddTail(r
, link
, &hlp
->queue
);
1115 ++ hlp
->stats
.queue_size
;
1117 /* do this first so idle=N has a chance to grow the child pool before it hits critical. */
1118 if (hlp
->childs
.needNew() > 0) {
1119 debugs(84, DBG_CRITICAL
, "Starting new " << hlp
->id_name
<< " helpers...");
1120 helperOpenServers(hlp
);
1124 if (hlp
->stats
.queue_size
< (int)hlp
->childs
.n_running
)
1127 if (squid_curtime
- hlp
->last_queue_warn
< 600)
1130 if (shutting_down
|| reconfiguring
)
1133 hlp
->last_queue_warn
= squid_curtime
;
1135 debugs(84, DBG_CRITICAL
, "WARNING: All " << hlp
->childs
.n_active
<< "/" << hlp
->childs
.n_max
<< " " << hlp
->id_name
<< " processes are busy.");
1136 debugs(84, DBG_CRITICAL
, "WARNING: " << hlp
->stats
.queue_size
<< " pending requests queued");
1137 debugs(84, DBG_CRITICAL
, "WARNING: Consider increasing the number of " << hlp
->id_name
<< " processes in your config file.");
1139 if (hlp
->stats
.queue_size
> (int)hlp
->childs
.n_running
* 2)
1140 fatalf("Too many queued %s requests", hlp
->id_name
);
1144 StatefulEnqueue(statefulhelper
* hlp
, helper_stateful_request
* r
)
1146 dlink_node
*link
= (dlink_node
*)memAllocate(MEM_DLINK_NODE
);
1147 dlinkAddTail(r
, link
, &hlp
->queue
);
1148 ++ hlp
->stats
.queue_size
;
1150 /* do this first so idle=N has a chance to grow the child pool before it hits critical. */
1151 if (hlp
->childs
.needNew() > 0) {
1152 debugs(84, DBG_CRITICAL
, "Starting new " << hlp
->id_name
<< " helpers...");
1153 helperStatefulOpenServers(hlp
);
1157 if (hlp
->stats
.queue_size
< (int)hlp
->childs
.n_running
)
1160 if (hlp
->stats
.queue_size
> (int)hlp
->childs
.n_running
* 2)
1161 fatalf("Too many queued %s requests", hlp
->id_name
);
1163 if (squid_curtime
- hlp
->last_queue_warn
< 600)
1166 if (shutting_down
|| reconfiguring
)
1169 hlp
->last_queue_warn
= squid_curtime
;
1171 debugs(84, DBG_CRITICAL
, "WARNING: All " << hlp
->childs
.n_active
<< "/" << hlp
->childs
.n_max
<< " " << hlp
->id_name
<< " processes are busy.");
1172 debugs(84, DBG_CRITICAL
, "WARNING: " << hlp
->stats
.queue_size
<< " pending requests queued");
1173 debugs(84, DBG_CRITICAL
, "WARNING: Consider increasing the number of " << hlp
->id_name
<< " processes in your config file.");
1176 static helper_request
*
1177 Dequeue(helper
* hlp
)
1180 helper_request
*r
= NULL
;
1182 if ((link
= hlp
->queue
.head
)) {
1183 r
= (helper_request
*)link
->data
;
1184 dlinkDelete(link
, &hlp
->queue
);
1185 memFree(link
, MEM_DLINK_NODE
);
1186 -- hlp
->stats
.queue_size
;
1192 static helper_stateful_request
*
1193 StatefulDequeue(statefulhelper
* hlp
)
1196 helper_stateful_request
*r
= NULL
;
1198 if ((link
= hlp
->queue
.head
)) {
1199 r
= (helper_stateful_request
*)link
->data
;
1200 dlinkDelete(link
, &hlp
->queue
);
1201 memFree(link
, MEM_DLINK_NODE
);
1202 -- hlp
->stats
.queue_size
;
1208 static helper_server
*
1209 GetFirstAvailable(helper
* hlp
)
1213 helper_server
*selected
= NULL
;
1214 debugs(84, 5, "GetFirstAvailable: Running servers " << hlp
->childs
.n_running
);
1216 if (hlp
->childs
.n_running
== 0)
1219 /* Find "least" loaded helper (approx) */
1220 for (n
= hlp
->servers
.head
; n
!= NULL
; n
= n
->next
) {
1221 srv
= (helper_server
*)n
->data
;
1223 if (selected
&& selected
->stats
.pending
<= srv
->stats
.pending
)
1226 if (srv
->flags
.shutdown
)
1229 if (!srv
->stats
.pending
)
1240 /* Check for overload */
1242 debugs(84, 5, "GetFirstAvailable: None available.");
1246 if (selected
->stats
.pending
>= (hlp
->childs
.concurrency
? hlp
->childs
.concurrency
: 1)) {
1247 debugs(84, 3, "GetFirstAvailable: Least-loaded helper is overloaded!");
1251 debugs(84, 5, "GetFirstAvailable: returning srv-" << selected
->index
);
1255 static helper_stateful_server
*
1256 StatefulGetFirstAvailable(statefulhelper
* hlp
)
1259 helper_stateful_server
*srv
= NULL
;
1260 debugs(84, 5, "StatefulGetFirstAvailable: Running servers " << hlp
->childs
.n_running
);
1262 if (hlp
->childs
.n_running
== 0)
1265 for (n
= hlp
->servers
.head
; n
!= NULL
; n
= n
->next
) {
1266 srv
= (helper_stateful_server
*)n
->data
;
1268 if (srv
->flags
.busy
)
1271 if (srv
->flags
.reserved
)
1274 if (srv
->flags
.shutdown
)
1277 if ((hlp
->IsAvailable
!= NULL
) && (srv
->data
!= NULL
) && !(hlp
->IsAvailable(srv
->data
)))
1280 debugs(84, 5, "StatefulGetFirstAvailable: returning srv-" << srv
->index
);
1284 debugs(84, 5, "StatefulGetFirstAvailable: None available.");
1289 helperDispatchWriteDone(const Comm::ConnectionPointer
&conn
, char *buf
, size_t len
, comm_err_t flag
, int xerrno
, void *data
)
1291 helper_server
*srv
= (helper_server
*)data
;
1293 srv
->writebuf
->clean();
1294 delete srv
->writebuf
;
1295 srv
->writebuf
= NULL
;
1296 srv
->flags
.writing
= false;
1298 if (flag
!= COMM_OK
) {
1299 /* Helper server has crashed */
1300 debugs(84, DBG_CRITICAL
, "helperDispatch: Helper " << srv
->parent
->id_name
<< " #" << srv
->index
+ 1 << " has crashed");
1304 if (!srv
->wqueue
->isNull()) {
1305 srv
->writebuf
= srv
->wqueue
;
1306 srv
->wqueue
= new MemBuf
;
1307 srv
->flags
.writing
= true;
1308 AsyncCall::Pointer call
= commCbCall(5,5, "helperDispatchWriteDone",
1309 CommIoCbPtrFun(helperDispatchWriteDone
, srv
));
1310 Comm::Write(srv
->writePipe
, srv
->writebuf
->content(), srv
->writebuf
->contentSize(), call
, NULL
);
1315 helperDispatch(helper_server
* srv
, helper_request
* r
)
1317 helper
*hlp
= srv
->parent
;
1318 helper_request
**ptr
= NULL
;
1321 if (!cbdataReferenceValid(r
->data
)) {
1322 debugs(84, DBG_IMPORTANT
, "helperDispatch: invalid callback data");
1323 helperRequestFree(r
);
1327 for (slot
= 0; slot
< (hlp
->childs
.concurrency
? hlp
->childs
.concurrency
: 1); ++slot
) {
1328 if (!srv
->requests
[slot
]) {
1329 ptr
= &srv
->requests
[slot
];
1336 r
->dispatch_time
= current_time
;
1338 if (srv
->wqueue
->isNull())
1339 srv
->wqueue
->init();
1341 if (hlp
->childs
.concurrency
)
1342 srv
->wqueue
->Printf("%d %s", slot
, r
->buf
);
1344 srv
->wqueue
->append(r
->buf
, strlen(r
->buf
));
1346 if (!srv
->flags
.writing
) {
1347 assert(NULL
== srv
->writebuf
);
1348 srv
->writebuf
= srv
->wqueue
;
1349 srv
->wqueue
= new MemBuf
;
1350 srv
->flags
.writing
= true;
1351 AsyncCall::Pointer call
= commCbCall(5,5, "helperDispatchWriteDone",
1352 CommIoCbPtrFun(helperDispatchWriteDone
, srv
));
1353 Comm::Write(srv
->writePipe
, srv
->writebuf
->content(), srv
->writebuf
->contentSize(), call
, NULL
);
1356 debugs(84, 5, "helperDispatch: Request sent to " << hlp
->id_name
<< " #" << srv
->index
+ 1 << ", " << strlen(r
->buf
) << " bytes");
1359 ++ srv
->stats
.pending
;
1360 ++ hlp
->stats
.requests
;
1364 helperStatefulDispatchWriteDone(const Comm::ConnectionPointer
&conn
, char *buf
, size_t len
, comm_err_t flag
,
1365 int xerrno
, void *data
)
1371 helperStatefulDispatch(helper_stateful_server
* srv
, helper_stateful_request
* r
)
1373 statefulhelper
*hlp
= srv
->parent
;
1375 if (!cbdataReferenceValid(r
->data
)) {
1376 debugs(84, DBG_IMPORTANT
, "helperStatefulDispatch: invalid callback data");
1377 helperStatefulRequestFree(r
);
1378 helperStatefulReleaseServer(srv
);
1382 debugs(84, 9, "helperStatefulDispatch busying helper " << hlp
->id_name
<< " #" << srv
->index
+ 1);
1384 if (r
->placeholder
== 1) {
1385 /* a callback is needed before this request can _use_ a helper. */
1386 /* we don't care about releasing this helper. The request NEVER
1387 * gets to the helper. So we throw away the return code */
1388 HelperReply nilReply
;
1389 nilReply
.whichServer
= srv
;
1390 r
->callback(r
->data
, nilReply
);
1391 /* throw away the placeholder */
1392 helperStatefulRequestFree(r
);
1393 /* and push the queue. Note that the callback may have submitted a new
1394 * request to the helper which is why we test for the request */
1396 if (srv
->request
== NULL
)
1397 helperStatefulServerDone(srv
);
1402 srv
->flags
.busy
= true;
1403 srv
->flags
.reserved
= true;
1405 srv
->dispatch_time
= current_time
;
1406 AsyncCall::Pointer call
= commCbCall(5,5, "helperStatefulDispatchWriteDone",
1407 CommIoCbPtrFun(helperStatefulDispatchWriteDone
, hlp
));
1408 Comm::Write(srv
->writePipe
, r
->buf
, strlen(r
->buf
), call
, NULL
);
1409 debugs(84, 5, "helperStatefulDispatch: Request sent to " <<
1410 hlp
->id_name
<< " #" << srv
->index
+ 1 << ", " <<
1411 (int) strlen(r
->buf
) << " bytes");
1414 ++ srv
->stats
.pending
;
1415 ++ hlp
->stats
.requests
;
1419 helperKickQueue(helper
* hlp
)
1424 while ((srv
= GetFirstAvailable(hlp
)) && (r
= Dequeue(hlp
)))
1425 helperDispatch(srv
, r
);
1429 helperStatefulKickQueue(statefulhelper
* hlp
)
1431 helper_stateful_request
*r
;
1432 helper_stateful_server
*srv
;
1434 while ((srv
= StatefulGetFirstAvailable(hlp
)) && (r
= StatefulDequeue(hlp
)))
1435 helperStatefulDispatch(srv
, r
);
1439 helperStatefulServerDone(helper_stateful_server
* srv
)
1441 if (!srv
->flags
.shutdown
) {
1442 helperStatefulKickQueue(srv
->parent
);
1443 } else if (!srv
->flags
.closing
&& !srv
->flags
.reserved
&& !srv
->flags
.busy
) {
1444 srv
->closeWritePipeSafely();
1450 helperRequestFree(helper_request
* r
)
1452 cbdataReferenceDone(r
->data
);
1458 helperStatefulRequestFree(helper_stateful_request
* r
)
1461 cbdataReferenceDone(r
->data
);
1467 // TODO: should helper_ and helper_stateful_ have a common parent?
1469 helperStartStats(StoreEntry
*sentry
, void *hlp
, const char *label
)
1473 storeAppendPrintf(sentry
, "%s: unavailable\n", label
);
1478 storeAppendPrintf(sentry
, "%s:\n", label
);