/*
- * Copyright (C) 1996-2019 The Squid Software Foundation and contributors
+ * Copyright (C) 1996-2021 The Squid Software Foundation and contributors
*
* Squid software is distributed under GPLv2+ license and includes
* contributions from numerous individuals and organizations.
/* DEBUG: section 20 Storage Manager */
#include "squid.h"
+#include "base/AsyncCbdataCalls.h"
+#include "base/PackableStream.h"
#include "base/TextException.h"
#include "CacheDigest.h"
#include "CacheManager.h"
+#include "CollapsedForwarding.h"
#include "comm/Connection.h"
#include "comm/Read.h"
+#if HAVE_DISKIO_MODULE_IPCIO
+#include "DiskIO/IpcIo/IpcIoFile.h"
+#endif
#include "ETag.h"
#include "event.h"
#include "fde.h"
#define STORE_IN_MEM_BUCKETS (229)
-/** \todo Convert these string constants to enum string-arrays generated */
+// TODO: Convert these string constants to enum string-arrays generated
const char *memStatusStr[] = {
"NOT_IN_MEMORY",
Root().stat(*output);
}
+/// reports the current state of Store-related queues
+static void
+StatQueues(StoreEntry *e)
+{
+ assert(e);
+ PackableStream stream(*e);
+ CollapsedForwarding::StatQueue(stream);
+#if HAVE_DISKIO_MODULE_IPCIO
+ stream << "\n";
+ IpcIoFile::StatQueue(stream);
+#endif
+ stream.flush();
+}
+
// XXX: new/delete operators need to be replaced with MEMPROXY_CLASS
// definitions but doing so exposes bug 4370, and maybe 4354 and 4355
void *
if (hasMemStore() && !shutting_down)
Store::Root().memoryDisconnect(*this);
- if (MemObject *mem = mem_obj) {
+ if (auto memObj = mem_obj) {
setMemStatus(NOT_IN_MEMORY);
mem_obj = NULL;
- delete mem;
+ delete memObj;
}
}
Store::Root().handleIdleEntry(*this); // may delete us
}
-void
-StoreEntry::getPublicByRequestMethod (StoreClient *aClient, HttpRequest * request, const HttpRequestMethod& method)
-{
- assert (aClient);
- aClient->created(storeGetPublicByRequestMethod(request, method));
-}
-
-void
-StoreEntry::getPublicByRequest (StoreClient *aClient, HttpRequest * request)
-{
- assert (aClient);
- aClient->created(storeGetPublicByRequest(request));
-}
-
-void
-StoreEntry::getPublic (StoreClient *aClient, const char *uri, const HttpRequestMethod& method)
-{
- assert (aClient);
- aClient->created(storeGetPublic(uri, method));
-}
-
StoreEntry *
storeGetPublic(const char *uri, const HttpRequestMethod& method)
{
return nullptr;
HttpRequestPointer request(mem_obj->request);
+ const auto &reply = mem_obj->freshestReply();
if (mem_obj->vary_headers.isEmpty()) {
/* First handle the case where the object no longer varies */
/* Make sure the request knows the variance status */
if (request->vary_headers.isEmpty())
- request->vary_headers = httpMakeVaryMark(request.getRaw(), mem_obj->getReply().getRaw());
+ request->vary_headers = httpMakeVaryMark(request.getRaw(), &reply);
}
// TODO: storeGetPublic() calls below may create unlocked entries.
throw TexcHere("failed to make Vary marker public");
}
/* We are allowed to do this typecast */
- HttpReply *rep = new HttpReply;
+ const HttpReplyPointer rep(new HttpReply);
rep->setHeaders(Http::scOkay, "Internal marker object", "x-squid-internal/vary", -1, -1, squid_curtime + 100000);
- String vary = mem_obj->getReply()->header.getList(Http::HdrType::VARY);
+ auto vary = reply.header.getList(Http::HdrType::VARY);
if (vary.size()) {
/* Again, we own this structure layout */
}
#if X_ACCELERATOR_VARY
- vary = mem_obj->getReply()->header.getList(Http::HdrType::HDR_X_ACCELERATOR_VARY);
+ vary = reply.header.getList(Http::HdrType::HDR_X_ACCELERATOR_VARY);
if (vary.size() > 0) {
/* Again, we own this structure layout */
assert(store_status == STORE_PENDING);
// XXX: caller uses content offset, but we also store headers
- if (const HttpReplyPointer reply = mem_obj->getReply())
- writeBuffer.offset += reply->hdr_sz;
+ writeBuffer.offset += mem_obj->baseReply().hdr_sz;
debugs(20, 5, "storeWrite: writing " << writeBuffer.length << " bytes for '" << getMD5Text() << "'");
PROF_stop(StoreEntry_write);
* XXX sigh, offset might be < 0 here, but it gets "corrected"
* later. This offset crap is such a mess.
*/
- tempBuffer.offset = mem_obj->endOffset() - (getReply() ? getReply()->hdr_sz : 0);
+ tempBuffer.offset = mem_obj->endOffset() - mem_obj->baseReply().hdr_sz;
write(tempBuffer);
}
va_list ap;
/* Fix of bug 753r. The value of vargs is undefined
* after vsnprintf() returns. Make a copy of vargs
- * incase we loop around and call vsnprintf() again.
+ * in case we loop around and call vsnprintf() again.
*/
va_copy(ap,vargs);
errno = 0;
int non_get;
int not_entry_cachable;
int wrong_content_length;
- int negative_cached;
int too_big;
int too_small;
int private_key;
if (mem_obj->object_sz >= 0 &&
mem_obj->object_sz < Config.Store.minObjectSize)
return 1;
- if (getReply()->content_length > -1)
- if (getReply()->content_length < Config.Store.minObjectSize)
- return 1;
+
+ const auto clen = mem().baseReply().content_length;
+ if (clen >= 0 && clen < Config.Store.minObjectSize)
+ return 1;
return 0;
}
if (mem_obj->endOffset() > store_maxobjsize)
return true;
- if (getReply()->content_length < 0)
- return false;
-
- return (getReply()->content_length > store_maxobjsize);
+ const auto clen = mem_obj->baseReply().content_length;
+ return (clen >= 0 && clen > store_maxobjsize);
}
// TODO: move "too many open..." checks outside -- we are called too early/late
if (store_status == STORE_OK && EBIT_TEST(flags, ENTRY_BAD_LENGTH)) {
debugs(20, 2, "StoreEntry::checkCachable: NO: wrong content-length");
++store_check_cachable_hist.no.wrong_content_length;
- } else if (EBIT_TEST(flags, ENTRY_NEGCACHED)) {
- debugs(20, 3, "StoreEntry::checkCachable: NO: negative cached");
- ++store_check_cachable_hist.no.negative_cached;
- return 0; /* avoid release call below */
- } else if (!mem_obj || !getReply()) {
+ } else if (!mem_obj) {
// XXX: In bug 4131, we forgetHit() without mem_obj, so we need
// this segfault protection, but how can we get such a HIT?
debugs(20, 2, "StoreEntry::checkCachable: NO: missing parts: " << *this);
storeAppendPrintf(sentry, "no.wrong_content_length\t%d\n",
store_check_cachable_hist.no.wrong_content_length);
storeAppendPrintf(sentry, "no.negative_cached\t%d\n",
- store_check_cachable_hist.no.negative_cached);
+ 0); // TODO: Remove this backward compatibility hack.
storeAppendPrintf(sentry, "no.missing_parts\t%d\n",
store_check_cachable_hist.no.missing_parts);
storeAppendPrintf(sentry, "no.too_big\t%d\n",
return;
}
- /* This is suspect: mem obj offsets include the headers. do we adjust for that
- * in use of object_sz?
- */
mem_obj->object_sz = mem_obj->endOffset();
store_status = STORE_OK;
/* Notify the server side */
- /*
- * DPW 2007-05-07
- * Should we check abort.data for validity?
- */
- if (mem_obj->abort.callback) {
- if (!cbdataReferenceValid(mem_obj->abort.data))
- debugs(20, DBG_IMPORTANT,HERE << "queueing event when abort.data is not valid");
- eventAdd("mem_obj->abort.callback",
- mem_obj->abort.callback,
- mem_obj->abort.data,
- 0.0,
- true);
- unregisterAbort();
+ if (mem_obj->abortCallback) {
+ ScheduleCallHere(mem_obj->abortCallback);
+ mem_obj->abortCallback = nullptr;
}
/* XXX Should we reverse these two, so that there is no
eventAdd("storeLateRelease", storeLateRelease, NULL, 0.0, 1);
}
-/* return 1 if a store entry is locked */
-int
-StoreEntry::locked() const
-{
- if (lock_count)
- return 1;
-
- /*
- * SPECIAL, PUBLIC entries should be "locked";
- * XXX: Their owner should lock them then instead of relying on this hack.
- */
- if (EBIT_TEST(flags, ENTRY_SPECIAL))
- if (!EBIT_TEST(flags, KEY_PRIVATE))
- return 1;
-
- return 0;
-}
-
+/// whether the base response has all the body bytes we expect
+/// \returns true for responses with unknown/unspecified body length
+/// \returns true for responses with the right number of accumulated body bytes
bool
StoreEntry::validLength() const
{
int64_t diff;
- const HttpReply *reply;
assert(mem_obj != NULL);
- reply = getReply();
+ const auto reply = &mem_obj->baseReply();
debugs(20, 3, "storeEntryValidLength: Checking '" << getMD5Text() << "'");
debugs(20, 5, "storeEntryValidLength: object_len = " <<
objectLen());
Mgr::RegisterAction("store_io", "Store IO Interface Stats", &Mgr::StoreIoAction::Create, 0, 1);
Mgr::RegisterAction("store_check_cachable_stats", "storeCheckCachable() Stats",
storeCheckCachableStats, 0, 1);
+ Mgr::RegisterAction("store_queues", "SMP Transients and Caching Queues", StatQueues, 0, 1);
}
void
void
storeConfigure(void)
{
- Store::Root().updateLimits();
+ Store::Root().configure();
}
bool
#else
expires = squid_curtime;
#endif
- EBIT_SET(flags, ENTRY_NEGCACHED);
+ if (expires > squid_curtime) {
+ EBIT_SET(flags, ENTRY_NEGCACHED);
+ debugs(20, 6, "expires = " << expires << " +" << (expires-squid_curtime) << ' ' << *this);
+ }
}
void
bool
StoreEntry::timestampsSet()
{
- const HttpReply *reply = getReply();
+ debugs(20, 7, *this << " had " << describeTimestamps());
+
+ // TODO: Remove change-reducing "&" before the official commit.
+ const auto reply = &mem().freshestReply();
+
time_t served_date = reply->date;
int age = reply->header.getInt(Http::HdrType::AGE);
/* Compute the timestamp, mimicking RFC2616 section 13.2.3. */
timestamp = served_date;
+ debugs(20, 5, *this << " has " << describeTimestamps());
+ return true;
+}
+
+bool
+StoreEntry::updateOnNotModified(const StoreEntry &e304)
+{
+ assert(mem_obj);
+ assert(e304.mem_obj);
+
+ // update reply before calling timestampsSet() below
+ const auto &oldReply = mem_obj->freshestReply();
+ const auto updatedReply = oldReply.recreateOnNotModified(e304.mem_obj->baseReply());
+ if (updatedReply) // HTTP 304 brought in new information
+ mem_obj->updateReply(*updatedReply);
+ // else continue to use the previous update, if any
+
+ if (!timestampsSet() && !updatedReply)
+ return false;
+
+ // Keep the old mem_obj->vary_headers; see HttpHeader::skipUpdateHeader().
+
+ debugs(20, 5, "updated basics in " << *this << " with " << e304);
+ mem_obj->appliedUpdates = true; // helps in triage; may already be true
return true;
}
void
-StoreEntry::registerAbort(STABH * cb, void *data)
+StoreEntry::registerAbortCallback(const AsyncCall::Pointer &handler)
{
assert(mem_obj);
- assert(mem_obj->abort.callback == NULL);
- mem_obj->abort.callback = cb;
- mem_obj->abort.data = cbdataReference(data);
+ assert(!mem_obj->abortCallback);
+ mem_obj->abortCallback = handler;
}
void
-StoreEntry::unregisterAbort()
+StoreEntry::unregisterAbortCallback(const char *reason)
{
assert(mem_obj);
- if (mem_obj->abort.callback) {
- mem_obj->abort.callback = NULL;
- cbdataReferenceDone(mem_obj->abort.data);
+ if (mem_obj->abortCallback) {
+ mem_obj->abortCallback->cancel(reason);
+ mem_obj->abortCallback = nullptr;
}
}
}
}
-int64_t
-StoreEntry::objectLen() const
-{
- assert(mem_obj != NULL);
- return mem_obj->object_sz;
-}
-
-int64_t
-StoreEntry::contentLen() const
-{
- assert(mem_obj != NULL);
- assert(getReply() != NULL);
- return objectLen() - getReply()->hdr_sz;
-}
-
-HttpReply const *
-StoreEntry::getReply() const
-{
- return (mem_obj ? mem_obj->getReply().getRaw() : nullptr);
-}
-
void
StoreEntry::reset()
{
- assert (mem_obj);
debugs(20, 3, url());
- mem_obj->reset();
+ mem().reset();
expires = lastModified_ = timestamp = -1;
}
{
lock("StoreEntry::storeErrorResponse");
buffer();
- replaceHttpReply(reply);
+ replaceHttpReply(HttpReplyPointer(reply));
flush();
complete();
negativeCache();
* a new reply. This eats the reply.
*/
void
-StoreEntry::replaceHttpReply(HttpReply *rep, bool andStartWriting)
+StoreEntry::replaceHttpReply(const HttpReplyPointer &rep, const bool andStartWriting)
{
debugs(20, 3, "StoreEntry::replaceHttpReply: " << url());
return;
}
- mem_obj->replaceReply(HttpReplyPointer(rep));
+ mem_obj->replaceBaseReply(rep);
if (andStartWriting)
startWriting();
assert (isEmpty());
assert(mem_obj);
- const HttpReply *rep = getReply();
- assert(rep);
+ // Per MemObject replies definitions, we can only write our base reply.
+ // Currently, all callers replaceHttpReply() first, so there is no updated
+ // reply here anyway. Eventually, we may need to support the
+ // updateOnNotModified(),startWriting() sequence as well.
+ assert(!mem_obj->updatedReply());
+ const auto rep = &mem_obj->baseReply();
buffer();
rep->packHeadersUsingSlowPacker(*this);
}
char const *
-StoreEntry::getSerialisedMetaData()
+StoreEntry::getSerialisedMetaData(size_t &length) const
{
StoreMeta *tlv_list = storeSwapMetaBuild(this);
int swap_hdr_sz;
char *result = storeSwapMetaPack(tlv_list, &swap_hdr_sz);
storeSwapTLVFree(tlv_list);
assert (swap_hdr_sz >= 0);
- mem_obj->swap_hdr_sz = (size_t) swap_hdr_sz;
+ length = static_cast<size_t>(swap_hdr_sz);
return result;
}
bool
StoreEntry::modifiedSince(const time_t ims, const int imslen) const
{
- int object_length;
const time_t mod_time = lastModified();
debugs(88, 3, "modifiedSince: '" << url() << "'");
if (mod_time < 0)
return true;
- /* Find size of the object */
- object_length = getReply()->content_length;
-
- if (object_length < 0)
- object_length = contentLen();
+ assert(imslen < 0); // TODO: Either remove imslen or support it properly.
if (mod_time > ims) {
debugs(88, 3, "--> YES: entry newer than client");
} else if (mod_time < ims) {
debugs(88, 3, "--> NO: entry older than client");
return false;
- } else if (imslen < 0) {
- debugs(88, 3, "--> NO: same LMT, no client length");
- return false;
- } else if (imslen == object_length) {
- debugs(88, 3, "--> NO: same LMT, same length");
- return false;
} else {
- debugs(88, 3, "--> YES: same LMT, different length");
- return true;
+ debugs(88, 3, "--> NO: same LMT");
+ return false;
}
}
bool
StoreEntry::hasEtag(ETag &etag) const
{
- if (const HttpReply *reply = getReply()) {
+ if (const auto reply = hasFreshestReply()) {
etag = reply->header.getETag(Http::HdrType::ETAG);
if (etag.str)
return true;
bool
StoreEntry::hasOneOfEtags(const String &reqETags, const bool allowWeakMatch) const
{
- const ETag repETag = getReply()->header.getETag(Http::HdrType::ETAG);
+ const auto repETag = mem().freshestReply().header.getETag(Http::HdrType::ETAG);
if (!repETag.str) {
static SBuf asterisk("*", 1);
return strListIsMember(&reqETags, asterisk, ',');