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/Read.h"
38 #include "comm/Write.h"
41 #include "format/Quoting.h"
46 #include "SquidMath.h"
47 #include "SquidTime.h"
51 #define HELPER_MAX_ARGS 64
53 /** Initial Squid input buffer size. Helper responses may exceed this, and
54 * Squid will grow the input buffer as needed, up to ReadBufMaxSize.
56 const size_t ReadBufMinSize(4*1024);
58 /** Maximum safe size of a helper-to-Squid response message plus one.
59 * Squid will warn and close the stream if a helper sends a too-big response.
60 * ssl_crtd helper is known to produce responses of at least 10KB in size.
61 * Some undocumented helpers are known to produce responses exceeding 8KB.
63 const size_t ReadBufMaxSize(32*1024);
65 static IOCB helperHandleRead
;
66 static IOCB helperStatefulHandleRead
;
67 static void helperServerFree(helper_server
*srv
);
68 static void helperStatefulServerFree(helper_stateful_server
*srv
);
69 static void Enqueue(helper
* hlp
, helper_request
*);
70 static helper_request
*Dequeue(helper
* hlp
);
71 static helper_stateful_request
*StatefulDequeue(statefulhelper
* hlp
);
72 static helper_server
*GetFirstAvailable(helper
* hlp
);
73 static helper_stateful_server
*StatefulGetFirstAvailable(statefulhelper
* hlp
);
74 static void helperDispatch(helper_server
* srv
, helper_request
* r
);
75 static void helperStatefulDispatch(helper_stateful_server
* srv
, helper_stateful_request
* r
);
76 static void helperKickQueue(helper
* hlp
);
77 static void helperStatefulKickQueue(statefulhelper
* hlp
);
78 static void helperStatefulServerDone(helper_stateful_server
* srv
);
79 static void helperRequestFree(helper_request
* r
);
80 static void helperStatefulRequestFree(helper_stateful_request
* r
);
81 static void StatefulEnqueue(statefulhelper
* hlp
, helper_stateful_request
* r
);
82 static bool helperStartStats(StoreEntry
*sentry
, void *hlp
, const char *label
);
84 CBDATA_CLASS_INIT(helper
);
85 CBDATA_CLASS_INIT(helper_server
);
86 CBDATA_CLASS_INIT(statefulhelper
);
87 CBDATA_CLASS_INIT(helper_stateful_server
);
89 InstanceIdDefinitions(HelperServerBase
, "Hlpr");
92 HelperServerBase::initStats()
101 HelperServerBase::closePipesSafely()
104 shutdown(writePipe
->fd
, SD_BOTH
);
107 flags
.closing
= true;
108 if (readPipe
->fd
== writePipe
->fd
)
116 if (WaitForSingleObject(hIpc
, 5000) != WAIT_OBJECT_0
) {
118 debugs(84, DBG_IMPORTANT
, "WARNING: " << hlp
->id_name
<<
119 " #" << index
<< " (" << hlp
->cmdline
->key
<< "," <<
120 (long int)pid
<< ") didn't exit in 5 seconds");
128 HelperServerBase::closeWritePipeSafely()
131 shutdown(writePipe
->fd
, (readPipe
->fd
== writePipe
->fd
? SD_BOTH
: SD_SEND
));
134 flags
.closing
= true;
135 if (readPipe
->fd
== writePipe
->fd
)
141 if (WaitForSingleObject(hIpc
, 5000) != WAIT_OBJECT_0
) {
143 debugs(84, DBG_IMPORTANT
, "WARNING: " << hlp
->id_name
<<
144 " #" << index
<< " (" << hlp
->cmdline
->key
<< "," <<
145 (long int)pid
<< ") didn't exit in 5 seconds");
153 helperOpenServers(helper
* hlp
)
159 const char *args
[HELPER_MAX_ARGS
+1]; // save space for a NULL terminator
160 char fd_note_buf
[FD_DESC_SZ
];
170 if (hlp
->cmdline
== NULL
)
173 progname
= hlp
->cmdline
->key
;
175 if ((s
= strrchr(progname
, '/')))
176 shortname
= xstrdup(s
+ 1);
178 shortname
= xstrdup(progname
);
180 /* figure out how many new child are actually needed. */
181 int need_new
= hlp
->childs
.needNew();
183 debugs(84, DBG_IMPORTANT
, "helperOpenServers: Starting " << need_new
<< "/" << hlp
->childs
.n_max
<< " '" << shortname
<< "' processes");
186 debugs(84, DBG_IMPORTANT
, "helperOpenServers: No '" << shortname
<< "' processes needed.");
189 procname
= (char *)xmalloc(strlen(shortname
) + 3);
191 snprintf(procname
, strlen(shortname
) + 3, "(%s)", shortname
);
193 args
[nargs
] = procname
;
196 for (w
= hlp
->cmdline
->next
; w
&& nargs
< HELPER_MAX_ARGS
; w
= w
->next
) {
197 args
[nargs
] = w
->key
;
204 assert(nargs
<= HELPER_MAX_ARGS
);
206 for (k
= 0; k
< need_new
; ++k
) {
209 pid
= ipcCreate(hlp
->ipc_type
,
219 debugs(84, DBG_IMPORTANT
, "WARNING: Cannot run '" << progname
<< "' process.");
223 ++ hlp
->childs
.n_running
;
224 ++ hlp
->childs
.n_active
;
225 srv
= new helper_server
;
229 srv
->addr
= hlp
->addr
;
230 srv
->readPipe
= new Comm::Connection
;
231 srv
->readPipe
->fd
= rfd
;
232 srv
->writePipe
= new Comm::Connection
;
233 srv
->writePipe
->fd
= wfd
;
234 srv
->rbuf
= (char *)memAllocBuf(ReadBufMinSize
, &srv
->rbuf_sz
);
235 srv
->wqueue
= new MemBuf
;
237 srv
->requests
= (helper_request
**)xcalloc(hlp
->childs
.concurrency
? hlp
->childs
.concurrency
: 1, sizeof(*srv
->requests
));
238 srv
->parent
= cbdataReference(hlp
);
239 dlinkAddTail(srv
, &srv
->link
, &hlp
->servers
);
242 snprintf(fd_note_buf
, FD_DESC_SZ
, "%s #%d", shortname
, k
+ 1);
243 fd_note(rfd
, fd_note_buf
);
245 snprintf(fd_note_buf
, FD_DESC_SZ
, "reading %s #%d", shortname
, k
+ 1);
246 fd_note(rfd
, fd_note_buf
);
247 snprintf(fd_note_buf
, FD_DESC_SZ
, "writing %s #%d", shortname
, k
+ 1);
248 fd_note(wfd
, fd_note_buf
);
251 commSetNonBlocking(rfd
);
254 commSetNonBlocking(wfd
);
256 AsyncCall::Pointer closeCall
= asyncCall(5,4, "helperServerFree", cbdataDialer(helperServerFree
, srv
));
257 comm_add_close_handler(rfd
, closeCall
);
259 AsyncCall::Pointer call
= commCbCall(5,4, "helperHandleRead",
260 CommIoCbPtrFun(helperHandleRead
, srv
));
261 comm_read(srv
->readPipe
, srv
->rbuf
, srv
->rbuf_sz
- 1, call
);
264 hlp
->last_restart
= squid_curtime
;
265 safe_free(shortname
);
267 helperKickQueue(hlp
);
273 * helperStatefulOpenServers: create the stateful child helper processes
276 helperStatefulOpenServers(statefulhelper
* hlp
)
279 const char *args
[HELPER_MAX_ARGS
+1]; // save space for a NULL terminator
280 char fd_note_buf
[FD_DESC_SZ
];
283 if (hlp
->cmdline
== NULL
)
286 if (hlp
->childs
.concurrency
)
287 debugs(84, DBG_CRITICAL
, "ERROR: concurrency= is not yet supported for stateful helpers ('" << hlp
->cmdline
<< "')");
289 char *progname
= hlp
->cmdline
->key
;
292 if ((s
= strrchr(progname
, '/')))
293 shortname
= xstrdup(s
+ 1);
295 shortname
= xstrdup(progname
);
297 /* figure out haw mant new helpers are needed. */
298 int need_new
= hlp
->childs
.needNew();
300 debugs(84, DBG_IMPORTANT
, "helperOpenServers: Starting " << need_new
<< "/" << hlp
->childs
.n_max
<< " '" << shortname
<< "' processes");
303 debugs(84, DBG_IMPORTANT
, "helperStatefulOpenServers: No '" << shortname
<< "' processes needed.");
306 char *procname
= (char *)xmalloc(strlen(shortname
) + 3);
308 snprintf(procname
, strlen(shortname
) + 3, "(%s)", shortname
);
310 args
[nargs
] = procname
;
313 for (wordlist
*w
= hlp
->cmdline
->next
; w
&& nargs
< HELPER_MAX_ARGS
; w
= w
->next
) {
314 args
[nargs
] = w
->key
;
321 assert(nargs
<= HELPER_MAX_ARGS
);
323 for (int k
= 0; k
< need_new
; ++k
) {
328 pid_t pid
= ipcCreate(hlp
->ipc_type
,
338 debugs(84, DBG_IMPORTANT
, "WARNING: Cannot run '" << progname
<< "' process.");
342 ++ hlp
->childs
.n_running
;
343 ++ hlp
->childs
.n_active
;
344 helper_stateful_server
*srv
= new helper_stateful_server
;
347 srv
->flags
.reserved
= false;
349 srv
->addr
= hlp
->addr
;
350 srv
->readPipe
= new Comm::Connection
;
351 srv
->readPipe
->fd
= rfd
;
352 srv
->writePipe
= new Comm::Connection
;
353 srv
->writePipe
->fd
= wfd
;
354 srv
->rbuf
= (char *)memAllocBuf(ReadBufMinSize
, &srv
->rbuf_sz
);
356 srv
->parent
= cbdataReference(hlp
);
358 if (hlp
->datapool
!= NULL
)
359 srv
->data
= hlp
->datapool
->alloc();
361 dlinkAddTail(srv
, &srv
->link
, &hlp
->servers
);
364 snprintf(fd_note_buf
, FD_DESC_SZ
, "%s #%d", shortname
, k
+ 1);
365 fd_note(rfd
, fd_note_buf
);
367 snprintf(fd_note_buf
, FD_DESC_SZ
, "reading %s #%d", shortname
, k
+ 1);
368 fd_note(rfd
, fd_note_buf
);
369 snprintf(fd_note_buf
, FD_DESC_SZ
, "writing %s #%d", shortname
, k
+ 1);
370 fd_note(wfd
, fd_note_buf
);
373 commSetNonBlocking(rfd
);
376 commSetNonBlocking(wfd
);
378 AsyncCall::Pointer closeCall
= asyncCall(5,4, "helperStatefulServerFree", cbdataDialer(helperStatefulServerFree
, srv
));
379 comm_add_close_handler(rfd
, closeCall
);
381 AsyncCall::Pointer call
= commCbCall(5,4, "helperStatefulHandleRead",
382 CommIoCbPtrFun(helperStatefulHandleRead
, srv
));
383 comm_read(srv
->readPipe
, srv
->rbuf
, srv
->rbuf_sz
- 1, call
);
386 hlp
->last_restart
= squid_curtime
;
387 safe_free(shortname
);
389 helperStatefulKickQueue(hlp
);
393 helperSubmit(helper
* hlp
, const char *buf
, HLPCB
* callback
, void *data
)
396 debugs(84, 3, "helperSubmit: hlp == NULL");
397 HelperReply nilReply
;
398 callback(data
, nilReply
);
402 helper_request
*r
= new helper_request
;
405 r
->callback
= callback
;
406 r
->data
= cbdataReference(data
);
407 r
->buf
= xstrdup(buf
);
409 if ((srv
= GetFirstAvailable(hlp
)))
410 helperDispatch(srv
, r
);
414 debugs(84, DBG_DATA
, Raw("buf", buf
, strlen(buf
)));
417 /// lastserver = "server last used as part of a reserved request sequence"
419 helperStatefulSubmit(statefulhelper
* hlp
, const char *buf
, HLPCB
* callback
, void *data
, helper_stateful_server
* lastserver
)
422 debugs(84, 3, "helperStatefulSubmit: hlp == NULL");
423 HelperReply nilReply
;
424 callback(data
, nilReply
);
428 helper_stateful_request
*r
= new helper_stateful_request
;
430 r
->callback
= callback
;
431 r
->data
= cbdataReference(data
);
434 r
->buf
= xstrdup(buf
);
441 if ((buf
!= NULL
) && lastserver
) {
442 debugs(84, 5, "StatefulSubmit with lastserver " << lastserver
);
443 assert(lastserver
->flags
.reserved
);
444 assert(!(lastserver
->request
));
446 debugs(84, 5, "StatefulSubmit dispatching");
447 helperStatefulDispatch(lastserver
, r
);
449 helper_stateful_server
*srv
;
450 if ((srv
= StatefulGetFirstAvailable(hlp
))) {
451 helperStatefulDispatch(srv
, r
);
453 StatefulEnqueue(hlp
, r
);
456 debugs(84, DBG_DATA
, "placeholder: '" << r
->placeholder
<<
457 "', " << Raw("buf", buf
, strlen(buf
)));
463 * helperStatefulReleaseServer tells the helper that whoever was
464 * using it no longer needs its services.
467 helperStatefulReleaseServer(helper_stateful_server
* srv
)
469 debugs(84, 3, HERE
<< "srv-" << srv
->index
<< " flags.reserved = " << srv
->flags
.reserved
);
470 if (!srv
->flags
.reserved
)
473 ++ srv
->stats
.releases
;
475 srv
->flags
.reserved
= false;
476 if (srv
->parent
->OnEmptyQueue
!= NULL
&& srv
->data
)
477 srv
->parent
->OnEmptyQueue(srv
->data
);
479 helperStatefulServerDone(srv
);
482 /** return a pointer to the stateful routines data area */
484 helperStatefulServerGetData(helper_stateful_server
* srv
)
490 * Dump some stats about the helper states to a StoreEntry
493 helperStats(StoreEntry
* sentry
, helper
* hlp
, const char *label
)
495 if (!helperStartStats(sentry
, hlp
, label
))
498 storeAppendPrintf(sentry
, "program: %s\n",
500 storeAppendPrintf(sentry
, "number active: %d of %d (%d shutting down)\n",
501 hlp
->childs
.n_active
, hlp
->childs
.n_max
, (hlp
->childs
.n_running
- hlp
->childs
.n_active
) );
502 storeAppendPrintf(sentry
, "requests sent: %d\n",
503 hlp
->stats
.requests
);
504 storeAppendPrintf(sentry
, "replies received: %d\n",
506 storeAppendPrintf(sentry
, "queue length: %d\n",
507 hlp
->stats
.queue_size
);
508 storeAppendPrintf(sentry
, "avg service time: %d msec\n",
509 hlp
->stats
.avg_svc_time
);
510 storeAppendPrintf(sentry
, "\n");
511 storeAppendPrintf(sentry
, "%7s\t%7s\t%7s\t%11s\t%11s\t%s\t%7s\t%7s\t%7s\n",
522 for (dlink_node
*link
= hlp
->servers
.head
; link
; link
= link
->next
) {
523 helper_server
*srv
= (helper_server
*)link
->data
;
524 double tt
= 0.001 * (srv
->requests
[0] ? tvSubMsec(srv
->requests
[0]->dispatch_time
, current_time
) : tvSubMsec(srv
->dispatch_time
, srv
->answer_time
));
525 storeAppendPrintf(sentry
, "%7u\t%7d\t%7d\t%11" PRIu64
"\t%11" PRIu64
"\t%c%c%c%c\t%7.3f\t%7d\t%s\n",
531 srv
->stats
.pending
? 'B' : ' ',
532 srv
->flags
.writing
? 'W' : ' ',
533 srv
->flags
.closing
? 'C' : ' ',
534 srv
->flags
.shutdown
? 'S' : ' ',
537 srv
->requests
[0] ? Format::QuoteMimeBlob(srv
->requests
[0]->buf
) : "(none)");
540 storeAppendPrintf(sentry
, "\nFlags key:\n\n");
541 storeAppendPrintf(sentry
, " B = BUSY\n");
542 storeAppendPrintf(sentry
, " W = WRITING\n");
543 storeAppendPrintf(sentry
, " C = CLOSING\n");
544 storeAppendPrintf(sentry
, " S = SHUTDOWN PENDING\n");
548 helperStatefulStats(StoreEntry
* sentry
, statefulhelper
* hlp
, const char *label
)
550 if (!helperStartStats(sentry
, hlp
, label
))
553 storeAppendPrintf(sentry
, "program: %s\n",
555 storeAppendPrintf(sentry
, "number active: %d of %d (%d shutting down)\n",
556 hlp
->childs
.n_active
, hlp
->childs
.n_max
, (hlp
->childs
.n_running
- hlp
->childs
.n_active
) );
557 storeAppendPrintf(sentry
, "requests sent: %d\n",
558 hlp
->stats
.requests
);
559 storeAppendPrintf(sentry
, "replies received: %d\n",
561 storeAppendPrintf(sentry
, "queue length: %d\n",
562 hlp
->stats
.queue_size
);
563 storeAppendPrintf(sentry
, "avg service time: %d msec\n",
564 hlp
->stats
.avg_svc_time
);
565 storeAppendPrintf(sentry
, "\n");
566 storeAppendPrintf(sentry
, "%7s\t%7s\t%7s\t%11s\t%11s\t%6s\t%7s\t%7s\t%7s\n",
577 for (dlink_node
*link
= hlp
->servers
.head
; link
; link
= link
->next
) {
578 helper_stateful_server
*srv
= (helper_stateful_server
*)link
->data
;
579 double tt
= 0.001 * tvSubMsec(srv
->dispatch_time
, srv
->flags
.busy
? current_time
: srv
->answer_time
);
580 storeAppendPrintf(sentry
, "%7u\t%7d\t%7d\t%11" PRIu64
"\t%11" PRIu64
"\t%c%c%c%c%c\t%7.3f\t%7d\t%s\n",
586 srv
->flags
.busy
? 'B' : ' ',
587 srv
->flags
.closing
? 'C' : ' ',
588 srv
->flags
.reserved
? 'R' : ' ',
589 srv
->flags
.shutdown
? 'S' : ' ',
590 srv
->request
? (srv
->request
->placeholder
? 'P' : ' ') : ' ',
593 srv
->request
? Format::QuoteMimeBlob(srv
->request
->buf
) : "(none)");
596 storeAppendPrintf(sentry
, "\nFlags key:\n\n");
597 storeAppendPrintf(sentry
, " B = BUSY\n");
598 storeAppendPrintf(sentry
, " C = CLOSING\n");
599 storeAppendPrintf(sentry
, " R = RESERVED\n");
600 storeAppendPrintf(sentry
, " S = SHUTDOWN PENDING\n");
601 storeAppendPrintf(sentry
, " P = PLACEHOLDER\n");
605 helperShutdown(helper
* hlp
)
607 dlink_node
*link
= hlp
->servers
.head
;
611 srv
= (helper_server
*)link
->data
;
614 if (srv
->flags
.shutdown
) {
615 debugs(84, 3, "helperShutdown: " << hlp
->id_name
<< " #" << srv
->index
<< " has already SHUT DOWN.");
619 assert(hlp
->childs
.n_active
> 0);
620 -- hlp
->childs
.n_active
;
621 srv
->flags
.shutdown
= true; /* request it to shut itself down */
623 if (srv
->flags
.closing
) {
624 debugs(84, 3, "helperShutdown: " << hlp
->id_name
<< " #" << srv
->index
<< " is CLOSING.");
628 if (srv
->stats
.pending
) {
629 debugs(84, 3, "helperShutdown: " << hlp
->id_name
<< " #" << srv
->index
<< " is BUSY.");
633 debugs(84, 3, "helperShutdown: " << hlp
->id_name
<< " #" << srv
->index
<< " shutting down.");
634 /* the rest of the details is dealt with in the helperServerFree
637 srv
->closePipesSafely();
642 helperStatefulShutdown(statefulhelper
* hlp
)
644 dlink_node
*link
= hlp
->servers
.head
;
645 helper_stateful_server
*srv
;
648 srv
= (helper_stateful_server
*)link
->data
;
651 if (srv
->flags
.shutdown
) {
652 debugs(84, 3, "helperStatefulShutdown: " << hlp
->id_name
<< " #" << srv
->index
<< " has already SHUT DOWN.");
656 assert(hlp
->childs
.n_active
> 0);
657 -- hlp
->childs
.n_active
;
658 srv
->flags
.shutdown
= true; /* request it to shut itself down */
660 if (srv
->flags
.busy
) {
661 debugs(84, 3, "helperStatefulShutdown: " << hlp
->id_name
<< " #" << srv
->index
<< " is BUSY.");
665 if (srv
->flags
.closing
) {
666 debugs(84, 3, "helperStatefulShutdown: " << hlp
->id_name
<< " #" << srv
->index
<< " is CLOSING.");
670 if (srv
->flags
.reserved
) {
672 debugs(84, 3, "helperStatefulShutdown: " << hlp
->id_name
<< " #" << srv
->index
<< " is RESERVED. Closing anyway.");
674 debugs(84, 3, "helperStatefulShutdown: " << hlp
->id_name
<< " #" << srv
->index
<< " is RESERVED. Not Shutting Down Yet.");
679 debugs(84, 3, "helperStatefulShutdown: " << hlp
->id_name
<< " #" << srv
->index
<< " shutting down.");
681 /* the rest of the details is dealt with in the helperStatefulServerFree
684 srv
->closePipesSafely();
690 /* note, don't free id_name, it probably points to static memory */
693 debugs(84, DBG_CRITICAL
, "WARNING: freeing " << id_name
<< " helper with " << stats
.queue_size
<< " requests queued");
696 /* ====================================================================== */
697 /* LOCAL FUNCTIONS */
698 /* ====================================================================== */
701 helperServerFree(helper_server
*srv
)
703 helper
*hlp
= srv
->parent
;
705 int i
, concurrency
= hlp
->childs
.concurrency
;
711 memFreeBuf(srv
->rbuf_sz
, srv
->rbuf
);
715 srv
->wqueue
->clean();
719 srv
->writebuf
->clean();
720 delete srv
->writebuf
;
721 srv
->writebuf
= NULL
;
724 if (Comm::IsConnOpen(srv
->writePipe
))
725 srv
->closeWritePipeSafely();
727 dlinkDelete(&srv
->link
, &hlp
->servers
);
729 assert(hlp
->childs
.n_running
> 0);
730 -- hlp
->childs
.n_running
;
732 if (!srv
->flags
.shutdown
) {
733 assert(hlp
->childs
.n_active
> 0);
734 -- hlp
->childs
.n_active
;
735 debugs(84, DBG_CRITICAL
, "WARNING: " << hlp
->id_name
<< " #" << srv
->index
<< " exited");
737 if (hlp
->childs
.needNew() > 0) {
738 debugs(80, DBG_IMPORTANT
, "Too few " << hlp
->id_name
<< " processes are running (need " << hlp
->childs
.needNew() << "/" << hlp
->childs
.n_max
<< ")");
740 if (hlp
->childs
.n_active
< hlp
->childs
.n_startup
&& hlp
->last_restart
> squid_curtime
- 30) {
741 if (srv
->stats
.replies
< 1)
742 fatalf("The %s helpers are crashing too rapidly, need help!\n", hlp
->id_name
);
744 debugs(80, DBG_CRITICAL
, "ERROR: The " << hlp
->id_name
<< " helpers are crashing too rapidly, need help!");
747 debugs(80, DBG_IMPORTANT
, "Starting new helpers");
748 helperOpenServers(hlp
);
752 for (i
= 0; i
< concurrency
; ++i
) {
753 // XXX: re-schedule these on another helper?
754 if ((r
= srv
->requests
[i
])) {
757 if (cbdataReferenceValidDone(r
->data
, &cbdata
)) {
758 HelperReply nilReply
;
759 r
->callback(cbdata
, nilReply
);
762 helperRequestFree(r
);
764 srv
->requests
[i
] = NULL
;
767 safe_free(srv
->requests
);
769 cbdataReferenceDone(srv
->parent
);
774 helperStatefulServerFree(helper_stateful_server
*srv
)
776 statefulhelper
*hlp
= srv
->parent
;
777 helper_stateful_request
*r
;
780 memFreeBuf(srv
->rbuf_sz
, srv
->rbuf
);
785 srv
->wqueue
->clean();
791 /* TODO: walk the local queue of requests and carry them all out */
792 if (Comm::IsConnOpen(srv
->writePipe
))
793 srv
->closeWritePipeSafely();
795 dlinkDelete(&srv
->link
, &hlp
->servers
);
797 assert(hlp
->childs
.n_running
> 0);
798 -- hlp
->childs
.n_running
;
800 if (!srv
->flags
.shutdown
) {
801 assert( hlp
->childs
.n_active
> 0);
802 -- hlp
->childs
.n_active
;
803 debugs(84, DBG_CRITICAL
, "WARNING: " << hlp
->id_name
<< " #" << srv
->index
<< " exited");
805 if (hlp
->childs
.needNew() > 0) {
806 debugs(80, DBG_IMPORTANT
, "Too few " << hlp
->id_name
<< " processes are running (need " << hlp
->childs
.needNew() << "/" << hlp
->childs
.n_max
<< ")");
808 if (hlp
->childs
.n_active
< hlp
->childs
.n_startup
&& hlp
->last_restart
> squid_curtime
- 30) {
809 if (srv
->stats
.replies
< 1)
810 fatalf("The %s helpers are crashing too rapidly, need help!\n", hlp
->id_name
);
812 debugs(80, DBG_CRITICAL
, "ERROR: The " << hlp
->id_name
<< " helpers are crashing too rapidly, need help!");
815 debugs(80, DBG_IMPORTANT
, "Starting new helpers");
816 helperStatefulOpenServers(hlp
);
820 if ((r
= srv
->request
)) {
823 if (cbdataReferenceValidDone(r
->data
, &cbdata
)) {
824 HelperReply nilReply
;
825 nilReply
.whichServer
= srv
;
826 r
->callback(cbdata
, nilReply
);
829 helperStatefulRequestFree(r
);
834 if (srv
->data
!= NULL
)
835 hlp
->datapool
->freeOne(srv
->data
);
837 cbdataReferenceDone(srv
->parent
);
842 /// Calls back with a pointer to the buffer with the helper output
844 helperReturnBuffer(int request_number
, helper_server
* srv
, helper
* hlp
, char * msg
, char * msg_end
)
846 helper_request
*r
= srv
->requests
[request_number
];
848 HLPCB
*callback
= r
->callback
;
850 srv
->requests
[request_number
] = NULL
;
855 if (cbdataReferenceValidDone(r
->data
, &cbdata
)) {
856 HelperReply
response(msg
, (msg_end
-msg
));
857 callback(cbdata
, response
);
860 -- srv
->stats
.pending
;
861 ++ srv
->stats
.replies
;
863 ++ hlp
->stats
.replies
;
865 srv
->answer_time
= current_time
;
867 srv
->dispatch_time
= r
->dispatch_time
;
869 hlp
->stats
.avg_svc_time
=
870 Math::intAverage(hlp
->stats
.avg_svc_time
,
871 tvSubMsec(r
->dispatch_time
, current_time
),
872 hlp
->stats
.replies
, REDIRECT_AV_FACTOR
);
874 helperRequestFree(r
);
876 debugs(84, DBG_IMPORTANT
, "helperHandleRead: unexpected reply on channel " <<
877 request_number
<< " from " << hlp
->id_name
<< " #" << srv
->index
<<
878 " '" << srv
->rbuf
<< "'");
881 if (!srv
->flags
.shutdown
) {
882 helperKickQueue(hlp
);
883 } else if (!srv
->flags
.closing
&& !srv
->stats
.pending
) {
884 srv
->flags
.closing
=true;
885 srv
->writePipe
->close();
890 helperHandleRead(const Comm::ConnectionPointer
&conn
, char *buf
, size_t len
, Comm::Flag flag
, int xerrno
, void *data
)
893 helper_server
*srv
= (helper_server
*)data
;
894 helper
*hlp
= srv
->parent
;
895 assert(cbdataReferenceValid(data
));
897 /* Bail out early on Comm::ERR_CLOSING - close handlers will tidy up for us */
899 if (flag
== Comm::ERR_CLOSING
) {
903 assert(conn
->fd
== srv
->readPipe
->fd
);
905 debugs(84, 5, "helperHandleRead: " << len
<< " bytes from " << hlp
->id_name
<< " #" << srv
->index
);
907 if (flag
!= Comm::OK
|| len
== 0) {
908 srv
->closePipesSafely();
913 srv
->rbuf
[srv
->roffset
] = '\0';
914 debugs(84, DBG_DATA
, Raw("accumulated", srv
->rbuf
, srv
->roffset
));
916 if (!srv
->stats
.pending
) {
917 /* someone spoke without being spoken to */
918 debugs(84, DBG_IMPORTANT
, "helperHandleRead: unexpected read from " <<
919 hlp
->id_name
<< " #" << srv
->index
<< ", " << (int)len
<<
920 " bytes '" << srv
->rbuf
<< "'");
926 while ((t
= strchr(srv
->rbuf
, hlp
->eom
))) {
927 /* end of reply found */
928 char *msg
= srv
->rbuf
;
931 debugs(84, 3, "helperHandleRead: end of reply found");
933 if (t
> srv
->rbuf
&& t
[-1] == '\r' && hlp
->eom
== '\n') {
935 // rewind to the \r octet which is the real terminal now
936 // and remember that we have to skip forward 2 places now.
943 if (hlp
->childs
.concurrency
) {
944 i
= strtol(msg
, &msg
, 10);
946 while (*msg
&& xisspace(*msg
))
950 helperReturnBuffer(i
, srv
, hlp
, msg
, t
);
951 srv
->roffset
-= (t
- srv
->rbuf
) + skip
;
952 memmove(srv
->rbuf
, t
+ skip
, srv
->roffset
);
953 srv
->rbuf
[srv
->roffset
] = '\0';
956 if (Comm::IsConnOpen(srv
->readPipe
) && !fd_table
[srv
->readPipe
->fd
].closing()) {
957 int spaceSize
= srv
->rbuf_sz
- srv
->roffset
- 1;
958 assert(spaceSize
>= 0);
960 // grow the input buffer if needed and possible
961 if (!spaceSize
&& srv
->rbuf_sz
+ 4096 <= ReadBufMaxSize
) {
962 srv
->rbuf
= (char *)memReallocBuf(srv
->rbuf
, srv
->rbuf_sz
+ 4096, &srv
->rbuf_sz
);
963 debugs(84, 3, HERE
<< "Grew read buffer to " << srv
->rbuf_sz
);
964 spaceSize
= srv
->rbuf_sz
- srv
->roffset
- 1;
965 assert(spaceSize
>= 0);
968 // quit reading if there is no space left
970 debugs(84, DBG_IMPORTANT
, "ERROR: Disconnecting from a " <<
971 "helper that overflowed " << srv
->rbuf_sz
<< "-byte " <<
972 "Squid input buffer: " << hlp
->id_name
<< " #" << srv
->index
);
973 srv
->closePipesSafely();
977 AsyncCall::Pointer call
= commCbCall(5,4, "helperHandleRead",
978 CommIoCbPtrFun(helperHandleRead
, srv
));
979 comm_read(srv
->readPipe
, srv
->rbuf
+ srv
->roffset
, spaceSize
, call
);
984 helperStatefulHandleRead(const Comm::ConnectionPointer
&conn
, char *buf
, size_t len
, Comm::Flag flag
, int xerrno
, void *data
)
987 helper_stateful_server
*srv
= (helper_stateful_server
*)data
;
988 helper_stateful_request
*r
;
989 statefulhelper
*hlp
= srv
->parent
;
990 assert(cbdataReferenceValid(data
));
992 /* Bail out early on Comm::ERR_CLOSING - close handlers will tidy up for us */
994 if (flag
== Comm::ERR_CLOSING
) {
998 assert(conn
->fd
== srv
->readPipe
->fd
);
1000 debugs(84, 5, "helperStatefulHandleRead: " << len
<< " bytes from " <<
1001 hlp
->id_name
<< " #" << srv
->index
);
1003 if (flag
!= Comm::OK
|| len
== 0) {
1004 srv
->closePipesSafely();
1008 srv
->roffset
+= len
;
1009 srv
->rbuf
[srv
->roffset
] = '\0';
1011 debugs(84, DBG_DATA
, Raw("accumulated", srv
->rbuf
, srv
->roffset
));
1014 /* someone spoke without being spoken to */
1015 debugs(84, DBG_IMPORTANT
, "helperStatefulHandleRead: unexpected read from " <<
1016 hlp
->id_name
<< " #" << srv
->index
<< ", " << (int)len
<<
1017 " bytes '" << srv
->rbuf
<< "'");
1022 if ((t
= strchr(srv
->rbuf
, hlp
->eom
))) {
1023 /* end of reply found */
1026 debugs(84, 3, "helperStatefulHandleRead: end of reply found");
1028 if (t
> srv
->rbuf
&& t
[-1] == '\r' && hlp
->eom
== '\n') {
1030 // rewind to the \r octet which is the real terminal now
1031 // and remember that we have to skip forward 2 places now.
1038 if (r
&& cbdataReferenceValid(r
->data
)) {
1039 HelperReply
res(srv
->rbuf
, (t
- srv
->rbuf
));
1040 res
.whichServer
= srv
;
1041 r
->callback(r
->data
, res
);
1043 debugs(84, DBG_IMPORTANT
, "StatefulHandleRead: no callback data registered");
1046 // only skip off the \0's _after_ passing its location in HelperReply above
1049 srv
->flags
.busy
= false;
1051 * BUG: the below assumes that only one response per read() was received and discards any octets remaining.
1052 * Doing this prohibits concurrency support with multiple replies per read().
1053 * TODO: check that read() setup on these buffers pays attention to roffest!=0
1054 * TODO: check that replies bigger than the buffer are discarded and do not to affect future replies
1057 helperStatefulRequestFree(r
);
1058 srv
->request
= NULL
;
1060 -- srv
->stats
.pending
;
1061 ++ srv
->stats
.replies
;
1063 ++ hlp
->stats
.replies
;
1064 srv
->answer_time
= current_time
;
1065 hlp
->stats
.avg_svc_time
=
1066 Math::intAverage(hlp
->stats
.avg_svc_time
,
1067 tvSubMsec(srv
->dispatch_time
, current_time
),
1068 hlp
->stats
.replies
, REDIRECT_AV_FACTOR
);
1071 helperStatefulServerDone(srv
);
1073 helperStatefulReleaseServer(srv
);
1076 if (Comm::IsConnOpen(srv
->readPipe
) && !fd_table
[srv
->readPipe
->fd
].closing()) {
1077 int spaceSize
= srv
->rbuf_sz
- srv
->roffset
- 1;
1078 assert(spaceSize
>= 0);
1080 // grow the input buffer if needed and possible
1081 if (!spaceSize
&& srv
->rbuf_sz
+ 4096 <= ReadBufMaxSize
) {
1082 srv
->rbuf
= (char *)memReallocBuf(srv
->rbuf
, srv
->rbuf_sz
+ 4096, &srv
->rbuf_sz
);
1083 debugs(84, 3, HERE
<< "Grew read buffer to " << srv
->rbuf_sz
);
1084 spaceSize
= srv
->rbuf_sz
- srv
->roffset
- 1;
1085 assert(spaceSize
>= 0);
1088 // quit reading if there is no space left
1090 debugs(84, DBG_IMPORTANT
, "ERROR: Disconnecting from a " <<
1091 "helper that overflowed " << srv
->rbuf_sz
<< "-byte " <<
1092 "Squid input buffer: " << hlp
->id_name
<< " #" << srv
->index
);
1093 srv
->closePipesSafely();
1097 AsyncCall::Pointer call
= commCbCall(5,4, "helperStatefulHandleRead",
1098 CommIoCbPtrFun(helperStatefulHandleRead
, srv
));
1099 comm_read(srv
->readPipe
, srv
->rbuf
+ srv
->roffset
, spaceSize
, call
);
1104 Enqueue(helper
* hlp
, helper_request
* r
)
1106 dlink_node
*link
= (dlink_node
*)memAllocate(MEM_DLINK_NODE
);
1107 dlinkAddTail(r
, link
, &hlp
->queue
);
1108 ++ hlp
->stats
.queue_size
;
1110 /* do this first so idle=N has a chance to grow the child pool before it hits critical. */
1111 if (hlp
->childs
.needNew() > 0) {
1112 debugs(84, DBG_CRITICAL
, "Starting new " << hlp
->id_name
<< " helpers...");
1113 helperOpenServers(hlp
);
1117 if (hlp
->stats
.queue_size
< (int)hlp
->childs
.n_running
)
1120 if (squid_curtime
- hlp
->last_queue_warn
< 600)
1123 if (shutting_down
|| reconfiguring
)
1126 hlp
->last_queue_warn
= squid_curtime
;
1128 debugs(84, DBG_CRITICAL
, "WARNING: All " << hlp
->childs
.n_active
<< "/" << hlp
->childs
.n_max
<< " " << hlp
->id_name
<< " processes are busy.");
1129 debugs(84, DBG_CRITICAL
, "WARNING: " << hlp
->stats
.queue_size
<< " pending requests queued");
1130 debugs(84, DBG_CRITICAL
, "WARNING: Consider increasing the number of " << hlp
->id_name
<< " processes in your config file.");
1132 if (hlp
->stats
.queue_size
> (int)hlp
->childs
.n_running
* 2)
1133 fatalf("Too many queued %s requests", hlp
->id_name
);
1137 StatefulEnqueue(statefulhelper
* hlp
, helper_stateful_request
* r
)
1139 dlink_node
*link
= (dlink_node
*)memAllocate(MEM_DLINK_NODE
);
1140 dlinkAddTail(r
, link
, &hlp
->queue
);
1141 ++ hlp
->stats
.queue_size
;
1143 /* do this first so idle=N has a chance to grow the child pool before it hits critical. */
1144 if (hlp
->childs
.needNew() > 0) {
1145 debugs(84, DBG_CRITICAL
, "Starting new " << hlp
->id_name
<< " helpers...");
1146 helperStatefulOpenServers(hlp
);
1150 if (hlp
->stats
.queue_size
< (int)hlp
->childs
.n_running
)
1153 if (hlp
->stats
.queue_size
> (int)hlp
->childs
.n_running
* 2)
1154 fatalf("Too many queued %s requests", hlp
->id_name
);
1156 if (squid_curtime
- hlp
->last_queue_warn
< 600)
1159 if (shutting_down
|| reconfiguring
)
1162 hlp
->last_queue_warn
= squid_curtime
;
1164 debugs(84, DBG_CRITICAL
, "WARNING: All " << hlp
->childs
.n_active
<< "/" << hlp
->childs
.n_max
<< " " << hlp
->id_name
<< " processes are busy.");
1165 debugs(84, DBG_CRITICAL
, "WARNING: " << hlp
->stats
.queue_size
<< " pending requests queued");
1166 debugs(84, DBG_CRITICAL
, "WARNING: Consider increasing the number of " << hlp
->id_name
<< " processes in your config file.");
1169 static helper_request
*
1170 Dequeue(helper
* hlp
)
1173 helper_request
*r
= NULL
;
1175 if ((link
= hlp
->queue
.head
)) {
1176 r
= (helper_request
*)link
->data
;
1177 dlinkDelete(link
, &hlp
->queue
);
1178 memFree(link
, MEM_DLINK_NODE
);
1179 -- hlp
->stats
.queue_size
;
1185 static helper_stateful_request
*
1186 StatefulDequeue(statefulhelper
* hlp
)
1189 helper_stateful_request
*r
= NULL
;
1191 if ((link
= hlp
->queue
.head
)) {
1192 r
= (helper_stateful_request
*)link
->data
;
1193 dlinkDelete(link
, &hlp
->queue
);
1194 memFree(link
, MEM_DLINK_NODE
);
1195 -- hlp
->stats
.queue_size
;
1201 static helper_server
*
1202 GetFirstAvailable(helper
* hlp
)
1206 helper_server
*selected
= NULL
;
1207 debugs(84, 5, "GetFirstAvailable: Running servers " << hlp
->childs
.n_running
);
1209 if (hlp
->childs
.n_running
== 0)
1212 /* Find "least" loaded helper (approx) */
1213 for (n
= hlp
->servers
.head
; n
!= NULL
; n
= n
->next
) {
1214 srv
= (helper_server
*)n
->data
;
1216 if (selected
&& selected
->stats
.pending
<= srv
->stats
.pending
)
1219 if (srv
->flags
.shutdown
)
1222 if (!srv
->stats
.pending
)
1233 /* Check for overload */
1235 debugs(84, 5, "GetFirstAvailable: None available.");
1239 if (selected
->stats
.pending
>= (hlp
->childs
.concurrency
? hlp
->childs
.concurrency
: 1)) {
1240 debugs(84, 3, "GetFirstAvailable: Least-loaded helper is overloaded!");
1244 debugs(84, 5, "GetFirstAvailable: returning srv-" << selected
->index
);
1248 static helper_stateful_server
*
1249 StatefulGetFirstAvailable(statefulhelper
* hlp
)
1252 helper_stateful_server
*srv
= NULL
;
1253 debugs(84, 5, "StatefulGetFirstAvailable: Running servers " << hlp
->childs
.n_running
);
1255 if (hlp
->childs
.n_running
== 0)
1258 for (n
= hlp
->servers
.head
; n
!= NULL
; n
= n
->next
) {
1259 srv
= (helper_stateful_server
*)n
->data
;
1261 if (srv
->flags
.busy
)
1264 if (srv
->flags
.reserved
)
1267 if (srv
->flags
.shutdown
)
1270 if ((hlp
->IsAvailable
!= NULL
) && (srv
->data
!= NULL
) && !(hlp
->IsAvailable(srv
->data
)))
1273 debugs(84, 5, "StatefulGetFirstAvailable: returning srv-" << srv
->index
);
1277 debugs(84, 5, "StatefulGetFirstAvailable: None available.");
1282 helperDispatchWriteDone(const Comm::ConnectionPointer
&conn
, char *buf
, size_t len
, Comm::Flag flag
, int xerrno
, void *data
)
1284 helper_server
*srv
= (helper_server
*)data
;
1286 srv
->writebuf
->clean();
1287 delete srv
->writebuf
;
1288 srv
->writebuf
= NULL
;
1289 srv
->flags
.writing
= false;
1291 if (flag
!= Comm::OK
) {
1292 /* Helper server has crashed */
1293 debugs(84, DBG_CRITICAL
, "helperDispatch: Helper " << srv
->parent
->id_name
<< " #" << srv
->index
<< " has crashed");
1297 if (!srv
->wqueue
->isNull()) {
1298 srv
->writebuf
= srv
->wqueue
;
1299 srv
->wqueue
= new MemBuf
;
1300 srv
->flags
.writing
= true;
1301 AsyncCall::Pointer call
= commCbCall(5,5, "helperDispatchWriteDone",
1302 CommIoCbPtrFun(helperDispatchWriteDone
, srv
));
1303 Comm::Write(srv
->writePipe
, srv
->writebuf
->content(), srv
->writebuf
->contentSize(), call
, NULL
);
1308 helperDispatch(helper_server
* srv
, helper_request
* r
)
1310 helper
*hlp
= srv
->parent
;
1311 helper_request
**ptr
= NULL
;
1314 if (!cbdataReferenceValid(r
->data
)) {
1315 debugs(84, DBG_IMPORTANT
, "helperDispatch: invalid callback data");
1316 helperRequestFree(r
);
1320 for (slot
= 0; slot
< (hlp
->childs
.concurrency
? hlp
->childs
.concurrency
: 1); ++slot
) {
1321 if (!srv
->requests
[slot
]) {
1322 ptr
= &srv
->requests
[slot
];
1329 r
->dispatch_time
= current_time
;
1331 if (srv
->wqueue
->isNull())
1332 srv
->wqueue
->init();
1334 if (hlp
->childs
.concurrency
)
1335 srv
->wqueue
->Printf("%d %s", slot
, r
->buf
);
1337 srv
->wqueue
->append(r
->buf
, strlen(r
->buf
));
1339 if (!srv
->flags
.writing
) {
1340 assert(NULL
== srv
->writebuf
);
1341 srv
->writebuf
= srv
->wqueue
;
1342 srv
->wqueue
= new MemBuf
;
1343 srv
->flags
.writing
= true;
1344 AsyncCall::Pointer call
= commCbCall(5,5, "helperDispatchWriteDone",
1345 CommIoCbPtrFun(helperDispatchWriteDone
, srv
));
1346 Comm::Write(srv
->writePipe
, srv
->writebuf
->content(), srv
->writebuf
->contentSize(), call
, NULL
);
1349 debugs(84, 5, "helperDispatch: Request sent to " << hlp
->id_name
<< " #" << srv
->index
<< ", " << strlen(r
->buf
) << " bytes");
1352 ++ srv
->stats
.pending
;
1353 ++ hlp
->stats
.requests
;
1357 helperStatefulDispatchWriteDone(const Comm::ConnectionPointer
&conn
, char *buf
, size_t len
, Comm::Flag flag
,
1358 int xerrno
, void *data
)
1364 helperStatefulDispatch(helper_stateful_server
* srv
, helper_stateful_request
* r
)
1366 statefulhelper
*hlp
= srv
->parent
;
1368 if (!cbdataReferenceValid(r
->data
)) {
1369 debugs(84, DBG_IMPORTANT
, "helperStatefulDispatch: invalid callback data");
1370 helperStatefulRequestFree(r
);
1371 helperStatefulReleaseServer(srv
);
1375 debugs(84, 9, "helperStatefulDispatch busying helper " << hlp
->id_name
<< " #" << srv
->index
);
1377 if (r
->placeholder
== 1) {
1378 /* a callback is needed before this request can _use_ a helper. */
1379 /* we don't care about releasing this helper. The request NEVER
1380 * gets to the helper. So we throw away the return code */
1381 HelperReply nilReply
;
1382 nilReply
.whichServer
= srv
;
1383 r
->callback(r
->data
, nilReply
);
1384 /* throw away the placeholder */
1385 helperStatefulRequestFree(r
);
1386 /* and push the queue. Note that the callback may have submitted a new
1387 * request to the helper which is why we test for the request */
1389 if (srv
->request
== NULL
)
1390 helperStatefulServerDone(srv
);
1395 srv
->flags
.busy
= true;
1396 srv
->flags
.reserved
= true;
1398 srv
->dispatch_time
= current_time
;
1399 AsyncCall::Pointer call
= commCbCall(5,5, "helperStatefulDispatchWriteDone",
1400 CommIoCbPtrFun(helperStatefulDispatchWriteDone
, hlp
));
1401 Comm::Write(srv
->writePipe
, r
->buf
, strlen(r
->buf
), call
, NULL
);
1402 debugs(84, 5, "helperStatefulDispatch: Request sent to " <<
1403 hlp
->id_name
<< " #" << srv
->index
<< ", " <<
1404 (int) strlen(r
->buf
) << " bytes");
1407 ++ srv
->stats
.pending
;
1408 ++ hlp
->stats
.requests
;
1412 helperKickQueue(helper
* hlp
)
1417 while ((srv
= GetFirstAvailable(hlp
)) && (r
= Dequeue(hlp
)))
1418 helperDispatch(srv
, r
);
1422 helperStatefulKickQueue(statefulhelper
* hlp
)
1424 helper_stateful_request
*r
;
1425 helper_stateful_server
*srv
;
1427 while ((srv
= StatefulGetFirstAvailable(hlp
)) && (r
= StatefulDequeue(hlp
)))
1428 helperStatefulDispatch(srv
, r
);
1432 helperStatefulServerDone(helper_stateful_server
* srv
)
1434 if (!srv
->flags
.shutdown
) {
1435 helperStatefulKickQueue(srv
->parent
);
1436 } else if (!srv
->flags
.closing
&& !srv
->flags
.reserved
&& !srv
->flags
.busy
) {
1437 srv
->closeWritePipeSafely();
1443 helperRequestFree(helper_request
* r
)
1445 cbdataReferenceDone(r
->data
);
1451 helperStatefulRequestFree(helper_stateful_request
* r
)
1454 cbdataReferenceDone(r
->data
);
1460 // TODO: should helper_ and helper_stateful_ have a common parent?
1462 helperStartStats(StoreEntry
*sentry
, void *hlp
, const char *label
)
1466 storeAppendPrintf(sentry
, "%s: unavailable\n", label
);
1471 storeAppendPrintf(sentry
, "%s:\n", label
);