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