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