/* * Copyright (C) 1996-2023 The Squid Software Foundation and contributors * * Squid software is distributed under GPLv2+ license and includes * contributions from numerous individuals and organizations. * Please see the COPYING and CONTRIBUTORS files for details. */ /* DEBUG: section 19 Store Memory Primitives */ #include "squid.h" #include "comm/Connection.h" #include "Generic.h" #include "globals.h" #include "HttpReply.h" #include "MemBuf.h" #include "MemObject.h" #include "SquidConfig.h" #include "Store.h" #include "StoreClient.h" #if USE_DELAY_POOLS #include "DelayPools.h" #endif /* TODO: make this global or private */ #if URL_CHECKSUM_DEBUG static unsigned int url_checksum(const char *url); unsigned int url_checksum(const char *url) { unsigned int ck; SquidMD5_CTX M; static unsigned char digest[16]; SquidMD5Init(&M); SquidMD5Update(&M, (unsigned char *) url, strlen(url)); SquidMD5Final(digest, &M); memcpy(&ck, digest, sizeof(ck)); return ck; } #endif RemovalPolicy * mem_policy = nullptr; size_t MemObject::inUseCount() { return Pool().inUseCount(); } const char * MemObject::storeId() const { if (storeId_.isEmpty()) { debugs(20, DBG_IMPORTANT, "ERROR: Squid BUG: Missing MemObject::storeId value"); dump(); storeId_ = "[unknown_URI]"; } return storeId_.c_str(); } const char * MemObject::logUri() const { return logUri_.isEmpty() ? storeId() : logUri_.c_str(); } bool MemObject::hasUris() const { return !storeId_.isEmpty(); } void MemObject::setUris(char const *aStoreId, char const *aLogUri, const HttpRequestMethod &aMethod) { if (hasUris()) return; storeId_ = aStoreId; debugs(88, 3, this << " storeId: " << storeId_); // fast pointer comparison for a common storeCreateEntry(url,url,...) case if (!aLogUri || aLogUri == aStoreId) logUri_.clear(); // use storeId_ by default to minimize copying else logUri_ = aLogUri; method = aMethod; #if URL_CHECKSUM_DEBUG chksum = url_checksum(urlXXX()); #endif } MemObject::MemObject() { debugs(20, 3, "MemObject constructed, this=" << this); ping_reply_callback = nullptr; memset(&start_ping, 0, sizeof(start_ping)); reply_ = new HttpReply; } MemObject::~MemObject() { debugs(20, 3, "MemObject destructed, this=" << this); #if URL_CHECKSUM_DEBUG checkUrlChecksum(); #endif assert(xitTable.index < 0); assert(memCache.index < 0); assert(swapout.sio == nullptr); data_hdr.freeContent(); } HttpReply & MemObject::adjustableBaseReply() { assert(!updatedReply_); return *reply_; } void MemObject::replaceBaseReply(const HttpReplyPointer &r) { assert(r); reply_ = r; updatedReply_ = nullptr; } void MemObject::write(const StoreIOBuffer &writeBuffer) { debugs(19, 6, "memWrite: offset " << writeBuffer.offset << " len " << writeBuffer.length); /* We don't separate out mime headers yet, so ensure that the first * write is at offset 0 - where they start */ assert (data_hdr.endOffset() || writeBuffer.offset == 0); assert (data_hdr.write (writeBuffer)); } void MemObject::dump() const { data_hdr.dump(); debugs(20, DBG_IMPORTANT, "MemObject->start_ping: " << start_ping); debugs(20, DBG_IMPORTANT, "MemObject->inmem_hi: " << data_hdr.endOffset()); debugs(20, DBG_IMPORTANT, "MemObject->inmem_lo: " << inmem_lo); debugs(20, DBG_IMPORTANT, "MemObject->nclients: " << nclients); debugs(20, DBG_IMPORTANT, "MemObject->reply: " << reply_); debugs(20, DBG_IMPORTANT, "MemObject->updatedReply: " << updatedReply_); debugs(20, DBG_IMPORTANT, "MemObject->appliedUpdates: " << appliedUpdates); debugs(20, DBG_IMPORTANT, "MemObject->request: " << request); debugs(20, DBG_IMPORTANT, "MemObject->logUri: " << logUri_); debugs(20, DBG_IMPORTANT, "MemObject->storeId: " << storeId_); } struct LowestMemReader : public unary_function { LowestMemReader(int64_t seed):current(seed) {} void operator() (store_client const &x) { if (x.getType() == STORE_MEM_CLIENT) current = std::min(current, x.discardableHttpEnd()); } int64_t current; }; struct StoreClientStats : public unary_function { StoreClientStats(MemBuf *anEntry):where(anEntry),index(0) {} void operator()(store_client const &x) { x.dumpStats(where, index); ++index; } MemBuf *where; size_t index; }; void MemObject::stat(MemBuf * mb) const { mb->appendf("\t" SQUIDSBUFPH " %s\n", SQUIDSBUFPRINT(method.image()), logUri()); if (!vary_headers.isEmpty()) mb->appendf("\tvary_headers: " SQUIDSBUFPH "\n", SQUIDSBUFPRINT(vary_headers)); mb->appendf("\tinmem_lo: %" PRId64 "\n", inmem_lo); mb->appendf("\tinmem_hi: %" PRId64 "\n", data_hdr.endOffset()); mb->appendf("\tswapout: %" PRId64 " bytes queued\n", swapout.queue_offset); if (swapout.sio.getRaw()) mb->appendf("\tswapout: %" PRId64 " bytes written\n", (int64_t) swapout.sio->offset()); if (xitTable.index >= 0) mb->appendf("\ttransient index: %d state: %d\n", xitTable.index, xitTable.io); if (memCache.index >= 0) mb->appendf("\tmem-cache index: %d state: %d offset: %" PRId64 "\n", memCache.index, memCache.io, memCache.offset); if (object_sz >= 0) mb->appendf("\tobject_sz: %" PRId64 "\n", object_sz); StoreClientStats statsVisitor(mb); for_each(clients, statsVisitor); } int64_t MemObject::endOffset () const { return data_hdr.endOffset(); } void MemObject::markEndOfReplyHeaders() { const int hdr_sz = endOffset(); assert(hdr_sz >= 0); assert(reply_); reply_->hdr_sz = hdr_sz; } int64_t MemObject::size() const { if (object_sz < 0) return endOffset(); return object_sz; } int64_t MemObject::expectedReplySize() const { if (object_sz >= 0) { debugs(20, 7, object_sz << " frozen by complete()"); return object_sz; } const auto hdr_sz = baseReply().hdr_sz; // Cannot predict future length using an empty/unset or HTTP/0 reply. // For any HTTP/1 reply, hdr_sz is positive -- status-line cannot be empty. if (hdr_sz <= 0) return -1; const auto clen = baseReply().bodySize(method); if (clen < 0) { debugs(20, 7, "unknown; hdr: " << hdr_sz); return -1; } const auto messageSize = clen + hdr_sz; debugs(20, 7, messageSize << " hdr: " << hdr_sz << " clen: " << clen); return messageSize; } void MemObject::reset() { assert(swapout.sio == nullptr); data_hdr.freeContent(); inmem_lo = 0; /* Should we check for clients? */ assert(reply_); reply_->reset(); updatedReply_ = nullptr; appliedUpdates = false; } int64_t MemObject::lowestMemReaderOffset() const { LowestMemReader lowest (endOffset() + 1); for_each (clients, lowest); return lowest.current; } /* XXX: This is wrong. It breaks *badly* on range combining */ bool MemObject::readAheadPolicyCanRead() const { const auto savedHttpHeaders = baseReply().hdr_sz; const bool canRead = endOffset() - savedHttpHeaders < lowestMemReaderOffset() + Config.readAheadGap; if (!canRead) { debugs(19, 5, "no: " << endOffset() << '-' << savedHttpHeaders << " < " << lowestMemReaderOffset() << '+' << Config.readAheadGap); } return canRead; } void MemObject::addClient(store_client *aClient) { ++nclients; dlinkAdd(aClient, &aClient->node, &clients); } #if URL_CHECKSUM_DEBUG void MemObject::checkUrlChecksum () const { assert(chksum == url_checksum(urlXXX())); } #endif /* * How much of the object data is on the disk? */ int64_t MemObject::objectBytesOnDisk() const { /* * NOTE: storeOffset() represents the disk file size, * not the amount of object data on disk. * * If we don't have at least 'swap_hdr_sz' bytes * then none of the object data is on disk. * * This should still be safe if swap_hdr_sz == 0, * meaning we haven't even opened the swapout file * yet. */ if (swapout.sio.getRaw() == nullptr) return 0; int64_t nwritten = swapout.sio->offset(); if (nwritten <= (int64_t)swap_hdr_sz) return 0; return (nwritten - swap_hdr_sz); } int64_t MemObject::policyLowestOffsetToKeep(bool swap) const { /* * Careful. lowest_offset can be greater than endOffset(), such * as in the case of a range request. */ int64_t lowest_offset = lowestMemReaderOffset(); // XXX: Remove the last (Config.onoff.memory_cache_first-based) condition // and update keepForLocalMemoryCache() accordingly. The caller wants to // remove all local memory that is safe to remove. Honoring caching // preferences is its responsibility. Our responsibility is safety. The // situation was different when ff4b33f added that condition -- there was no // keepInLocalMemory/keepForLocalMemoryCache() call guard back then. if (endOffset() < lowest_offset || endOffset() - inmem_lo > (int64_t)Config.Store.maxInMemObjSize || (swap && !Config.onoff.memory_cache_first)) return lowest_offset; return inmem_lo; } void MemObject::trimSwappable() { int64_t new_mem_lo = policyLowestOffsetToKeep(1); /* * We should only free up to what we know has been written * to disk, not what has been queued for writing. Otherwise * there will be a chunk of the data which is not in memory * and is not yet on disk. * The -1 makes sure the page isn't freed until storeSwapOut has * walked to the next page. */ int64_t on_disk; if ((on_disk = objectBytesOnDisk()) - 1 < new_mem_lo) new_mem_lo = on_disk - 1; if (new_mem_lo == -1) new_mem_lo = 0; /* the above might become -1 */ data_hdr.freeDataUpto(new_mem_lo); inmem_lo = new_mem_lo; } void MemObject::trimUnSwappable() { if (const int64_t new_mem_lo = policyLowestOffsetToKeep(false)) { assert (new_mem_lo > 0); data_hdr.freeDataUpto(new_mem_lo); inmem_lo = new_mem_lo; } // else we should not trim anything at this time } bool MemObject::isContiguous() const { bool result = data_hdr.hasContigousContentRange (Range(inmem_lo, endOffset())); /* XXX : make this higher level */ debugs (19, result ? 4 :3, "MemObject::isContiguous: Returning " << (result ? "true" : "false")); return result; } int MemObject::mostBytesWanted(int max, bool ignoreDelayPools) const { #if USE_DELAY_POOLS if (!ignoreDelayPools) { /* identify delay id with largest allowance */ DelayId largestAllowance = mostBytesAllowed (); return largestAllowance.bytesWanted(0, max); } #else (void)ignoreDelayPools; #endif return max; } void MemObject::setNoDelay(bool const newValue) { #if USE_DELAY_POOLS for (dlink_node *node = clients.head; node; node = node->next) { store_client *sc = (store_client *) node->data; sc->delayId.setNoDelay(newValue); } #else (void)newValue; #endif } void MemObject::delayRead(const AsyncCall::Pointer &aRead) { #if USE_DELAY_POOLS if (readAheadPolicyCanRead()) { if (DelayId mostAllowedId = mostBytesAllowed()) { mostAllowedId.delayRead(aRead); return; } } #endif deferredReads.delay(aRead); } void MemObject::kickReads() { deferredReads.schedule(); } #if USE_DELAY_POOLS DelayId MemObject::mostBytesAllowed() const { int j; int jmax = -1; DelayId result; for (dlink_node *node = clients.head; node; node = node->next) { store_client *sc = (store_client *) node->data; j = sc->bytesWanted(); if (j > jmax) { jmax = j; result = sc->delayId; } } return result; } #endif int64_t MemObject::availableForSwapOut() const { return endOffset() - swapout.queue_offset; }