]> git.ipfire.org Git - thirdparty/squid.git/blame - src/peer_digest.cc
Renamed struct peer to class CachePeer.
[thirdparty/squid.git] / src / peer_digest.cc
CommitLineData
9b7de833 1
2/*
9b7de833 3 * DEBUG: section 72 Peer Digest Routines
4 * AUTHOR: Alex Rousskov
5 *
2b6662ba 6 * SQUID Web Proxy Cache http://www.squid-cache.org/
e25c139f 7 * ----------------------------------------------------------
9b7de833 8 *
2b6662ba 9 * Squid is the result of efforts by numerous individuals from
10 * the Internet community; see the CONTRIBUTORS file for full
11 * details. Many organizations have provided support for Squid's
12 * development; see the SPONSORS file for full details. Squid is
13 * Copyrighted (C) 2001 by the Regents of the University of
14 * California; see the COPYRIGHT file for full details. Squid
15 * incorporates software developed and/or copyrighted by other
16 * sources; see the CREDITS file for full details.
9b7de833 17 *
18 * This program is free software; you can redistribute it and/or modify
19 * it under the terms of the GNU General Public License as published by
20 * the Free Software Foundation; either version 2 of the License, or
21 * (at your option) any later version.
26ac0430 22 *
9b7de833 23 * This program is distributed in the hope that it will be useful,
24 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 * GNU General Public License for more details.
26ac0430 27 *
9b7de833 28 * You should have received a copy of the GNU General Public License
29 * along with this program; if not, write to the Free Software
cbdec147 30 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
e25c139f 31 *
9b7de833 32 */
33
582c2af2 34#include "squid.h"
6cfa8966 35#if USE_CACHE_DIGESTS
b814e8d4 36#include "CacheDigest.h"
a553a5a3 37#include "event.h"
aa839030 38#include "forward.h"
af69c635 39#include "globals.h"
528b2c61 40#include "HttpReply.h"
582c2af2 41#include "HttpRequest.h"
308e60be 42#include "internal.h"
528b2c61 43#include "MemObject.h"
f0ba2534 44#include "neighbors.h"
b6149797 45#include "mime_header.h"
aa839030 46#include "PeerDigest.h"
985c86bc 47#include "SquidTime.h"
582c2af2 48#include "Store.h"
fb548aaf 49#include "store_key_md5.h"
598465f1 50#include "StoreClient.h"
4e540555 51#include "tools.h"
598465f1 52
9b7de833 53/* local types */
54
55/* local prototypes */
e13ee7ad 56static time_t peerDigestIncDelay(const PeerDigest * pd);
57static time_t peerDigestNewDelay(const StoreEntry * e);
58static void peerDigestSetCheck(PeerDigest * pd, time_t delay);
eb16313f 59static void peerDigestClean(PeerDigest *);
e13ee7ad 60static EVH peerDigestCheck;
61static void peerDigestRequest(PeerDigest * pd);
add2192d 62static STCB peerDigestHandleReply;
3dfaa3d2 63static int peerDigestFetchReply(void *, char *, ssize_t);
1f140227 64int peerDigestSwapInHeaders(void *, char *, ssize_t);
65int peerDigestSwapInCBlock(void *, char *, ssize_t);
66int peerDigestSwapInMask(void *, char *, ssize_t);
4b4cd312 67static int peerDigestFetchedEnough(DigestFetchState * fetch, char *buf, ssize_t size, const char *step_name);
551e60a9 68static void peerDigestFetchStop(DigestFetchState * fetch, char *buf, const char *reason);
e13ee7ad 69static void peerDigestFetchAbort(DigestFetchState * fetch, char *buf, const char *reason);
70static void peerDigestReqFinish(DigestFetchState * fetch, char *buf, int, int, int, const char *reason, int err);
71static void peerDigestPDFinish(DigestFetchState * fetch, int pcb_valid, int err);
72static void peerDigestFetchFinish(DigestFetchState * fetch, int err);
73static void peerDigestFetchSetStats(DigestFetchState * fetch);
74static int peerDigestSetCBlock(PeerDigest * pd, const char *buf);
75static int peerDigestUseful(const PeerDigest * pd);
395b813e 76
9b7de833 77/* local constants */
aa839030 78Version const CacheDigestVer = { 5, 3 };
9d486b43 79
9b7de833 80#define StoreDigestCBlockSize sizeof(StoreDigestCBlock)
81
e13ee7ad 82/* min interval for requesting digests from a given peer */
83static const time_t PeerDigestReqMinGap = 5 * 60; /* seconds */
84/* min interval for requesting digests (cumulative request stream) */
85static const time_t GlobDigestReqMinGap = 1 * 60; /* seconds */
bd890734 86
87/* local vars */
9d486b43 88
8a6218c6 89static time_t pd_last_req_time = 0; /* last call to Check */
9b7de833 90
e13ee7ad 91/* initialize peer digest */
92static void
a3c6762c 93peerDigestInit(PeerDigest * pd, CachePeer * p)
9b7de833 94{
e13ee7ad 95 assert(pd && p);
96
97 memset(pd, 0, sizeof(*pd));
4e454ade 98 /*
99 * DPW 2007-04-12
100 * Lock on to the peer here. The corresponding cbdataReferenceDone()
101 * is in peerDigestDestroy().
102 */
103 pd->peer = cbdataReference(p);
e13ee7ad 104 /* if peer disappears, we will know it's name */
528b2c61 105 pd->host = p->host;
e13ee7ad 106
107 pd->times.initialized = squid_curtime;
9b7de833 108}
109
4b4cd312 110static void
8a6218c6 111peerDigestClean(PeerDigest * pd)
9b7de833 112{
e13ee7ad 113 assert(pd);
62e76326 114
e13ee7ad 115 if (pd->cd)
62e76326 116 cacheDigestDestroy(pd->cd);
117
30abd221 118 pd->host.clean();
9b7de833 119}
120
0353e724 121CBDATA_CLASS_INIT(PeerDigest);
122
123void *
124PeerDigest::operator new (size_t)
125{
126 CBDATA_INIT_TYPE(PeerDigest);
127 PeerDigest *result = cbdataAlloc(PeerDigest);
128 return result;
129}
130
131void
132PeerDigest::operator delete (void *address)
133{
134 PeerDigest *t = static_cast<PeerDigest *>(address);
135 cbdataFree(t);
136}
137
e13ee7ad 138/* allocate new peer digest, call Init, and lock everything */
139PeerDigest *
a3c6762c 140peerDigestCreate(CachePeer * p)
e13ee7ad 141{
142 PeerDigest *pd;
8a6218c6 143 assert(p);
e13ee7ad 144
0353e724 145 pd = new PeerDigest;
e13ee7ad 146 peerDigestInit(pd, p);
e13ee7ad 147
fa80a8ef 148 /* XXX This does not look right, and the same thing again in the caller */
149 return cbdataReference(pd);
e13ee7ad 150}
151
152/* call Clean and free/unlock everything */
2d72d4fd 153static void
8a6218c6 154peerDigestDestroy(PeerDigest * pd)
e13ee7ad 155{
8abf232c 156 void *p;
e13ee7ad 157 assert(pd);
8abf232c 158 void * peerTmp = pd->peer;
e13ee7ad 159
4e454ade 160 /*
161 * DPW 2007-04-12
162 * We locked the peer in peerDigestInit(), this is
163 * where we unlock it. If the peer is still valid,
164 * tell it that the digest is gone.
165 */
8abf232c 166 if (cbdataReferenceValidDone(peerTmp, &p))
a3c6762c 167 peerNoteDigestGone((CachePeer *)p);
e13ee7ad 168
169 peerDigestClean(pd);
62e76326 170
00d77d6b 171 delete pd;
e13ee7ad 172}
173
174/* called by peer to indicate that somebody actually needs this digest */
175void
8a6218c6 176peerDigestNeeded(PeerDigest * pd)
e13ee7ad 177{
178 assert(pd);
179 assert(!pd->flags.needed);
180 assert(!pd->cd);
181
182 pd->flags.needed = 1;
183 pd->times.needed = squid_curtime;
8a6218c6 184 peerDigestSetCheck(pd, 0); /* check asap */
e13ee7ad 185}
186
187/* currently we do not have a reason to disable without destroying */
8a6218c6 188#if FUTURE_CODE
395b813e 189/* disables peer for good */
190static void
8a6218c6 191peerDigestDisable(PeerDigest * pd)
395b813e 192{
bf8fe701 193 debugs(72, 2, "peerDigestDisable: peer " << pd->host.buf() << " disabled for good");
e13ee7ad 194 pd->times.disabled = squid_curtime;
8a6218c6 195 pd->times.next_check = -1; /* never */
e13ee7ad 196 pd->flags.usable = 0;
197
198 if (pd->cd) {
62e76326 199 cacheDigestDestroy(pd->cd);
200 pd->cd = NULL;
e13ee7ad 201 }
62e76326 202
e13ee7ad 203 /* we do not destroy the pd itself to preserve its "history" and stats */
395b813e 204}
62e76326 205
e13ee7ad 206#endif
395b813e 207
e13ee7ad 208/* increment retry delay [after an unsuccessful attempt] */
395b813e 209static time_t
8a6218c6 210peerDigestIncDelay(const PeerDigest * pd)
395b813e 211{
e13ee7ad 212 assert(pd);
213 return pd->times.retry_delay > 0 ?
62e76326 214 2 * pd->times.retry_delay : /* exponential backoff */
215 PeerDigestReqMinGap; /* minimal delay */
395b813e 216}
217
62e76326 218/* artificially increases Expires: setting to avoid race conditions
e13ee7ad 219 * returns the delay till that [increased] expiration time */
00485c29 220static time_t
e13ee7ad 221peerDigestNewDelay(const StoreEntry * e)
00485c29 222{
e13ee7ad 223 assert(e);
62e76326 224
00485c29 225 if (e->expires > 0)
62e76326 226 return e->expires + PeerDigestReqMinGap - squid_curtime;
227
e13ee7ad 228 return PeerDigestReqMinGap;
00485c29 229}
230
e13ee7ad 231/* registers next digest verification */
395b813e 232static void
e13ee7ad 233peerDigestSetCheck(PeerDigest * pd, time_t delay)
395b813e 234{
e13ee7ad 235 eventAdd("peerDigestCheck", peerDigestCheck, pd, (double) delay, 1);
236 pd->times.next_check = squid_curtime + delay;
bb790702 237 debugs(72, 3, "peerDigestSetCheck: will check peer " << pd->host << " in " << delay << " secs");
e13ee7ad 238}
239
5385c86a 240/*
241 * called when peer is about to disappear or have already disappeared
242 */
e13ee7ad 243void
8a6218c6 244peerDigestNotePeerGone(PeerDigest * pd)
245{
e13ee7ad 246 if (pd->flags.requested) {
bb790702 247 debugs(72, 2, "peerDigest: peer " << pd->host << " gone, will destroy after fetch.");
62e76326 248 /* do nothing now, the fetching chain will notice and take action */
395b813e 249 } else {
bb790702 250 debugs(72, 2, "peerDigest: peer " << pd->host << " is gone, destroying now.");
62e76326 251 peerDigestDestroy(pd);
395b813e 252 }
253}
254
e13ee7ad 255/* callback for eventAdd() (with peer digest locked)
26ac0430 256 * request new digest if our copy is too old or if we lack one;
e13ee7ad 257 * schedule next check otherwise */
9b7de833 258static void
e13ee7ad 259peerDigestCheck(void *data)
9b7de833 260{
e6ccf245 261 PeerDigest *pd = (PeerDigest *)data;
e13ee7ad 262 time_t req_time;
263
e13ee7ad 264 assert(!pd->flags.requested);
5d9bb360 265
8a6218c6 266 pd->times.next_check = 0; /* unknown */
e13ee7ad 267
fa80a8ef 268 if (!cbdataReferenceValid(pd->peer)) {
62e76326 269 peerDigestNotePeerGone(pd);
270 return;
9b7de833 271 }
62e76326 272
cc192b50 273 debugs(72, 3, "peerDigestCheck: peer " << pd->peer->host << ":" << pd->peer->http_port);
26ac0430
AJ
274 debugs(72, 3, "peerDigestCheck: time: " << squid_curtime <<
275 ", last received: " << (long int) pd->times.received << " (" <<
bf8fe701 276 std::showpos << (int) (squid_curtime - pd->times.received) << ")");
e13ee7ad 277
278 /* decide when we should send the request:
279 * request now unless too close to other requests */
280 req_time = squid_curtime;
281
282 /* per-peer limit */
62e76326 283
e13ee7ad 284 if (req_time - pd->times.received < PeerDigestReqMinGap) {
b4197865 285 debugs(72, 2, "peerDigestCheck: " << pd->host <<
bf8fe701 286 ", avoiding close peer requests (" <<
287 (int) (req_time - pd->times.received) << " < " <<
288 (int) PeerDigestReqMinGap << " secs).");
289
62e76326 290 req_time = pd->times.received + PeerDigestReqMinGap;
bd890734 291 }
62e76326 292
e13ee7ad 293 /* global limit */
294 if (req_time - pd_last_req_time < GlobDigestReqMinGap) {
b4197865 295 debugs(72, 2, "peerDigestCheck: " << pd->host <<
bf8fe701 296 ", avoiding close requests (" <<
297 (int) (req_time - pd_last_req_time) << " < " <<
298 (int) GlobDigestReqMinGap << " secs).");
299
62e76326 300 req_time = pd_last_req_time + GlobDigestReqMinGap;
9b7de833 301 }
62e76326 302
e13ee7ad 303 if (req_time <= squid_curtime)
62e76326 304 peerDigestRequest(pd); /* will set pd->flags.requested */
e13ee7ad 305 else
62e76326 306 peerDigestSetCheck(pd, req_time - squid_curtime);
9b7de833 307}
308
2812146b 309CBDATA_TYPE(DigestFetchState);
310
e13ee7ad 311/* ask store for a digest */
9b7de833 312static void
e13ee7ad 313peerDigestRequest(PeerDigest * pd)
9b7de833 314{
a3c6762c 315 CachePeer *p = pd->peer;
9b7de833 316 StoreEntry *e, *old_e;
317 char *url;
9d486b43 318 const cache_key *key;
190154cf 319 HttpRequest *req;
9b7de833 320 DigestFetchState *fetch = NULL;
528b2c61 321 StoreIOBuffer tempBuffer;
e13ee7ad 322
323 pd->req_result = NULL;
324 pd->flags.requested = 1;
325
9b7de833 326 /* compute future request components */
62e76326 327
7e3ce7b9 328 if (p->digest_url)
62e76326 329 url = xstrdup(p->digest_url);
7e3ce7b9 330 else
62e76326 331 url = internalRemoteUri(p->host, p->http_port,
332 "/squid-internal-periodic/", StoreDigestFileName);
7e3ce7b9 333
c21ad0f5 334 req = HttpRequest::CreateFromUrl(url);
62e76326 335
e13ee7ad 336 assert(req);
62e76326 337
f66a9ef4 338 key = storeKeyPublicByRequest(req);
62e76326 339
bf8fe701 340 debugs(72, 2, "peerDigestRequest: " << url << " key: " << storeKeyText(key));
e13ee7ad 341
9b7de833 342 /* add custom headers */
2246b732 343 assert(!req->header.len);
62e76326 344
a9925b40 345 req->header.putStr(HDR_ACCEPT, StoreDigestMimeStr);
62e76326 346
a9925b40 347 req->header.putStr(HDR_ACCEPT, "text/html");
62e76326 348
9bc73deb 349 if (p->login)
62e76326 350 xstrncpy(req->login, p->login, MAX_LOGIN_SZ);
351
9b7de833 352 /* create fetch state structure */
2812146b 353 CBDATA_INIT_TYPE(DigestFetchState);
62e76326 354
72711e31 355 fetch = cbdataAlloc(DigestFetchState);
62e76326 356
6dd9f4bd 357 fetch->request = HTTPMSGLOCK(req);
62e76326 358
fa80a8ef 359 fetch->pd = cbdataReference(pd);
62e76326 360
e13ee7ad 361 fetch->offset = 0;
62e76326 362
add2192d 363 fetch->state = DIGEST_READ_REPLY;
e13ee7ad 364
365 /* update timestamps */
9b7de833 366 fetch->start_time = squid_curtime;
62e76326 367
e13ee7ad 368 pd->times.requested = squid_curtime;
62e76326 369
e13ee7ad 370 pd_last_req_time = squid_curtime;
371
92695e5e 372 req->flags.cachable = 1;
62e76326 373
9b7de833 374 /* the rest is based on clientProcessExpired() */
92695e5e 375 req->flags.refresh = 1;
62e76326 376
c8f4eac4 377 old_e = fetch->old_entry = Store::Root().get(key);
62e76326 378
9b7de833 379 if (old_e) {
bf8fe701 380 debugs(72, 5, "peerDigestRequest: found old entry");
34266cde 381
3d0ac046 382 old_e->lock();
3900307b 383 old_e->createMemObject(url, url);
34266cde 384
62e76326 385 fetch->old_sc = storeClientListAdd(old_e, fetch);
9b7de833 386 }
62e76326 387
9b7de833 388 e = fetch->entry = storeCreateEntry(url, url, req->flags, req->method);
e13ee7ad 389 assert(EBIT_TEST(e->flags, KEY_PRIVATE));
06d2839d 390 fetch->sc = storeClientListAdd(e, fetch);
9b7de833 391 /* set lastmod to trigger IMS request if possible */
62e76326 392
9b7de833 393 if (old_e)
62e76326 394 e->lastmod = old_e->lastmod;
e13ee7ad 395
9b7de833 396 /* push towards peer cache */
bf8fe701 397 debugs(72, 3, "peerDigestRequest: forwarding to fwdStart...");
62e76326 398
e83cc785 399 FwdState::fwdStart(Comm::ConnectionPointer(), e, req);
62e76326 400
598465f1 401 tempBuffer.offset = 0;
62e76326 402
f95db407 403 tempBuffer.length = SM_PAGE_SIZE;
62e76326 404
598465f1 405 tempBuffer.data = fetch->buf;
62e76326 406
598465f1 407 storeClientCopy(fetch->sc, e, tempBuffer,
62e76326 408 peerDigestHandleReply, fetch);
9b7de833 409}
410
add2192d 411/* Handle the data copying .. */
412
413/*
414 * This routine handles the copy data and then redirects the
415 * copy to a bunch of subfunctions depending upon the copy state.
416 * It also tracks the buffer offset and "seen", since I'm actually
417 * not interested in rewriting everything to suit my little idea.
418 */
9b7de833 419static void
2324cda2 420peerDigestHandleReply(void *data, StoreIOBuffer receivedData)
add2192d 421{
e6ccf245 422 DigestFetchState *fetch = (DigestFetchState *)data;
add2192d 423 int retsize = -1;
424 digest_read_state_t prevstate;
425 int newsize;
426
2324cda2 427 assert(fetch->pd && receivedData.data);
428 /* The existing code assumes that the received pointer is
598465f1 429 * where we asked the data to be put
430 */
2324cda2 431 assert(fetch->buf + fetch->bufofs == receivedData.data);
add2192d 432
433 /* Update the buffer size */
2324cda2 434 fetch->bufofs += receivedData.length;
add2192d 435
436 assert(fetch->bufofs <= SM_PAGE_SIZE);
437
438 /* If we've fetched enough, return */
62e76326 439
add2192d 440 if (peerDigestFetchedEnough(fetch, fetch->buf, fetch->bufofs, "peerDigestHandleReply"))
62e76326 441 return;
add2192d 442
443 /* Call the right function based on the state */
444 /* (Those functions will update the state if needed) */
fa80a8ef 445
e2848e94 446 /* Give us a temporary reference. Some of the calls we make may
447 * try to destroy the fetch structure, and we like to know if they
448 * do
bac6d4bd 449 */
e2848e94 450 fetch = cbdataReference(fetch);
add2192d 451
452 /* Repeat this loop until we're out of data OR the state changes */
453 /* (So keep going if the state has changed and we still have data */
454 do {
62e76326 455 prevstate = fetch->state;
456
457 switch (fetch->state) {
458
459 case DIGEST_READ_REPLY:
460 retsize = peerDigestFetchReply(fetch, fetch->buf, fetch->bufofs);
461 break;
462
463 case DIGEST_READ_HEADERS:
464 retsize = peerDigestSwapInHeaders(fetch, fetch->buf, fetch->bufofs);
465 break;
466
467 case DIGEST_READ_CBLOCK:
468 retsize = peerDigestSwapInCBlock(fetch, fetch->buf, fetch->bufofs);
469 break;
470
471 case DIGEST_READ_MASK:
472 retsize = peerDigestSwapInMask(fetch, fetch->buf, fetch->bufofs);
473 break;
474
475 case DIGEST_READ_NONE:
476 break;
477
478 case DIGEST_READ_DONE:
479 goto finish;
480 break;
481
482 default:
483 fatal("Bad digest transfer mode!\n");
484 }
485
486 if (retsize < 0)
487 goto finish;
488
489 /*
490 * The returned size indicates how much of the buffer was read -
491 * so move the remainder of the buffer to the beginning
492 * and update the bufofs / bufsize
493 */
494 newsize = fetch->bufofs - retsize;
495
41d00cd3 496 memmove(fetch->buf, fetch->buf + retsize, fetch->bufofs - newsize);
62e76326 497
498 fetch->bufofs = newsize;
add2192d 499
e2848e94 500 } while (cbdataReferenceValid(fetch) && prevstate != fetch->state && fetch->bufofs > 0);
add2192d 501
502 /* Update the copy offset */
2324cda2 503 fetch->offset += receivedData.length;
add2192d 504
505 /* Schedule another copy */
fa80a8ef 506 if (cbdataReferenceValid(fetch)) {
62e76326 507 StoreIOBuffer tempBuffer;
508 tempBuffer.offset = fetch->offset;
509 tempBuffer.length = SM_PAGE_SIZE - fetch->bufofs;
510 tempBuffer.data = fetch->buf + fetch->bufofs;
511 storeClientCopy(fetch->sc, fetch->entry, tempBuffer,
512 peerDigestHandleReply, fetch);
add2192d 513 }
62e76326 514
515finish:
e2848e94 516 /* Get rid of our reference, we've finished with it for now */
517 cbdataReferenceDone(fetch);
add2192d 518}
519
add2192d 520/* wait for full http headers to be received then parse them */
521/*
522 * This routine handles parsing the reply line.
523 * If the reply line indicates an OK, the same data is thrown
524 * to SwapInHeaders(). If the reply line is a NOT_MODIFIED,
525 * we simply stop parsing.
526 */
527static int
9b7de833 528peerDigestFetchReply(void *data, char *buf, ssize_t size)
529{
e6ccf245 530 DigestFetchState *fetch = (DigestFetchState *)data;
e13ee7ad 531 PeerDigest *pd = fetch->pd;
9bc73deb 532 size_t hdr_size;
e13ee7ad 533 assert(pd && buf);
9b7de833 534 assert(!fetch->offset);
e13ee7ad 535
add2192d 536 assert(fetch->state == DIGEST_READ_REPLY);
62e76326 537
9b7de833 538 if (peerDigestFetchedEnough(fetch, buf, size, "peerDigestFetchReply"))
62e76326 539 return -1;
e13ee7ad 540
9bc73deb 541 if ((hdr_size = headersEnd(buf, size))) {
62e76326 542 http_status status;
543 HttpReply const *reply = fetch->entry->getReply();
544 assert(reply);
545 assert (reply->sline.status != 0);
546 status = reply->sline.status;
b4197865 547 debugs(72, 3, "peerDigestFetchReply: " << pd->host << " status: " << status <<
26ac0430 548 ", expires: " << (long int) reply->expires << " (" << std::showpos <<
bf8fe701 549 (int) (reply->expires - squid_curtime) << ")");
62e76326 550
551 /* this "if" is based on clientHandleIMSReply() */
552
553 if (status == HTTP_NOT_MODIFIED) {
190154cf 554 HttpRequest *r = NULL;
62e76326 555 /* our old entry is fine */
556 assert(fetch->old_entry);
557
558 if (!fetch->old_entry->mem_obj->request)
559 fetch->old_entry->mem_obj->request = r =
6dd9f4bd 560 HTTPMSGLOCK(fetch->entry->mem_obj->request);
62e76326 561
562 assert(fetch->old_entry->mem_obj->request);
563
07947ad8 564 HttpReply *old_rep = (HttpReply *) fetch->old_entry->getReply();
565
06a5ae20 566 old_rep->updateOnNotModified(reply);
62e76326 567
3900307b 568 fetch->old_entry->timestampsSet();
62e76326 569
570 /* get rid of 304 reply */
571 storeUnregister(fetch->sc, fetch->entry, fetch);
572
97b5e68f 573 fetch->entry->unlock();
62e76326 574
575 fetch->entry = fetch->old_entry;
576
577 fetch->old_entry = NULL;
578
579 /* preserve request -- we need its size to update counters */
580 /* requestUnlink(r); */
581 /* fetch->entry->mem_obj->request = NULL; */
582 } else if (status == HTTP_OK) {
583 /* get rid of old entry if any */
584
585 if (fetch->old_entry) {
bf8fe701 586 debugs(72, 3, "peerDigestFetchReply: got new digest, releasing old one");
62e76326 587 storeUnregister(fetch->old_sc, fetch->old_entry, fetch);
d88e3c49 588 fetch->old_entry->releaseRequest();
97b5e68f 589 fetch->old_entry->unlock();
62e76326 590 fetch->old_entry = NULL;
591 }
592 } else {
593 /* some kind of a bug */
594 peerDigestFetchAbort(fetch, buf, httpStatusLineReason(&reply->sline));
595 return -1; /* XXX -1 will abort stuff in ReadReply! */
596 }
597
598 /* must have a ready-to-use store entry if we got here */
599 /* can we stay with the old in-memory digest? */
600 if (status == HTTP_NOT_MODIFIED && fetch->pd->cd) {
601 peerDigestFetchStop(fetch, buf, "Not modified");
602 fetch->state = DIGEST_READ_DONE;
603 } else {
604 fetch->state = DIGEST_READ_HEADERS;
605 }
9b7de833 606 } else {
62e76326 607 /* need more data, do we have space? */
608
609 if (size >= SM_PAGE_SIZE)
610 peerDigestFetchAbort(fetch, buf, "reply header too big");
9b7de833 611 }
add2192d 612
613 /* We don't want to actually ack that we've handled anything,
614 * otherwise SwapInHeaders() won't get the reply line .. */
615 return 0;
9b7de833 616}
617
618/* fetch headers from disk, pass on to SwapInCBlock */
1f140227 619int
9b7de833 620peerDigestSwapInHeaders(void *data, char *buf, ssize_t size)
621{
e6ccf245 622 DigestFetchState *fetch = (DigestFetchState *)data;
9b7de833 623 size_t hdr_size;
e13ee7ad 624
add2192d 625 assert(fetch->state == DIGEST_READ_HEADERS);
62e76326 626
9b7de833 627 if (peerDigestFetchedEnough(fetch, buf, size, "peerDigestSwapInHeaders"))
62e76326 628 return -1;
e13ee7ad 629
9d486b43 630 assert(!fetch->offset);
62e76326 631
9b7de833 632 if ((hdr_size = headersEnd(buf, size))) {
62e76326 633 assert(fetch->entry->getReply());
634 assert (fetch->entry->getReply()->sline.status != 0);
635
636 if (fetch->entry->getReply()->sline.status != HTTP_OK) {
e0236918 637 debugs(72, DBG_IMPORTANT, "peerDigestSwapInHeaders: " << fetch->pd->host <<
bf8fe701 638 " status " << fetch->entry->getReply()->sline.status <<
639 " got cached!");
640
62e76326 641 peerDigestFetchAbort(fetch, buf, "internal status error");
642 return -1;
643 }
644
645 fetch->state = DIGEST_READ_CBLOCK;
646 return hdr_size; /* Say how much data we read */
9b7de833 647 } else {
62e76326 648 /* need more data, do we have space? */
649
650 if (size >= SM_PAGE_SIZE) {
651 peerDigestFetchAbort(fetch, buf, "stored header too big");
652 return -1;
653 } else {
654 return 0; /* We need to read more to parse .. */
655 }
9b7de833 656 }
62e76326 657
add2192d 658 fatal("peerDigestSwapInHeaders() - shouldn't get here!\n");
0b5f04b8 659 return 0; /* keep gcc happy */
9b7de833 660}
661
1f140227 662int
9b7de833 663peerDigestSwapInCBlock(void *data, char *buf, ssize_t size)
664{
e6ccf245 665 DigestFetchState *fetch = (DigestFetchState *)data;
e13ee7ad 666
add2192d 667 assert(fetch->state == DIGEST_READ_CBLOCK);
62e76326 668
9b7de833 669 if (peerDigestFetchedEnough(fetch, buf, size, "peerDigestSwapInCBlock"))
62e76326 670 return -1;
e13ee7ad 671
e6ccf245 672 if (size >= (ssize_t)StoreDigestCBlockSize) {
62e76326 673 PeerDigest *pd = fetch->pd;
62e76326 674
e4a67a80 675 assert(pd && fetch->entry->getReply());
62e76326 676
677 if (peerDigestSetCBlock(pd, buf)) {
678 /* XXX: soon we will have variable header size */
679 /* switch to CD buffer and fetch digest guts */
680 buf = NULL;
681 assert(pd->cd->mask);
682 fetch->state = DIGEST_READ_MASK;
683 return StoreDigestCBlockSize;
684 } else {
685 peerDigestFetchAbort(fetch, buf, "invalid digest cblock");
686 return -1;
687 }
9b7de833 688 } else {
62e76326 689 /* need more data, do we have space? */
690
691 if (size >= SM_PAGE_SIZE) {
692 peerDigestFetchAbort(fetch, buf, "digest cblock too big");
693 return -1;
694 } else {
695 return 0; /* We need more data */
696 }
9b7de833 697 }
62e76326 698
add2192d 699 fatal("peerDigestSwapInCBlock(): shouldn't get here!\n");
0b5f04b8 700 return 0; /* keep gcc happy */
9b7de833 701}
702
1f140227 703int
9b7de833 704peerDigestSwapInMask(void *data, char *buf, ssize_t size)
705{
e6ccf245 706 DigestFetchState *fetch = (DigestFetchState *)data;
e13ee7ad 707 PeerDigest *pd;
708
e13ee7ad 709 pd = fetch->pd;
710 assert(pd->cd && pd->cd->mask);
9d486b43 711
add2192d 712 /*
713 * NOTENOTENOTENOTENOTE: buf doesn't point to pd->cd->mask anymore!
714 * we need to do the copy ourselves!
715 */
41d00cd3 716 memcpy(pd->cd->mask + fetch->mask_offset, buf, size);
add2192d 717
718 /* NOTE! buf points to the middle of pd->cd->mask! */
62e76326 719
add2192d 720 if (peerDigestFetchedEnough(fetch, NULL, size, "peerDigestSwapInMask"))
62e76326 721 return -1;
add2192d 722
da407def 723 fetch->mask_offset += size;
62e76326 724
57d55dfa 725 if (fetch->mask_offset >= pd->cd->mask_size) {
e4049756 726 debugs(72, 2, "peerDigestSwapInMask: Done! Got " <<
727 fetch->mask_offset << ", expected " << pd->cd->mask_size);
57d55dfa 728 assert(fetch->mask_offset == pd->cd->mask_size);
62e76326 729 assert(peerDigestFetchedEnough(fetch, NULL, 0, "peerDigestSwapInMask"));
730 return -1; /* XXX! */
e13ee7ad 731 } else {
62e76326 732 /* We always read everything, so return so */
733 return size;
9b7de833 734 }
62e76326 735
add2192d 736 fatal("peerDigestSwapInMask(): shouldn't get here!\n");
0b5f04b8 737 return 0; /* keep gcc happy */
9b7de833 738}
739
740static int
4b4cd312 741peerDigestFetchedEnough(DigestFetchState * fetch, char *buf, ssize_t size, const char *step_name)
9b7de833 742{
e13ee7ad 743 PeerDigest *pd = NULL;
8a6218c6 744 const char *host = "<unknown>"; /* peer host */
745 const char *reason = NULL; /* reason for completion */
746 const char *no_bug = NULL; /* successful completion if set */
e2848e94 747 const int pdcb_valid = cbdataReferenceValid(fetch->pd);
748 const int pcb_valid = cbdataReferenceValid(fetch->pd->peer);
e13ee7ad 749
750 /* test possible exiting conditions (the same for most steps!)
751 * cases marked with '?!' should not happen */
752
753 if (!reason) {
62e76326 754 if (!(pd = fetch->pd))
755 reason = "peer digest disappeared?!";
756
bac6d4bd 757#if DONT /* WHY NOT? /HNO */
62e76326 758
759 else if (!cbdataReferenceValid(pd))
760 reason = "invalidated peer digest?!";
761
5385c86a 762#endif
62e76326 763
764 else
b4197865 765 host = pd->host.termedBuf();
e13ee7ad 766 }
62e76326 767
e4049756 768 debugs(72, 6, step_name << ": peer " << host << ", offset: " <<
769 fetch->offset << " size: " << size << ".");
e13ee7ad 770
771 /* continue checking (with pd and host known and valid) */
62e76326 772
e13ee7ad 773 if (!reason) {
62e76326 774 if (!cbdataReferenceValid(pd->peer))
775 reason = "peer disappeared";
776 else if (size < 0)
777 reason = "swap failure";
778 else if (!fetch->entry)
779 reason = "swap aborted?!";
780 else if (EBIT_TEST(fetch->entry->flags, ENTRY_ABORTED))
781 reason = "swap aborted";
e13ee7ad 782 }
62e76326 783
e13ee7ad 784 /* continue checking (maybe-successful eof case) */
785 if (!reason && !size) {
62e76326 786 if (!pd->cd)
787 reason = "null digest?!";
47f6e231 788 else if (fetch->mask_offset != (int)pd->cd->mask_size)
62e76326 789 reason = "premature end of digest?!";
790 else if (!peerDigestUseful(pd))
791 reason = "useless digest";
792 else
793 reason = no_bug = "success";
e13ee7ad 794 }
62e76326 795
e13ee7ad 796 /* finish if we have a reason */
9b7de833 797 if (reason) {
62e76326 798 const int level = strstr(reason, "?!") ? 1 : 3;
bf8fe701 799 debugs(72, level, "" << step_name << ": peer " << host << ", exiting after '" << reason << "'");
62e76326 800 peerDigestReqFinish(fetch, buf,
801 1, pdcb_valid, pcb_valid, reason, !no_bug);
e13ee7ad 802 } else {
62e76326 803 /* paranoid check */
804 assert(pdcb_valid && pcb_valid);
e13ee7ad 805 }
62e76326 806
e13ee7ad 807 return reason != NULL;
808}
809
551e60a9 810/* call this when all callback data is valid and fetch must be stopped but
811 * no error has occurred (e.g. we received 304 reply and reuse old digest) */
812static void
813peerDigestFetchStop(DigestFetchState * fetch, char *buf, const char *reason)
814{
815 assert(reason);
b4197865 816 debugs(72, 2, "peerDigestFetchStop: peer " << fetch->pd->host << ", reason: " << reason);
551e60a9 817 peerDigestReqFinish(fetch, buf, 1, 1, 1, reason, 0);
818}
819
e13ee7ad 820/* call this when all callback data is valid but something bad happened */
821static void
822peerDigestFetchAbort(DigestFetchState * fetch, char *buf, const char *reason)
823{
551e60a9 824 assert(reason);
b4197865 825 debugs(72, 2, "peerDigestFetchAbort: peer " << fetch->pd->host << ", reason: " << reason);
e13ee7ad 826 peerDigestReqFinish(fetch, buf, 1, 1, 1, reason, 1);
827}
828
829/* complete the digest transfer, update stats, unlock/release everything */
830static void
831peerDigestReqFinish(DigestFetchState * fetch, char *buf,
62e76326 832 int fcb_valid, int pdcb_valid, int pcb_valid,
833 const char *reason, int err)
e13ee7ad 834{
835 assert(reason);
836
837 /* must go before peerDigestPDFinish */
62e76326 838
e13ee7ad 839 if (pdcb_valid) {
62e76326 840 fetch->pd->flags.requested = 0;
841 fetch->pd->req_result = reason;
e13ee7ad 842 }
62e76326 843
e13ee7ad 844 /* schedule next check if peer is still out there */
845 if (pcb_valid) {
62e76326 846 PeerDigest *pd = fetch->pd;
847
848 if (err) {
849 pd->times.retry_delay = peerDigestIncDelay(pd);
850 peerDigestSetCheck(pd, pd->times.retry_delay);
851 } else {
852 pd->times.retry_delay = 0;
853 peerDigestSetCheck(pd, peerDigestNewDelay(fetch->entry));
854 }
9b7de833 855 }
62e76326 856
e13ee7ad 857 /* note: order is significant */
858 if (fcb_valid)
62e76326 859 peerDigestFetchSetStats(fetch);
860
e13ee7ad 861 if (pdcb_valid)
62e76326 862 peerDigestPDFinish(fetch, pcb_valid, err);
863
e13ee7ad 864 if (fcb_valid)
62e76326 865 peerDigestFetchFinish(fetch, err);
9b7de833 866}
867
e13ee7ad 868/* destroys digest if peer disappeared
869 * must be called only when fetch and pd cbdata are valid */
9b7de833 870static void
e13ee7ad 871peerDigestPDFinish(DigestFetchState * fetch, int pcb_valid, int err)
9b7de833 872{
e13ee7ad 873 PeerDigest *pd = fetch->pd;
b4197865 874 const char *host = pd->host.termedBuf();
e13ee7ad 875
876 pd->times.received = squid_curtime;
877 pd->times.req_delay = fetch->resp_time;
8a6218c6 878 kb_incr(&pd->stats.sent.kbytes, (size_t) fetch->sent.bytes);
879 kb_incr(&pd->stats.recv.kbytes, (size_t) fetch->recv.bytes);
e13ee7ad 880 pd->stats.sent.msgs += fetch->sent.msg;
881 pd->stats.recv.msgs += fetch->recv.msg;
882
883 if (err) {
e0236918 884 debugs(72, DBG_IMPORTANT, "" << (pcb_valid ? "temporary " : "" ) << "disabling (" << pd->req_result << ") digest from " << host);
62e76326 885
886 if (pd->cd) {
887 cacheDigestDestroy(pd->cd);
888 pd->cd = NULL;
889 }
890
891 pd->flags.usable = 0;
892
893 if (!pcb_valid)
894 peerDigestNotePeerGone(pd);
e13ee7ad 895 } else {
62e76326 896 assert(pcb_valid);
897
898 pd->flags.usable = 1;
e13ee7ad 899
62e76326 900 /* XXX: ugly condition, but how? */
e13ee7ad 901
62e76326 902 if (fetch->entry->store_status == STORE_OK)
bf8fe701 903 debugs(72, 2, "re-used old digest from " << host);
62e76326 904 else
bf8fe701 905 debugs(72, 2, "received valid digest from " << host);
d1cdaa16 906 }
62e76326 907
fa80a8ef 908 cbdataReferenceDone(fetch->pd);
e13ee7ad 909}
910
911/* free fetch state structures
912 * must be called only when fetch cbdata is valid */
913static void
914peerDigestFetchFinish(DigestFetchState * fetch, int err)
915{
916 assert(fetch->entry && fetch->request);
917
9b7de833 918 if (fetch->old_entry) {
48e7baac 919 debugs(72, 3, "peerDigestFetchFinish: deleting old entry");
8121ba82 920 storeUnregister(fetch->old_sc, fetch->old_entry, fetch);
d88e3c49 921 fetch->old_entry->releaseRequest();
97b5e68f 922 fetch->old_entry->unlock();
62e76326 923 fetch->old_entry = NULL;
9b7de833 924 }
62e76326 925
1543ab6c 926 /* update global stats */
83704487 927 kb_incr(&statCounter.cd.kbytes_sent, (size_t) fetch->sent.bytes);
62e76326 928
83704487 929 kb_incr(&statCounter.cd.kbytes_recv, (size_t) fetch->recv.bytes);
62e76326 930
83704487 931 statCounter.cd.msgs_sent += fetch->sent.msg;
62e76326 932
83704487 933 statCounter.cd.msgs_recv += fetch->recv.msg;
e13ee7ad 934
1543ab6c 935 /* unlock everything */
06d2839d 936 storeUnregister(fetch->sc, fetch->entry, fetch);
62e76326 937
97b5e68f 938 fetch->entry->unlock();
62e76326 939
6dd9f4bd 940 HTTPMSGUNLOCK(fetch->request);
62e76326 941
9b7de833 942 fetch->entry = NULL;
62e76326 943
3855c318 944 assert(fetch->pd == NULL);
62e76326 945
9b7de833 946 cbdataFree(fetch);
9b7de833 947}
948
e13ee7ad 949/* calculate fetch stats after completion */
950static void
951peerDigestFetchSetStats(DigestFetchState * fetch)
952{
953 MemObject *mem;
954 assert(fetch->entry && fetch->request);
955
956 mem = fetch->entry->mem_obj;
957 assert(mem);
958
959 /* XXX: outgoing numbers are not precise */
960 /* XXX: we must distinguish between 304 hits and misses here */
e11fe29a 961 fetch->sent.bytes = fetch->request->prefixLen();
528b2c61 962 /* XXX: this is slightly wrong: we don't KNOW that the entire memobject
963 * was fetched. We only know how big it is
964 */
965 fetch->recv.bytes = mem->size();
e13ee7ad 966 fetch->sent.msg = fetch->recv.msg = 1;
967 fetch->expires = fetch->entry->expires;
968 fetch->resp_time = squid_curtime - fetch->start_time;
969
bf8fe701 970 debugs(72, 3, "peerDigestFetchFinish: recv " << fetch->recv.bytes <<
971 " bytes in " << (int) fetch->resp_time << " secs");
972
973 debugs(72, 3, "peerDigestFetchFinish: expires: " <<
974 (long int) fetch->expires << " (" << std::showpos <<
975 (int) (fetch->expires - squid_curtime) << "), lmt: " <<
976 std::noshowpos << (long int) fetch->entry->lastmod << " (" <<
977 std::showpos << (int) (fetch->entry->lastmod - squid_curtime) <<
978 ")");
979
e13ee7ad 980}
981
9b7de833 982static int
8a6218c6 983peerDigestSetCBlock(PeerDigest * pd, const char *buf)
9b7de833 984{
985 StoreDigestCBlock cblock;
986 int freed_size = 0;
b4197865 987 const char *host = pd->host.termedBuf();
e13ee7ad 988
41d00cd3 989 memcpy(&cblock, buf, sizeof(cblock));
9b7de833 990 /* network -> host conversions */
991 cblock.ver.current = ntohs(cblock.ver.current);
992 cblock.ver.required = ntohs(cblock.ver.required);
993 cblock.capacity = ntohl(cblock.capacity);
994 cblock.count = ntohl(cblock.count);
995 cblock.del_count = ntohl(cblock.del_count);
996 cblock.mask_size = ntohl(cblock.mask_size);
bf8fe701 997 debugs(72, 2, "got digest cblock from " << host << "; ver: " <<
998 (int) cblock.ver.current << " (req: " << (int) cblock.ver.required <<
999 ")");
1000
26ac0430
AJ
1001 debugs(72, 2, "\t size: " <<
1002 cblock.mask_size << " bytes, e-cnt: " <<
1003 cblock.count << ", e-util: " <<
bf8fe701 1004 xpercentInt(cblock.count, cblock.capacity) << "%" );
6106c6fc 1005 /* check version requirements (both ways) */
62e76326 1006
9b7de833 1007 if (cblock.ver.required > CacheDigestVer.current) {
e0236918 1008 debugs(72, DBG_IMPORTANT, "" << host << " digest requires version " <<
bf8fe701 1009 cblock.ver.required << "; have: " << CacheDigestVer.current);
1010
62e76326 1011 return 0;
9b7de833 1012 }
62e76326 1013
6106c6fc 1014 if (cblock.ver.current < CacheDigestVer.required) {
e0236918 1015 debugs(72, DBG_IMPORTANT, "" << host << " digest is version " <<
bf8fe701 1016 cblock.ver.current << "; we require: " <<
1017 CacheDigestVer.required);
1018
62e76326 1019 return 0;
6106c6fc 1020 }
62e76326 1021
9b7de833 1022 /* check consistency */
4b4cd312 1023 if (cblock.ver.required > cblock.ver.current ||
62e76326 1024 cblock.mask_size <= 0 || cblock.capacity <= 0 ||
1025 cblock.bits_per_entry <= 0 || cblock.hash_func_count <= 0) {
fa84c01d 1026 debugs(72, DBG_CRITICAL, "" << host << " digest cblock is corrupted.");
62e76326 1027 return 0;
9b7de833 1028 }
62e76326 1029
d1cdaa16 1030 /* check consistency further */
e6ccf245 1031 if ((size_t)cblock.mask_size != cacheDigestCalcMaskSize(cblock.capacity, cblock.bits_per_entry)) {
fa84c01d 1032 debugs(72, DBG_CRITICAL, host << " digest cblock is corrupted " <<
e4049756 1033 "(mask size mismatch: " << cblock.mask_size << " ? " <<
1034 cacheDigestCalcMaskSize(cblock.capacity, cblock.bits_per_entry)
1035 << ").");
62e76326 1036 return 0;
d1cdaa16 1037 }
62e76326 1038
d1cdaa16 1039 /* there are some things we cannot do yet */
1040 if (cblock.hash_func_count != CacheDigestHashFuncCount) {
fa84c01d 1041 debugs(72, DBG_CRITICAL, "" << host << " digest: unsupported #hash functions: " <<
bf8fe701 1042 cblock.hash_func_count << " ? " << CacheDigestHashFuncCount << ".");
62e76326 1043 return 0;
d1cdaa16 1044 }
62e76326 1045
9b7de833 1046 /*
1047 * no cblock bugs below this point
1048 */
1049 /* check size changes */
e6ccf245 1050 if (pd->cd && cblock.mask_size != (ssize_t)pd->cd->mask_size) {
e4049756 1051 debugs(72, 2, host << " digest changed size: " << cblock.mask_size <<
1052 " -> " << pd->cd->mask_size);
62e76326 1053 freed_size = pd->cd->mask_size;
1054 cacheDigestDestroy(pd->cd);
1055 pd->cd = NULL;
9b7de833 1056 }
62e76326 1057
e13ee7ad 1058 if (!pd->cd) {
26ac0430
AJ
1059 debugs(72, 2, "creating " << host << " digest; size: " << cblock.mask_size << " (" <<
1060 std::showpos << (int) (cblock.mask_size - freed_size) << ") bytes");
62e76326 1061 pd->cd = cacheDigestCreate(cblock.capacity, cblock.bits_per_entry);
1062
1063 if (cblock.mask_size >= freed_size)
1064 kb_incr(&statCounter.cd.memory, cblock.mask_size - freed_size);
9b7de833 1065 }
62e76326 1066
e13ee7ad 1067 assert(pd->cd);
9b7de833 1068 /* these assignments leave us in an inconsistent state until we finish reading the digest */
e13ee7ad 1069 pd->cd->count = cblock.count;
1070 pd->cd->del_count = cblock.del_count;
9b7de833 1071 return 1;
1072}
1073
9b7de833 1074static int
8a6218c6 1075peerDigestUseful(const PeerDigest * pd)
9b7de833 1076{
d1cdaa16 1077 /* TODO: we should calculate the prob of a false hit instead of bit util */
e13ee7ad 1078 const int bit_util = cacheDigestBitUtil(pd->cd);
62e76326 1079
e13ee7ad 1080 if (bit_util > 65) {
fa84c01d 1081 debugs(72, DBG_CRITICAL, "Warning: " << pd->host <<
bf8fe701 1082 " peer digest has too many bits on (" << bit_util << "%%).");
1083
62e76326 1084 return 0;
9b7de833 1085 }
62e76326 1086
9b7de833 1087 return 1;
1088}
7f6eb0fe 1089
e13ee7ad 1090static int
1091saneDiff(time_t diff)
1092{
3db6fb54 1093 return abs((int) diff) > squid_curtime / 2 ? 0 : diff;
e13ee7ad 1094}
1095
1096void
8a6218c6 1097peerDigestStatsReport(const PeerDigest * pd, StoreEntry * e)
e13ee7ad 1098{
1099#define f2s(flag) (pd->flags.flag ? "yes" : "no")
38650cc8 1100#define appendTime(tm) storeAppendPrintf(e, "%s\t %10ld\t %+d\t %+d\n", \
1101 ""#tm, (long int)pd->times.tm, \
e13ee7ad 1102 saneDiff(pd->times.tm - squid_curtime), \
1103 saneDiff(pd->times.tm - pd->times.initialized))
1104
e13ee7ad 1105 assert(pd);
1106
b4197865 1107 const char *host = pd->host.termedBuf();
e13ee7ad 1108 storeAppendPrintf(e, "\npeer digest from %s\n", host);
1109
1110 cacheDigestGuessStatsReport(&pd->stats.guess, e, host);
1111
1112 storeAppendPrintf(e, "\nevent\t timestamp\t secs from now\t secs from init\n");
1113 appendTime(initialized);
1114 appendTime(needed);
1115 appendTime(requested);
1116 appendTime(received);
1117 appendTime(next_check);
1118
1119 storeAppendPrintf(e, "peer digest state:\n");
1120 storeAppendPrintf(e, "\tneeded: %3s, usable: %3s, requested: %3s\n",
62e76326 1121 f2s(needed), f2s(usable), f2s(requested));
e13ee7ad 1122 storeAppendPrintf(e, "\n\tlast retry delay: %d secs\n",
62e76326 1123 (int) pd->times.retry_delay);
e13ee7ad 1124 storeAppendPrintf(e, "\tlast request response time: %d secs\n",
62e76326 1125 (int) pd->times.req_delay);
e13ee7ad 1126 storeAppendPrintf(e, "\tlast request result: %s\n",
62e76326 1127 pd->req_result ? pd->req_result : "(none)");
e13ee7ad 1128
1129 storeAppendPrintf(e, "\npeer digest traffic:\n");
1130 storeAppendPrintf(e, "\trequests sent: %d, volume: %d KB\n",
62e76326 1131 pd->stats.sent.msgs, (int) pd->stats.sent.kbytes.kb);
e13ee7ad 1132 storeAppendPrintf(e, "\treplies recv: %d, volume: %d KB\n",
62e76326 1133 pd->stats.recv.msgs, (int) pd->stats.recv.kbytes.kb);
e13ee7ad 1134
1135 storeAppendPrintf(e, "\npeer digest structure:\n");
62e76326 1136
e13ee7ad 1137 if (pd->cd)
62e76326 1138 cacheDigestReport(pd->cd, host, e);
e13ee7ad 1139 else
62e76326 1140 storeAppendPrintf(e, "\tno in-memory copy\n");
e13ee7ad 1141}
1142
7f6eb0fe 1143#endif