Squid Web Cache master
Loading...
Searching...
No Matches
store_client.cc
Go to the documentation of this file.
1/*
2 * Copyright (C) 1996-2025 The Squid Software Foundation and contributors
3 *
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.
7 */
8
9/* DEBUG: section 90 Storage Manager Client-Side Interface */
10
11#include "squid.h"
12#include "acl/FilledChecklist.h"
14#include "base/CodeContext.h"
15#include "event.h"
16#include "globals.h"
17#include "HttpReply.h"
18#include "HttpRequest.h"
19#include "MemBuf.h"
20#include "MemObject.h"
21#include "mime_header.h"
22#include "sbuf/Stream.h"
23#include "SquidConfig.h"
24#include "SquidMath.h"
25#include "StatCounters.h"
26#include "Store.h"
27#include "store/SwapMetaIn.h"
28#include "store_swapin.h"
29#include "StoreClient.h"
30#if USE_DELAY_POOLS
31#include "DelayPools.h"
32#endif
33
34/*
35 * NOTE: 'Header' refers to the swapfile metadata header.
36 * 'OBJHeader' refers to the object header, with canonical
37 * processed object headers (which may derive from FTP/HTTP etc
38 * upstream protocols
39 * 'Body' refers to the swapfile body, which is the full
40 * HTTP reply (including HTTP headers and body).
41 */
44static void storeClientCopy2(StoreEntry * e, store_client * sc);
45static bool CheckQuickAbortIsReasonable(StoreEntry * entry);
46
48
49/* StoreClient */
50
51bool
53{
55 return false;
56
58 return true;
59
61 fillChecklist(checklist);
62 return checklist.fastCheck().allowed();
63}
64
65bool
66StoreClient::startCollapsingOn(const StoreEntry &e, const bool doingRevalidation) const
67{
69 return false; // collapsing is impossible due to the entry state
70
71 if (!onCollapsingPath())
72 return false; // collapsing is impossible due to Squid configuration
73
74 /* collapsing is possible; the caller must collapse */
75
76 if (const auto tags = loggingTags()) {
77 if (doingRevalidation)
78 tags->collapsingHistory.revalidationCollapses++;
79 else
80 tags->collapsingHistory.otherCollapses++;
81 }
82
83 didCollapse = true;
84 debugs(85, 5, e << " doingRevalidation=" << doingRevalidation);
85 return true;
86}
87
88/* store_client */
89
90int
92{
93 return type;
94}
95
96#if STORE_CLIENT_LIST_DEBUG
97static store_client *
98storeClientListSearch(const MemObject * mem, void *data)
99{
101 store_client *sc = nullptr;
102
103 for (node = mem->clients.head; node; node = node->next) {
104 sc = node->data;
105
106 if (sc->owner == data)
107 return sc;
108 }
109
110 return nullptr;
111}
112
113int
114storeClientIsThisAClient(store_client * sc, void *someClient)
115{
116 return sc->owner == someClient;
117}
118
119#endif
120#include "HttpRequest.h"
121
122/* add client with fd to client list */
125{
126 MemObject *mem = e->mem_obj;
127 store_client *sc;
128 assert(mem);
129#if STORE_CLIENT_LIST_DEBUG
130
131 if (storeClientListSearch(mem, data) != NULL)
132 /* XXX die! */
133 assert(1 == 0);
134#else
135 (void)data;
136#endif
137
138 sc = new store_client (e);
139
140 mem->addClient(sc);
141
142 return sc;
143}
144
146void
151
153void
155{
158
159 // XXX: Some legacy code relies on zero-length buffers having nil data
160 // pointers. Some other legacy code expects "correct" result.offset even
161 // when there is no body to return. Accommodate all those expectations.
162 auto result = StoreIOBuffer(0, copyInto.offset, nullptr);
163 if (object_ok && parsingBuffer && parsingBuffer->contentSize())
164 result = parsingBuffer->packBack();
165 result.flags.error = object_ok ? 0 : 1;
166
167 // TODO: Move object_ok handling above into this `if` statement.
168 if (object_ok) {
169 // works for zero hdr_sz cases as well; see also: nextHttpReadOffset()
170 discardableHttpEnd_ = NaturalSum<int64_t>(entry->mem().baseReply().hdr_sz, result.offset, result.length).value();
171 } else {
172 // object_ok is sticky, so we will not be able to use any response bytes
174 }
175 debugs(90, 7, "with " << result << "; discardableHttpEnd_=" << discardableHttpEnd_);
176
177 // no HTTP headers and no body bytes (but not because there was no space)
178 atEof_ = !sendingHttpHeaders() && !result.length && copyInto.length;
179
180 parsingBuffer.reset();
181 ++answers;
182
183 STCB *temphandler = _callback.callback_handler;
184 const auto cbdata = _callback.cbData.validDone();
186 copyInto.data = nullptr;
187
188 if (cbdata)
189 temphandler(cbdata, result);
190}
191
193#if STORE_CLIENT_LIST_DEBUG
194 owner(cbdataReference(data)),
195#endif
196 entry(e),
197 type(e->storeClientType()),
198 object_ok(true),
199 atEof_(false),
200 answers(0)
201{
202 Assure(entry);
203 entry->lock("store_client");
204
205 flags.disk_io_pending = false;
206 flags.store_copying = false;
207 ++ entry->refcount;
208
209 if (getType() == STORE_DISK_CLIENT) {
210 /* assert we'll be able to get the data we want */
211 /* maybe we should open swapin_sio here */
213 }
214}
215
217{
218 assert(entry);
219 entry->unlock("store_client");
220}
221
222/* copy bytes requested by the client */
223void
225 StoreEntry * e,
226 StoreIOBuffer copyInto,
227 STCB * callback,
228 void *data)
229{
230 assert (sc != nullptr);
231 sc->copy(e, copyInto,callback,data);
232}
233
234void
236 StoreIOBuffer copyRequest,
237 STCB * callback_fn,
238 void *data)
239{
240 assert (anEntry == entry);
241 assert (callback_fn);
242 assert (data);
244 debugs(90, 3, "store_client::copy: " << entry->getMD5Text() << ", from " <<
245 copyRequest.offset << ", for length " <<
246 (int) copyRequest.length << ", cb " << callback_fn << ", cbdata " <<
247 data);
248
249#if STORE_CLIENT_LIST_DEBUG
250
251 assert(this == storeClientListSearch(entry->mem_obj, data));
252#endif
253
255 _callback = Callback(callback_fn, data);
256 copyInto.data = copyRequest.data;
257 copyInto.length = copyRequest.length;
258 copyInto.offset = copyRequest.offset;
259 Assure(copyInto.offset >= 0);
260
261 if (!copyInto.length) {
262 // During the first storeClientCopy() call, a zero-size buffer means
263 // that we will have to drop any HTTP response body bytes we read (with
264 // the HTTP headers from disk). After that, it means we cannot return
265 // anything to the caller at all.
266 debugs(90, 2, "WARNING: zero-size storeClientCopy() buffer: " << copyInto);
267 // keep going; moreToRead() should prevent any from-Store reading
268 }
269
270 // Our nextHttpReadOffset() expects the first copy() call to have zero
271 // offset. More complex code could handle a positive first offset, but it
272 // would only be useful when reading responses from memory: We would not
273 // _delay_ the response (to read the requested HTTP body bytes from disk)
274 // when we already can respond with HTTP headers.
276
277 parsingBuffer.emplace(copyInto);
278
280 debugs(90, 7, "discardableHttpEnd_=" << discardableHttpEnd_);
281
282 static bool copying (false);
283 assert (!copying);
284 copying = true;
285 /* we might be blocking comm reads due to readahead limits
286 * now we have a new offset, trigger those reads...
287 */
289 copying = false;
290
291 anEntry->lock("store_client::copy"); // see deletion note below
292
293 storeClientCopy2(entry, this);
294
295 // Bug 3480: This store_client object may be deleted now if, for example,
296 // the client rejects the hit response copied above. Use on-stack pointers!
297
298#if USE_ADAPTATION
299 anEntry->kickProducer();
300#endif
301 anEntry->unlock("store_client::copy");
302
303 // Add no code here. This object may no longer exist.
304}
305
307bool
309{
310 if (!copyInto.length)
311 return false; // the client supplied a zero-size buffer
312
314 return true; // there may be more coming
315
316 /* STORE_OK, including aborted entries: no more data is coming */
317
318 if (canReadFromMemory())
319 return true; // memory has the first byte wanted by the client
320
321 if (!entry->hasDisk())
322 return false; // cannot read anything from disk either
323
324 if (entry->objectLen() >= 0 && copyInto.offset >= entry->contentLen())
325 return false; // the disk cannot have byte(s) wanted by the client
326
327 // we cannot be sure until we swap in metadata and learn contentLen(),
328 // but the disk may have the byte(s) wanted by the client
329 return true;
330}
331
332static void
334{
335 /* reentrancy not allowed - note this could lead to
336 * dropped notifications about response data availability
337 */
338
339 if (sc->flags.store_copying) {
340 debugs(90, 3, "prevented recursive copying for " << *e);
341 return;
342 }
343
344 debugs(90, 3, "storeClientCopy2: " << e->getMD5Text());
345 assert(sc->_callback.pending());
346 /*
347 * We used to check for ENTRY_ABORTED here. But there were some
348 * problems. For example, we might have a slow client (or two) and
349 * the peer server is reading far ahead and swapping to disk. Even
350 * if the peer aborts, we want to give the client(s)
351 * everything we got before the abort condition occurred.
352 */
353 sc->doCopy(e);
354}
355
358bool
360{
361 return !answeredOnce() && entry->mem().baseReply().hdr_sz > 0;
362}
363
364void
366{
368 Assure(!flags.disk_io_pending);
369 Assure(!flags.store_copying);
370
371 assert (anEntry == entry);
372 flags.store_copying = true;
373 MemObject *mem = entry->mem_obj;
374
375 debugs(33, 5, this << " into " << copyInto <<
376 " hi: " << mem->endOffset() <<
377 " objectLen: " << entry->objectLen() <<
378 " past_answers: " << answers);
379
380 const auto sendHttpHeaders = sendingHttpHeaders();
381
382 if (!sendHttpHeaders && !moreToRead()) {
383 /* There is no more to send! */
384 debugs(33, 3, "There is no more to send!");
385 noteNews();
386 flags.store_copying = false;
387 return;
388 }
389
390 if (!sendHttpHeaders && anEntry->store_status == STORE_PENDING && nextHttpReadOffset() >= mem->endOffset()) {
391 debugs(90, 3, "store_client::doCopy: Waiting for more");
392 flags.store_copying = false;
393 return;
394 }
395
396 /*
397 * Slight weirdness here. We open a swapin file for any
398 * STORE_DISK_CLIENT, even if we can copy the requested chunk
399 * from memory in the next block. We must try to open the
400 * swapin file before sending any data to the client side. If
401 * we postpone the open, and then can not open the file later
402 * on, the client loses big time. Its transfer just gets cut
403 * off. Better to open it early (while the client side handler
404 * is clientCacheHit) so that we can fall back to a cache miss
405 * if needed.
406 */
407
408 if (STORE_DISK_CLIENT == getType() && swapin_sio == nullptr) {
409 if (!startSwapin())
410 return; // failure
411 }
412
413 // Send any immediately available body bytes unless we sendHttpHeaders.
414 // TODO: Send those body bytes when we sendHttpHeaders as well.
415 if (!sendHttpHeaders && canReadFromMemory()) {
417 noteNews(); // will sendHttpHeaders (if needed) as well
418 flags.store_copying = false;
419 return;
420 }
421
422 if (sendHttpHeaders) {
423 debugs(33, 5, "just send HTTP headers: " << mem->baseReply().hdr_sz);
424 noteNews();
425 flags.store_copying = false;
426 return;
427 }
428
429 // no information that the client needs is available immediately
431}
432
434bool
436{
437 debugs(90, 3, "store_client::doCopy: Need to open swap in file");
438 /* gotta open the swapin file */
439
441 /* yuck -- this causes a TCP_SWAPFAIL_MISS on the client side */
442 fail();
443 flags.store_copying = false;
444 return false;
445 } else if (!flags.disk_io_pending) {
446 /* Don't set store_io_pending here */
447 storeSwapInStart(this);
448
449 if (swapin_sio == nullptr) {
450 fail();
451 flags.store_copying = false;
452 return false;
453 }
454
455 return true;
456 } else {
457 debugs(90, DBG_IMPORTANT, "WARNING: Averted multiple fd operation (1)");
458 flags.store_copying = false;
459 return false;
460 }
461}
462
463void
465{
467 if (error)
468 fail();
469 else
470 noteNews();
471}
472
473void
475{
476 /* What the client wants is not in memory. Schedule a disk read */
477 if (getType() == STORE_DISK_CLIENT) {
478 // we should have called startSwapin() already
479 assert(swapin_sio != nullptr);
480 } else if (!swapin_sio && !startSwapin()) {
481 debugs(90, 3, "bailing after swapin start failure for " << *entry);
482 assert(!flags.store_copying);
483 return;
484 }
485
486 assert(!flags.disk_io_pending);
487
488 debugs(90, 3, "reading " << *entry << " from disk");
489
490 fileRead();
491
492 flags.store_copying = false;
493}
494
496bool
498{
499 const auto &mem = entry->mem();
500 const auto memReadOffset = nextHttpReadOffset();
501 // XXX: This (lo <= offset < end) logic does not support Content-Range gaps.
502 return mem.inmem_lo <= memReadOffset && memReadOffset < mem.endOffset() &&
503 parsingBuffer->spaceSize();
504}
505
507int64_t
509{
511 const auto &mem = entry->mem();
512 const auto hdr_sz = mem.baseReply().hdr_sz;
513 // Certain SMP cache manager transactions do not store HTTP headers in
514 // mem_hdr; they store just a kid-specific piece of the future report body.
515 // In such cases, hdr_sz ought to be zero. In all other (known) cases,
516 // mem_hdr contains HTTP response headers (positive hdr_sz if parsed)
517 // followed by HTTP response body. This code math accommodates all cases.
518 return NaturalSum<int64_t>(hdr_sz, copyInto.offset, parsingBuffer->contentSize()).value();
519}
520
524void
526{
528 const auto readInto = parsingBuffer->space().positionAt(nextHttpReadOffset());
529
530 debugs(90, 3, "copying HTTP body bytes from memory into " << readInto);
531 const auto sz = entry->mem_obj->data_hdr.copy(readInto);
532 Assure(sz > 0); // our canReadFromMemory() precondition guarantees that
533 parsingBuffer->appended(readInto.data, sz);
534}
535
536void
538{
539 MemObject *mem = entry->mem_obj;
540
542 assert(!flags.disk_io_pending);
543 flags.disk_io_pending = true;
544
545 // mem->swap_hdr_sz is zero here during initial read(s)
546 const auto nextStoreReadOffset = NaturalSum<int64_t>(mem->swap_hdr_sz, nextHttpReadOffset()).value();
547
548 // XXX: If fileRead() is called when we do not yet know mem->swap_hdr_sz,
549 // then we must start reading from disk offset zero to learn it: we cannot
550 // compute correct HTTP response start offset on disk without it. However,
551 // late startSwapin() calls imply that the assertion below might fail.
552 Assure(mem->swap_hdr_sz > 0 || !nextStoreReadOffset);
553
554 // TODO: Remove this assertion. Introduced in 1998 commit 3157c72, it
555 // assumes that swapped out memory is freed unconditionally, but we no
556 // longer do that because trimMemory() path checks lowestMemReaderOffset().
557 // It is also misplaced: We are not swapping out anything here and should
558 // not care about any swapout invariants.
559 if (mem->swap_hdr_sz != 0)
560 if (entry->swappingOut())
561 assert(mem->swapout.sio->offset() > nextStoreReadOffset);
562
563 // XXX: We should let individual cache_dirs limit the read size instead, but
564 // we cannot do that without more fixes and research because:
565 // * larger reads corrupt responses when cache_dir uses SharedMemory::get();
566 // * we do not know how to find all I/O code that assumes this limit;
567 // * performance effects of larger disk reads may be negative somewhere.
568 const decltype(StoreIOBuffer::length) maxReadSize = SM_PAGE_SIZE;
569
571 // also, do not read more than we can return (via a copyInto.length buffer)
572 const auto readSize = std::min(copyInto.length, maxReadSize);
573 lastDiskRead = parsingBuffer->makeSpace(readSize).positionAt(nextStoreReadOffset);
574 debugs(90, 5, "into " << lastDiskRead);
575
582 this);
583}
584
585void
586store_client::readBody(const char * const buf, const ssize_t lastIoResult)
587{
588 Assure(flags.disk_io_pending);
589 flags.disk_io_pending = false;
592 debugs(90, 3, "got " << lastIoResult << " using " << *parsingBuffer);
593
594 if (lastIoResult < 0)
595 return fail();
596
597 if (!lastIoResult) {
598 if (answeredOnce())
599 return noteNews();
600
601 debugs(90, DBG_CRITICAL, "ERROR: Truncated HTTP headers in on-disk object");
602 return fail();
603 }
604
605 assert(lastDiskRead.data == buf);
606 lastDiskRead.length = lastIoResult;
607
608 parsingBuffer->appended(buf, lastIoResult);
609
610 // we know swap_hdr_sz by now and were reading beyond swap metadata because
611 // readHead() would have been called otherwise (to read swap metadata)
612 const auto swap_hdr_sz = entry->mem().swap_hdr_sz;
613 Assure(swap_hdr_sz > 0);
614 Assure(!Less(lastDiskRead.offset, swap_hdr_sz));
615
616 // Map lastDiskRead (i.e. the disk area we just read) to an HTTP reply part.
617 // The bytes are the same, but disk and HTTP offsets differ by swap_hdr_sz.
618 const auto httpOffset = lastDiskRead.offset - swap_hdr_sz;
619 const auto httpPart = StoreIOBuffer(lastDiskRead).positionAt(httpOffset);
620
623}
624
626void
628{
629 // We cannot de-serialize on-disk HTTP response without MemObject because
630 // without MemObject::swap_hdr_sz we cannot know where that response starts.
633
634 if (!answeredOnce()) {
635 // All on-disk responses have HTTP headers. First disk body read(s)
636 // include HTTP headers that we must parse (if needed) and skip.
637 const auto haveHttpHeaders = entry->hasParsedReplyHeader();
638 if (!haveHttpHeaders && !parseHttpHeadersFromDisk())
639 return;
641 }
642
643 noteNews();
644}
645
649void
651{
652 // XXX: Reject [memory-]uncachable/unshareable responses instead of assuming
653 // that an HTTP response should be written to MemObject's data_hdr (and that
654 // it may purge already cached entries) just because it "fits" and was
655 // loaded from disk. For example, this response may already be marked for
656 // release. The (complex) cachability decision(s) should be made outside
657 // (and obeyed by) this low-level code.
658 if (httpResponsePart.length && entry->mem_obj->inmem_lo == 0 && entry->objectLen() <= (int64_t)Config.Store.maxInMemObjSize && Config.onoff.memory_cache_disk) {
659 storeGetMemSpace(httpResponsePart.length);
660 // XXX: This "recheck" is not needed because storeGetMemSpace() cannot
661 // purge mem_hdr bytes of a locked entry, and we do lock ours. And
662 // inmem_lo offset itself should not be relevant to appending new bytes.
663 //
664 // recheck for the above call may purge entry's data from the memory cache
665 if (entry->mem_obj->inmem_lo == 0) {
666 // XXX: This code assumes a non-shared memory cache.
667 if (httpResponsePart.offset == entry->mem_obj->endOffset())
668 entry->mem_obj->write(httpResponsePart);
669 }
670 }
671}
672
673void
675{
676 debugs(90, 3, (object_ok ? "once" : "again"));
677 if (!object_ok)
678 return; // we failed earlier; nothing to do now
679
680 object_ok = false;
681
682 noteNews();
683}
684
686void
688{
689 /* synchronous open failures callback from the store,
690 * before startSwapin detects the failure.
691 * TODO: fix this inconsistent behaviour - probably by
692 * having storeSwapInStart become a callback functions,
693 * not synchronous
694 */
695
697 debugs(90, 5, "client lost interest");
698 return;
699 }
700
701 if (_callback.notifier) {
702 debugs(90, 5, "earlier news is being delivered by " << _callback.notifier);
703 return;
704 }
705
706 _callback.notifier = asyncCall(90, 4, "store_client::FinishCallback", cbdataDialer(store_client::FinishCallback, this));
708
710}
711
712static void
713storeClientReadHeader(void *data, const char *buf, ssize_t len, StoreIOState::Pointer)
714{
715 store_client *sc = (store_client *)data;
716 sc->readHeader(buf, len);
717}
718
719static void
720storeClientReadBody(void *data, const char *buf, ssize_t len, StoreIOState::Pointer)
721{
722 store_client *sc = (store_client *)data;
723 sc->readBody(buf, len);
724}
725
726void
727store_client::readHeader(char const *buf, ssize_t len)
728{
729 MemObject *const mem = entry->mem_obj;
730
731 assert(flags.disk_io_pending);
732 flags.disk_io_pending = false;
734
735 // abort if we fail()'d earlier
736 if (!object_ok)
737 return;
738
740 debugs(90, 3, "got " << len << " using " << *parsingBuffer);
741
742 if (len < 0)
743 return fail();
744
745 try {
746 Assure(!parsingBuffer->contentSize());
747 parsingBuffer->appended(buf, len);
749 parsingBuffer->consume(mem->swap_hdr_sz);
750 } catch (...) {
751 debugs(90, DBG_IMPORTANT, "ERROR: Failed to unpack Store entry metadata: " << CurrentException);
752 fail();
753 return;
754 }
755
758}
759
760/*
761 * This routine hasn't been optimised to take advantage of the
762 * passed sc. Yet.
763 */
764int
766{
767 MemObject *mem = e->mem_obj;
768#if STORE_CLIENT_LIST_DEBUG
769 assert(sc == storeClientListSearch(e->mem_obj, data));
770#else
771 (void)data;
772#endif
773
774 if (mem == nullptr)
775 return 0;
776
777 debugs(90, 3, "storeUnregister: called for '" << e->getMD5Text() << "'");
778
779 if (sc == nullptr) {
780 debugs(90, 3, "storeUnregister: No matching client for '" << e->getMD5Text() << "'");
781 return 0;
782 }
783
784 if (mem->clientCount() == 0) {
785 debugs(90, 3, "storeUnregister: Consistency failure - store client being unregistered is not in the mem object's list for '" << e->getMD5Text() << "'");
786 return 0;
787 }
788
789 dlinkDelete(&sc->node, &mem->clients);
790 -- mem->nclients;
791
792 const auto swapoutFinished = e->swappedOut() || e->swapoutFailed();
793 if (e->store_status == STORE_OK && !swapoutFinished)
794 e->swapOut();
795
796 if (sc->swapin_sio != nullptr) {
798 sc->swapin_sio = nullptr;
800 }
801
803 debugs(90, 3, "forgetting store_client callback for " << *e);
804 // Do not notify: Callers want to stop copying and forget about this
805 // pending copy request. Some would mishandle a notification from here.
806 if (sc->_callback.notifier)
807 sc->_callback.notifier->cancel("storeUnregister");
808 }
809
810#if STORE_CLIENT_LIST_DEBUG
811 cbdataReferenceDone(sc->owner);
812
813#endif
814
815 // We must lock to safely dereference e below, after deleting sc and after
816 // calling CheckQuickAbortIsReasonable().
817 e->lock("storeUnregister");
818
819 // XXX: We might be inside sc store_client method somewhere up the call
820 // stack. TODO: Convert store_client to AsyncJob to make destruction async.
821 delete sc;
822
824 e->abort();
825 else
826 mem->kickReads();
827
828#if USE_ADAPTATION
829 e->kickProducer();
830#endif
831
832 e->unlock("storeUnregister");
833 return 1;
834}
835
836/* Call handlers waiting for data to be appended to E. */
837void
839{
841 debugs(90, 3, "DELAY_SENDING is on, exiting " << *this);
842 return;
843 }
845 debugs(90, 3, "ENTRY_FWD_HDR_WAIT is on, exiting " << *this);
846 return;
847 }
848
849 /* Commit what we can to disk, if appropriate */
850 swapOut();
851 int i = 0;
852 store_client *sc;
853 dlink_node *nx = nullptr;
855
856 debugs(90, 3, mem_obj->nclients << " clients; " << *this << ' ' << getMD5Text());
857 /* walk the entire list looking for valid callbacks */
858
859 const auto savedContext = CodeContext::Current();
860 for (node = mem_obj->clients.head; node; node = nx) {
861 sc = (store_client *)node->data;
862 nx = node->next;
863 ++i;
864
865 if (!sc->_callback.pending())
866 continue;
867
868 if (sc->flags.disk_io_pending)
869 continue;
870
871 if (sc->flags.store_copying)
872 continue;
873
874 // XXX: If invokeHandlers() is (indirectly) called from a store_client
875 // method, then the above three conditions may not be sufficient to
876 // prevent us from reentering the same store_client object! This
877 // probably does not happen in the current code, but no observed
878 // invariant prevents this from (accidentally) happening in the future.
879
880 // TODO: Convert store_client into AsyncJob; make this call asynchronous
882 debugs(90, 3, "checking client #" << i);
883 storeClientCopy2(this, sc);
884 }
885 CodeContext::Reset(savedContext);
886}
887
888// Does not account for remote readers/clients.
889int
891{
892 MemObject *mem = e->mem_obj;
893 int npend = nullptr == mem ? 0 : mem->nclients;
894 debugs(90, 3, "storePendingNClients: returning " << npend);
895 return npend;
896}
897
898/* return true if the request should be aborted */
899static bool
901{
902 assert(entry);
903 debugs(90, 3, "entry=" << *entry);
904
905 if (storePendingNClients(entry) > 0) {
906 debugs(90, 3, "quick-abort? NO storePendingNClients() > 0");
907 return false;
908 }
909
910 if (Store::Root().transientReaders(*entry)) {
911 debugs(90, 3, "quick-abort? NO still have one or more transient readers");
912 return false;
913 }
914
915 if (entry->store_status != STORE_PENDING) {
916 debugs(90, 3, "quick-abort? NO store_status != STORE_PENDING");
917 return false;
918 }
919
920 if (EBIT_TEST(entry->flags, ENTRY_SPECIAL)) {
921 debugs(90, 3, "quick-abort? NO ENTRY_SPECIAL");
922 return false;
923 }
924
925 if (shutting_down) {
926 debugs(90, 3, "quick-abort? YES avoid heavy optional work during shutdown");
927 return true;
928 }
929
930 MemObject * const mem = entry->mem_obj;
931 assert(mem);
932 debugs(90, 3, "mem=" << mem);
933
934 if (mem->request && !mem->request->flags.cachable) {
935 debugs(90, 3, "quick-abort? YES !mem->request->flags.cachable");
936 return true;
937 }
938
939 if (EBIT_TEST(entry->flags, KEY_PRIVATE)) {
940 debugs(90, 3, "quick-abort? YES KEY_PRIVATE");
941 return true;
942 }
943
944 const auto &reply = mem->baseReply();
945
946 if (reply.hdr_sz <= 0) {
947 // TODO: Check whether this condition works for HTTP/0 responses.
948 debugs(90, 3, "quick-abort? YES no object data received yet");
949 return true;
950 }
951
952 if (Config.quickAbort.min < 0) {
953 debugs(90, 3, "quick-abort? NO disabled");
954 return false;
955 }
956
957 if (mem->request && mem->request->range && mem->request->getRangeOffsetLimit() < 0) {
958 // the admin has configured "range_offset_limit none"
959 debugs(90, 3, "quick-abort? NO admin configured range replies to full-download");
960 return false;
961 }
962
963 if (reply.content_length < 0) {
964 // XXX: cf.data.pre does not document what should happen in this case
965 // We know that quick_abort is enabled, but no limit can be applied.
966 debugs(90, 3, "quick-abort? YES unknown content length");
967 return true;
968 }
969 const auto expectlen = reply.hdr_sz + reply.content_length;
970
971 int64_t curlen = mem->endOffset();
972
973 if (curlen > expectlen) {
974 debugs(90, 3, "quick-abort? YES bad content length (" << curlen << " of " << expectlen << " bytes received)");
975 return true;
976 }
977
978 if ((expectlen - curlen) < (Config.quickAbort.min << 10)) {
979 debugs(90, 3, "quick-abort? NO only a little more object left to receive");
980 return false;
981 }
982
983 if ((expectlen - curlen) > (Config.quickAbort.max << 10)) {
984 debugs(90, 3, "quick-abort? YES too much left to go");
985 return true;
986 }
987
988 if (curlen > expectlen*(Config.quickAbort.pct/100.0)) {
989 debugs(90, 3, "quick-abort? NO past point of no return");
990 return false;
991 }
992
993 debugs(90, 3, "quick-abort? YES default");
994 return true;
995}
996
1000bool
1002{
1003 try {
1004 return tryParsingHttpHeaders();
1005 } catch (...) {
1006 // XXX: Our parser enforces Config.maxReplyHeaderSize limit, but our
1007 // packer does not. Since packing might increase header size, we may
1008 // cache a header that we cannot parse and get here. Same for MemStore.
1009 debugs(90, DBG_CRITICAL, "ERROR: Cannot parse on-disk HTTP headers" <<
1010 Debug::Extra << "exception: " << CurrentException <<
1011 Debug::Extra << "raw input size: " << parsingBuffer->contentSize() << " bytes" <<
1012 Debug::Extra << "current buffer capacity: " << parsingBuffer->capacity() << " bytes");
1013 fail();
1014 return false;
1015 }
1016}
1017
1020bool
1022{
1024 Assure(!copyInto.offset); // otherwise, parsingBuffer cannot have HTTP response headers
1025 auto &adjustableReply = entry->mem().adjustableBaseReply();
1026 if (adjustableReply.parseTerminatedPrefix(parsingBuffer->c_str(), parsingBuffer->contentSize()))
1027 return true;
1028
1029 // TODO: Optimize by checking memory as well. For simplicity sake, we
1030 // continue on the disk-reading path, but readFromMemory() can give us the
1031 // missing header bytes immediately if a concurrent request put those bytes
1032 // into memory while we were waiting for our disk response.
1034 return false;
1035}
1036
1038void
1040{
1041 const auto hdr_sz = entry->mem_obj->baseReply().hdr_sz;
1042 Assure(hdr_sz > 0); // all on-disk responses have HTTP headers
1043 if (Less(parsingBuffer->contentSize(), hdr_sz)) {
1044 debugs(90, 5, "discovered " << hdr_sz << "-byte HTTP headers in memory after reading some of them from disk: " << *parsingBuffer);
1045 parsingBuffer->consume(parsingBuffer->contentSize()); // skip loaded HTTP header prefix
1046 } else {
1047 parsingBuffer->consume(hdr_sz); // skip loaded HTTP headers
1048 const auto httpBodyBytesAfterHeader = parsingBuffer->contentSize(); // may be zero
1049 Assure(httpBodyBytesAfterHeader <= copyInto.length);
1050 debugs(90, 5, "read HTTP body prefix: " << httpBodyBytesAfterHeader);
1051 }
1052}
1053
1054void
1055store_client::dumpStats(MemBuf * output, int clientNumber) const
1056{
1057 if (_callback.pending())
1058 return;
1059
1060 output->appendf("\tClient #%d, %p\n", clientNumber, this);
1061 output->appendf("\t\tcopy_offset: %" PRId64 "\n", copyInto.offset);
1062 output->appendf("\t\tcopy_size: %zu\n", copyInto.length);
1063 output->append("\t\tflags:", 8);
1064
1065 if (flags.disk_io_pending)
1066 output->append(" disk_io_pending", 16);
1067
1068 if (flags.store_copying)
1069 output->append(" store_copying", 14);
1070
1071 if (_callback.notifier)
1072 output->append(" notifying", 10);
1073
1074 output->append("\n",1);
1075}
1076
1077bool
1079{
1080 return callback_handler && !notifier;
1081}
1082
1084 callback_handler(function),
1085 cbData(data),
1086 codeContext(CodeContext::Current())
1087{
1088}
1089
1090#if USE_DELAY_POOLS
1091int
1093{
1094 // TODO: To avoid using stale copyInto, return zero if !_callback.pending()?
1096}
1097
1098void
1100{
1101 delayId = delay_id;
1102}
1103#endif
1104
#define Assure(condition)
Definition Assure.h:35
#define ScheduleCallHere(call)
Definition AsyncCall.h:166
RefCount< AsyncCallT< Dialer > > asyncCall(int aDebugSection, int aDebugLevel, const char *aName, const Dialer &aDialer)
Definition AsyncCall.h:156
UnaryCbdataDialer< Argument1 > cbdataDialer(typename UnaryCbdataDialer< Argument1 >::Handler *handler, Argument1 *arg1)
class SquidConfig Config
constexpr bool Less(const A a, const B b)
whether integer a is less than integer b, with correct overflow handling
Definition SquidMath.h:48
StatCounters statCounter
void(void *, StoreIOBuffer) STCB
Definition StoreClient.h:32
int storeClientIsThisAClient(store_client *sc, void *someClient)
std::ostream & CurrentException(std::ostream &os)
prints active (i.e., thrown but not yet handled) exception
void error(char *format,...)
#define assert(EX)
Definition assert.h:17
#define cbdataReferenceDone(var)
Definition cbdata.h:357
#define cbdataReference(var)
Definition cbdata.h:348
#define CBDATA_CLASS_INIT(type)
Definition cbdata.h:325
Acl::Answer const & fastCheck()
Definition Checklist.cc:298
bool allowed() const
Definition Acl.h:82
virtual void fillChecklist(ACLFilledChecklist &) const =0
configure the given checklist (to reflect the current transaction state)
bool cancel(const char *reason)
Definition AsyncCall.cc:56
void * validDone()
Definition cbdata.h:396
static const Pointer & Current()
static void Reset()
forgets the current context, setting it to nil/unknown
static std::ostream & Extra(std::ostream &)
Definition debug.cc:1316
int bytesWanted(int min, int max) const
Definition DelayId.cc:119
HttpHdrRange * range
int64_t getRangeOffsetLimit()
RequestFlags flags
void append(const char *c, int sz) override
Definition MemBuf.cc:209
StoreIOState::Pointer sio
Definition MemObject.h:162
size_t clientCount() const
Definition MemObject.h:152
int nclients
Definition MemObject.h:156
SwapOut swapout
Definition MemObject.h:169
HttpRequestPointer request
Definition MemObject.h:205
void addClient(store_client *)
Definition MemObject.cc:303
dlink_list clients
Definition MemObject.h:150
mem_hdr data_hdr
Definition MemObject.h:148
void write(const StoreIOBuffer &buf)
Definition MemObject.cc:136
int64_t inmem_lo
Definition MemObject.h:149
size_t swap_hdr_sz
Definition MemObject.h:216
int64_t endOffset() const
Definition MemObject.cc:214
const HttpReply & baseReply() const
Definition MemObject.h:60
HttpReply & adjustableBaseReply()
Definition MemObject.cc:121
void kickReads()
Definition MemObject.cc:459
void appendf(const char *fmt,...) PRINTF_FORMAT_ARG2
Append operation with printf-style arguments.
Definition Packable.h:61
SupportOrVeto cachable
whether the response may be stored in the cache
struct SquidConfig::@76 quickAbort
acl_access * collapsedForwardingAccess
int memory_cache_disk
size_t maxInMemObjSize
struct SquidConfig::@90 onoff
int collapsed_forwarding
int64_t min
Definition SquidConfig.h:94
struct SquidConfig::@91 accessList
int64_t max
Definition SquidConfig.h:96
struct SquidConfig::@88 Store
struct StatCounters::@113 swap
bool onCollapsingPath() const
whether Squid configuration allows collapsing for this transaction
bool didCollapse
whether startCollapsingOn() was called and returned true
Definition StoreClient.h:64
virtual LogTags * loggingTags() const =0
bool startCollapsingOn(const StoreEntry &, const bool doingRevalidation) const
bool swappedOut() const
whether the entire entry is now on disk (possibly marked for deletion)
Definition Store.h:135
uint16_t flags
Definition Store.h:231
void invokeHandlers()
MemObject & mem()
Definition Store.h:47
int unlock(const char *context)
Definition store.cc:469
bool hasDisk(const sdirno dirn=-1, const sfileno filen=-1) const
Definition store.cc:1929
bool hasParsedReplyHeader() const
whether this entry has access to [deserialized] [HTTP] response headers
Definition store.cc:231
void lock(const char *context)
Definition store.cc:445
bool swappingOut() const
whether we are in the process of writing this entry to disk
Definition Store.h:133
const char * getMD5Text() const
Definition store.cc:207
void kickProducer()
calls back producer registered with deferProducer
Definition store.cc:376
MemObject * mem_obj
Definition Store.h:220
void abort()
Definition store.cc:1077
int64_t objectLen() const
Definition Store.h:253
store_status_t store_status
Definition Store.h:243
int64_t contentLen() const
Definition Store.h:254
bool swapoutFailed() const
whether we failed to write this entry to disk
Definition Store.h:137
uint16_t refcount
Definition Store.h:230
bool hittingRequiresCollapsing() const
whether this entry can feed collapsed requests and only them
Definition Store.h:215
StoreIOBuffer & positionAt(const int64_t newOffset)
convenience method for changing the offset of a being-configured buffer
@ readerDone
success or failure: either way, stop swapping in
off_t offset() const
void STRCB(void *their_data, const char *buf, ssize_t len, StoreIOState::Pointer self)
ssize_t copy(StoreIOBuffer const &) const
Definition stmem.cc:187
int getType() const
bool sendingHttpHeaders() const
void readHeader(const char *buf, ssize_t len)
void skipHttpHeadersFromDisk()
skips HTTP header bytes previously loaded from disk
void handleBodyFromDisk()
de-serializes HTTP response (partially) read from disk storage
void setDelayId(DelayId delay_id)
std::optional< Store::ParsingBuffer > parsingBuffer
void scheduleDiskRead()
bool startSwapin()
opens the swapin "file" if possible; otherwise, fail()s and returns false
bool answeredOnce() const
void noteNews()
if necessary and possible, informs the Store reader about copy() result
bool parseHttpHeadersFromDisk()
void readFromMemory()
bool moreToRead() const
Whether Store has (or possibly will have) more entry data for us.
void noteSwapInDone(bool error)
int64_t discardableHttpEnd_
the client will not use HTTP response bytes with lower offsets (if any)
StoreIOBuffer lastDiskRead
buffer used for the last storeRead() call
void readBody(const char *buf, ssize_t len)
DelayId delayId
int64_t nextHttpReadOffset() const
The offset of the next stored HTTP response byte wanted by the client.
StoreEntry * entry
struct store_client::@122 flags
void copy(StoreEntry *, StoreIOBuffer, STCB *, void *)
int bytesWanted() const
bool tryParsingHttpHeaders()
void doCopy(StoreEntry *e)
void maybeWriteFromDiskToMemory(const StoreIOBuffer &)
bool canReadFromMemory() const
whether at least one byte wanted by the client is in memory
struct store_client::Callback _callback
StoreIOBuffer copyInto
void finishCallback()
finishes a copy()-STCB sequence by synchronously calling STCB
bool disk_io_pending
store_client(StoreEntry *)
static void FinishCallback(store_client *)
finishCallback() wrapper; TODO: Add NullaryMemFunT for non-jobs.
dlink_node node
uint64_t answers
the total number of finishCallback() calls
StoreIOState::Pointer swapin_sio
void dumpStats(MemBuf *output, int clientNumber) const
#define DBG_IMPORTANT
Definition Stream.h:38
#define debugs(SECTION, LEVEL, CONTENT)
Definition Stream.h:192
#define DBG_CRITICAL
Definition Stream.h:37
#define SM_PAGE_SIZE
Definition defines.h:63
#define EBIT_TEST(flag, bit)
Definition defines.h:67
@ ENTRY_SPECIAL
Definition enums.h:79
@ KEY_PRIVATE
Definition enums.h:97
@ ENTRY_FWD_HDR_WAIT
Definition enums.h:106
@ DELAY_SENDING
Definition enums.h:92
@ ENTRY_ABORTED
Definition enums.h:110
@ STORE_DISK_CLIENT
Definition enums.h:69
@ STORE_PENDING
Definition enums.h:46
@ STORE_OK
Definition enums.h:45
int shutting_down
Controller & Root()
safely access controller singleton
void UnpackHitSwapMeta(char const *, ssize_t, StoreEntry &)
deserializes entry metadata from the given buffer into the cache hit entry
void storeGetMemSpace(int size)
Definition store.cc:1121
int storeTooManyDiskFilesOpen(void)
Definition store.cc:889
int storeUnregister(store_client *sc, StoreEntry *e, void *data)
void storeClientCopy(store_client *sc, StoreEntry *e, StoreIOBuffer copyInto, STCB *callback, void *data)
int storePendingNClients(const StoreEntry *e)
static void storeClientCopy2(StoreEntry *e, store_client *sc)
store_client * storeClientListAdd(StoreEntry *e, void *data)
static bool CheckQuickAbortIsReasonable(StoreEntry *entry)
static StoreIOState::STRCB storeClientReadBody
static StoreIOState::STRCB storeClientReadHeader
void storeClose(StoreIOState::Pointer sio, int how)
Definition store_io.cc:65
void storeRead(StoreIOState::Pointer sio, char *buf, size_t size, off_t offset, StoreIOState::STRCB *callback, void *callback_data)
Definition store_io.cc:79
void storeSwapInStart(store_client *sc)
Definition parse.c:104
struct node * next
Definition parse.c:105
CodeContextPointer codeContext
Store client context.
CallbackData cbData
the first STCB callback parameter
STCB * callback_handler
where to deliver the answer
AsyncCall::Pointer notifier
a scheduled asynchronous finishCallback() call (or nil)
#define NULL
Definition types.h:145
#define PRId64
Definition types.h:104