store_client.cc
Go to the documentation of this file.
1 /*
2  * Copyright (C) 1996-2023 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"
13 #include "base/AsyncCbdataCalls.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  */
44 static void storeClientCopy2(StoreEntry * e, store_client * sc);
45 static bool CheckQuickAbortIsReasonable(StoreEntry * entry);
46 
48 
49 /* StoreClient */
50 
51 bool
53 {
55  return false;
56 
58  return true;
59 
61  fillChecklist(checklist);
62  return checklist.fastCheck().allowed();
63 }
64 
65 bool
66 StoreClient::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 
90 int
92 {
93  return type;
94 }
95 
96 #if STORE_CLIENT_LIST_DEBUG
97 static store_client *
98 storeClientListSearch(const MemObject * mem, void *data)
99 {
100  dlink_node *node;
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 
113 int
114 storeClientIsThisAClient(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 */
123 store_client *
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 
146 void
148 {
149  sc->finishCallback();
150 }
151 
153 void
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();
185  _callback = Callback();
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 */
223 void
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 
234 void
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  */
288  entry->mem_obj->kickReads();
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 
307 bool
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 
332 static 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 
358 bool
360 {
361  return !answeredOnce() && entry->mem().baseReply().hdr_sz > 0;
362 }
363 
364 void
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()) {
416  readFromMemory();
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 
434 bool
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 
463 void
465 {
467  if (error)
468  fail();
469  else
470  noteNews();
471 }
472 
473 void
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 
496 bool
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 
507 int64_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 
524 void
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 
536 void
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 
585 void
586 store_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 
621  maybeWriteFromDiskToMemory(httpPart);
623 }
624 
626 void
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.
631  Assure(entry->mem_obj);
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 
649 void
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 
673 void
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 
686 void
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 
712 static void
713 storeClientReadHeader(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 
719 static void
720 storeClientReadBody(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 
726 void
727 store_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);
748  Store::UnpackHitSwapMeta(buf, len, *entry);
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  */
764 int
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) {
797  storeClose(sc->swapin_sio, StoreIOState::readerDone);
798  sc->swapin_sio = nullptr;
799  ++statCounter.swap.ins;
800  }
801 
802  if (sc->_callback.callback_handler || sc->_callback.notifier) {
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. */
837 void
839 {
840  if (EBIT_TEST(flags, DELAY_SENDING)) {
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;
854  dlink_node *node;
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
881  CodeContext::Reset(sc->_callback.codeContext);
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.
889 int
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 */
899 static 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 
1000 bool
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 
1020 bool
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.
1033  scheduleDiskRead();
1034  return false;
1035 }
1036 
1038 void
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 
1054 void
1055 store_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 
1077 bool
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
1091 int
1093 {
1094  // TODO: To avoid using stale copyInto, return zero if !_callback.pending()?
1095  return delayId.bytesWanted(0, copyInto.length);
1096 }
1097 
1098 void
1100 {
1101  delayId = delay_id;
1102 }
1103 #endif
1104 
int hdr_sz
Definition: Message.h:81
static void FinishCallback(store_client *)
finishCallback() wrapper; TODO: Add NullaryMemFunT for non-jobs.
Definition: parse.c:104
struct store_client::Callback _callback
HttpReply & adjustableBaseReply()
Definition: MemObject.cc:121
int storeClientIsThisAClient(store_client *sc, void *someClient)
bool tryParsingHttpHeaders()
StoreEntry * entry
Definition: StoreClient.h:113
STCB * callback_handler
where to deliver the answer
Definition: StoreClient.h:210
struct StatCounters::@120 swap
void readFromMemory()
void maybeWriteFromDiskToMemory(const StoreIOBuffer &)
#define SM_PAGE_SIZE
Definition: defines.h:63
void readHeader(const char *buf, ssize_t len)
void appendf(const char *fmt,...) PRINTF_FORMAT_ARG2
Append operation with printf-style arguments.
Definition: Packable.h:61
#define DBG_CRITICAL
Definition: Stream.h:37
void UnpackHitSwapMeta(char const *, ssize_t, StoreEntry &)
deserializes entry metadata from the given buffer into the cache hit entry
Definition: SwapMetaIn.cc:310
void STRCB(void *their_data, const char *buf, ssize_t len, StoreIOState::Pointer self)
Definition: StoreIOState.h:29
void noteSwapInDone(bool error)
struct SquidConfig::@98 accessList
SupportOrVeto cachable
whether the response may be stored in the cache
Definition: RequestFlags.h:35
static StoreIOState::STRCB storeClientReadHeader
Definition: store_client.cc:43
acl_access * collapsedForwardingAccess
Definition: SquidConfig.h:406
MemObject * mem_obj
Definition: Store.h:220
RequestFlags flags
Definition: HttpRequest.h:141
void noteNews()
if necessary and possible, informs the Store reader about copy() result
mem_hdr data_hdr
Definition: MemObject.h:148
struct node * next
Definition: parse.c:105
int64_t getRangeOffsetLimit()
Definition: HttpRequest.cc:594
MemObject & mem()
Definition: Store.h:47
int bytesWanted(int min, int max) const
Definition: DelayId.cc:126
#define ScheduleCallHere(call)
Definition: AsyncCall.h:166
void lock(const char *context)
Definition: store.cc:445
bool canReadFromMemory() const
whether at least one byte wanted by the client is in memory
@ ENTRY_ABORTED
Definition: enums.h:110
void error(char *format,...)
@ KEY_PRIVATE
Definition: enums.h:97
int64_t offset
Definition: StoreIOBuffer.h:58
StoreIOState::Pointer sio
Definition: MemObject.h:162
int64_t max
Definition: SquidConfig.h:96
StoreIOBuffer & positionAt(const int64_t newOffset)
convenience method for changing the offset of a being-configured buffer
Definition: StoreIOBuffer.h:47
Definition: cbdata.cc:37
struct SquidConfig::@97 onoff
uint16_t flags
Definition: Store.h:231
void storeGetMemSpace(int size)
Definition: store.cc:1121
struct SquidConfig::@83 quickAbort
int64_t endOffset() const
Definition: MemObject.cc:214
int64_t objectLen() const
Definition: Store.h:253
#define cbdataReference(var)
Definition: cbdata.h:348
static void storeClientCopy2(StoreEntry *e, store_client *sc)
int64_t inmem_lo
Definition: MemObject.h:149
RefCount< AsyncCallT< Dialer > > asyncCall(int aDebugSection, int aDebugLevel, const char *aName, const Dialer &aDialer)
Definition: AsyncCall.h:156
AsyncCall::Pointer notifier
a scheduled asynchronous finishCallback() call (or nil)
Definition: StoreClient.h:215
@ readerDone
success or failure: either way, stop swapping in
Definition: StoreIOState.h:60
void invokeHandlers()
static void Reset()
forgets the current context, setting it to nil/unknown
Definition: CodeContext.cc:77
void skipHttpHeadersFromDisk()
skips HTTP header bytes previously loaded from disk
UnaryCbdataDialer< Argument1 > cbdataDialer(typename UnaryCbdataDialer< Argument1 >::Handler *handler, Argument1 *arg1)
int64_t nextHttpReadOffset() const
The offset of the next stored HTTP response byte wanted by the client.
void scheduleDiskRead()
void storeRead(StoreIOState::Pointer sio, char *buf, size_t size, off_t offset, StoreIOState::STRCB *callback, void *callback_data)
Definition: store_io.cc:79
int memory_cache_disk
Definition: SquidConfig.h:336
void append(const char *c, int sz) override
Definition: MemBuf.cc:209
#define NULL
Definition: types.h:145
const HttpReply & baseReply() const
Definition: MemObject.h:60
@ ENTRY_FWD_HDR_WAIT
Definition: enums.h:106
void abort()
Definition: store.cc:1077
store_client(StoreEntry *)
const Acl::Answer & fastCheck()
Definition: Checklist.cc:298
uint16_t refcount
Definition: Store.h:230
bool hasDisk(const sdirno dirn=-1, const sfileno filen=-1) const
Definition: store.cc:1929
@ STORE_DISK_CLIENT
Definition: enums.h:69
Definition: MemBuf.h:23
#define EBIT_TEST(flag, bit)
Definition: defines.h:67
int collapsed_forwarding
Definition: SquidConfig.h:323
int64_t contentLen() const
Definition: Store.h:254
bool onCollapsingPath() const
whether Squid configuration allows collapsing for this transaction
Definition: store_client.cc:52
void write(const StoreIOBuffer &buf)
Definition: MemObject.cc:136
void readBody(const char *buf, ssize_t len)
int unlock(const char *context)
Definition: store.cc:469
virtual LogTags * loggingTags() const =0
off_t offset() const
Definition: StoreIOState.h:48
struct SquidConfig::@95 Store
store_status_t store_status
Definition: Store.h:243
#define assert(EX)
Definition: assert.h:17
StoreIOBuffer lastDiskRead
buffer used for the last storeRead() call
Definition: StoreClient.h:194
bool swapoutFailed() const
whether we failed to write this entry to disk
Definition: Store.h:137
std::ostream & CurrentException(std::ostream &os)
prints active (i.e., thrown but not yet handled) exception
bool parseHttpHeadersFromDisk()
#define cbdataReferenceDone(var)
Definition: cbdata.h:357
#define Assure(condition)
Definition: Assure.h:35
HttpHdrRange * range
Definition: HttpRequest.h:143
int bytesWanted() const
static int sc[16]
Definition: smbdes.c:121
virtual void fillChecklist(ACLFilledChecklist &) const =0
configure the given checklist (to reflect the current transaction state)
#define CBDATA_CLASS_INIT(type)
Definition: cbdata.h:325
@ STORE_OK
Definition: enums.h:45
size_t maxInMemObjSize
Definition: SquidConfig.h:268
void swapOut()
static std::ostream & Extra(std::ostream &)
Definition: debug.cc:1316
static bool CheckQuickAbortIsReasonable(StoreEntry *entry)
bool startCollapsingOn(const StoreEntry &, const bool doingRevalidation) const
Definition: store_client.cc:66
DelayId delayId
Definition: StoreClient.h:128
StoreIOBuffer copyInto
Definition: StoreClient.h:179
int64_t discardableHttpEnd_
the client will not use HTTP response bytes with lower offsets (if any)
Definition: StoreClient.h:183
bool didCollapse
whether startCollapsingOn() was called and returned true
Definition: StoreClient.h:64
StoreIOState::Pointer swapin_sio
Definition: StoreClient.h:114
int getType() const
Definition: store_client.cc:91
ssize_t copy(StoreIOBuffer const &) const
Definition: stmem.cc:187
bool sendingHttpHeaders() const
std::optional< Store::ParsingBuffer > parsingBuffer
Definition: StoreClient.h:192
bool allowed() const
Definition: Acl.h:82
void kickProducer()
calls back producer registered with deferProducer
Definition: store.cc:376
static const Pointer & Current()
Definition: CodeContext.cc:33
int storeTooManyDiskFilesOpen(void)
Definition: store.cc:889
void storeClose(StoreIOState::Pointer sio, int how)
Definition: store_io.cc:65
void dumpStats(MemBuf *output, int clientNumber) const
struct store_client::@129 flags
size_t clientCount() const
Definition: MemObject.h:152
void addClient(store_client *)
Definition: MemObject.cc:303
@ ENTRY_SPECIAL
Definition: enums.h:79
#define DBG_IMPORTANT
Definition: Stream.h:38
bool answeredOnce() const
Definition: StoreClient.h:144
static StoreIOState::STRCB storeClientReadBody
Definition: store_client.cc:42
int64_t min
Definition: SquidConfig.h:94
void copy(StoreEntry *, StoreIOBuffer, STCB *, void *)
#define PRId64
Definition: types.h:104
int shutting_down
bool moreToRead() const
Whether Store has (or possibly will have) more entry data for us.
void kickReads()
Definition: MemObject.cc:459
bool startSwapin()
opens the swapin "file" if possible; otherwise, fail()s and returns false
void storeSwapInStart(store_client *sc)
Definition: store_swapin.cc:21
bool hittingRequiresCollapsing() const
whether this entry can feed collapsed requests and only them
Definition: Store.h:215
CallbackData cbData
the first STCB callback parameter
Definition: StoreClient.h:211
bool hasParsedReplyHeader() const
whether this entry has access to [deserialized] [HTTP] response headers
Definition: store.cc:231
void * validDone()
Definition: cbdata.h:396
void(void *, StoreIOBuffer) STCB
Definition: StoreClient.h:32
bool swappingOut() const
whether we are in the process of writing this entry to disk
Definition: Store.h:133
void setDelayId(DelayId delay_id)
bool swappedOut() const
whether the entire entry is now on disk (possibly marked for deletion)
Definition: Store.h:135
void doCopy(StoreEntry *e)
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
void finishCallback()
finishes a copy()-STCB sequence by synchronously calling STCB
int storeUnregister(store_client *sc, StoreEntry *e, void *data)
void storeClientCopy(store_client *sc, StoreEntry *e, StoreIOBuffer copyInto, STCB *callback, void *data)
void handleBodyFromDisk()
de-serializes HTTP response (partially) read from disk storage
#define debugs(SECTION, LEVEL, CONTENT)
Definition: Stream.h:192
const A & min(A const &lhs, A const &rhs)
HttpRequestPointer request
Definition: MemObject.h:205
uint64_t answers
the total number of finishCallback() calls
Definition: StoreClient.h:186
size_t swap_hdr_sz
Definition: MemObject.h:216
int nclients
Definition: MemObject.h:156
const char * getMD5Text() const
Definition: store.cc:207
@ DELAY_SENDING
Definition: enums.h:92
class SquidConfig Config
Definition: SquidConfig.cc:12
store_client * storeClientListAdd(StoreEntry *e, void *data)
int storePendingNClients(const StoreEntry *e)
dlink_list clients
Definition: MemObject.h:150
StatCounters statCounter
Definition: StatCounters.cc:12
@ STORE_PENDING
Definition: enums.h:46
SwapOut swapout
Definition: MemObject.h:169
Controller & Root()
safely access controller singleton
Definition: Controller.cc:926

 

Introduction

Documentation

Support

Miscellaneous