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