1 /*
2  * Copyright (C) 1996-2021 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 19    Store Memory Primitives */
10 
11 #include "squid.h"
12 #include "comm/Connection.h"
13 #include "Generic.h"
14 #include "globals.h"
15 #include "HttpReply.h"
16 #include "HttpRequest.h"
17 #include "MemBuf.h"
18 #include "MemObject.h"
19 #include "profiler/Profiler.h"
20 #include "SquidConfig.h"
21 #include "Store.h"
22 #include "StoreClient.h"
23 
24 #if USE_DELAY_POOLS
25 #include "DelayPools.h"
26 #endif
27 
28 /* TODO: make this global or private */
29 #if URL_CHECKSUM_DEBUG
30 static unsigned int url_checksum(const char *url);
31 unsigned int
url_checksum(const char * url)32 url_checksum(const char *url)
33 {
34     unsigned int ck;
35     SquidMD5_CTX M;
36     static unsigned char digest[16];
37     SquidMD5Init(&M);
38     SquidMD5Update(&M, (unsigned char *) url, strlen(url));
39     SquidMD5Final(digest, &M);
40     memcpy(&ck, digest, sizeof(ck));
41     return ck;
42 }
43 
44 #endif
45 
46 RemovalPolicy * mem_policy = NULL;
47 
48 size_t
inUseCount()49 MemObject::inUseCount()
50 {
51     return Pool().inUseCount();
52 }
53 
54 const char *
storeId() const55 MemObject::storeId() const
56 {
57     if (!storeId_.size()) {
58         debugs(20, DBG_IMPORTANT, "Bug: Missing MemObject::storeId value");
59         dump();
60         storeId_ = "[unknown_URI]";
61     }
62     return storeId_.termedBuf();
63 }
64 
65 const char *
logUri() const66 MemObject::logUri() const
67 {
68     return logUri_.size() ? logUri_.termedBuf() : storeId();
69 }
70 
71 bool
hasUris() const72 MemObject::hasUris() const
73 {
74     return storeId_.size();
75 }
76 
77 void
setUris(char const * aStoreId,char const * aLogUri,const HttpRequestMethod & aMethod)78 MemObject::setUris(char const *aStoreId, char const *aLogUri, const HttpRequestMethod &aMethod)
79 {
80     if (hasUris())
81         return;
82 
83     storeId_ = aStoreId;
84     debugs(88, 3, this << " storeId: " << storeId_);
85 
86     // fast pointer comparison for a common storeCreateEntry(url,url,...) case
87     if (!aLogUri || aLogUri == aStoreId)
88         logUri_.clean(); // use storeId_ by default to minimize copying
89     else
90         logUri_ = aLogUri;
91 
92     method = aMethod;
93 
94 #if URL_CHECKSUM_DEBUG
95     chksum = url_checksum(urlXXX());
96 #endif
97 }
98 
MemObject()99 MemObject::MemObject()
100 {
101     debugs(20, 3, "MemObject constructed, this=" << this);
102     ping_reply_callback = nullptr;
103     memset(&start_ping, 0, sizeof(start_ping));
104     _reply = new HttpReply;
105     HTTPMSGLOCK(_reply);
106 }
107 
~MemObject()108 MemObject::~MemObject()
109 {
110     debugs(20, 3, "MemObject destructed, this=" << this);
111     const Ctx ctx = ctx_enter(hasUris() ? urlXXX() : "[unknown_ctx]");
112 
113 #if URL_CHECKSUM_DEBUG
114     checkUrlChecksum();
115 #endif
116 
117     if (!shutting_down) { // Store::Root() is FATALly missing during shutdown
118         assert(xitTable.index < 0);
119         assert(memCache.index < 0);
120         assert(swapout.sio == NULL);
121     }
122 
123     data_hdr.freeContent();
124 
125 #if 0
126     /*
127      * There is no way to abort FD-less clients, so they might
128      * still have mem->clients set.
129      */
130     assert(clients.head == NULL);
131 
132 #endif
133 
134     HTTPMSGUNLOCK(_reply);
135 
136     HTTPMSGUNLOCK(request);
137 
138     ctx_exit(ctx);              /* must exit before we free mem->url */
139 }
140 
141 void
unlinkRequest()142 MemObject::unlinkRequest()
143 {
144     HTTPMSGUNLOCK(request);
145 }
146 
147 void
write(const StoreIOBuffer & writeBuffer)148 MemObject::write(const StoreIOBuffer &writeBuffer)
149 {
150     PROF_start(MemObject_write);
151     debugs(19, 6, "memWrite: offset " << writeBuffer.offset << " len " << writeBuffer.length);
152 
153     /* We don't separate out mime headers yet, so ensure that the first
154      * write is at offset 0 - where they start
155      */
156     assert (data_hdr.endOffset() || writeBuffer.offset == 0);
157 
158     assert (data_hdr.write (writeBuffer));
159     PROF_stop(MemObject_write);
160 }
161 
162 void
dump() const163 MemObject::dump() const
164 {
165     data_hdr.dump();
166 #if 0
167     /* do we want this one? */
168     debugs(20, DBG_IMPORTANT, "MemObject->data.origin_offset: " << (data_hdr.head ? data_hdr.head->nodeBuffer.offset : 0));
169 #endif
170 
171     debugs(20, DBG_IMPORTANT, "MemObject->start_ping: " << start_ping.tv_sec  << "."<< std::setfill('0') << std::setw(6) << start_ping.tv_usec);
172     debugs(20, DBG_IMPORTANT, "MemObject->inmem_hi: " << data_hdr.endOffset());
173     debugs(20, DBG_IMPORTANT, "MemObject->inmem_lo: " << inmem_lo);
174     debugs(20, DBG_IMPORTANT, "MemObject->nclients: " << nclients);
175     debugs(20, DBG_IMPORTANT, "MemObject->reply: " << _reply);
176     debugs(20, DBG_IMPORTANT, "MemObject->request: " << request);
177     debugs(20, DBG_IMPORTANT, "MemObject->logUri: " << logUri_);
178     debugs(20, DBG_IMPORTANT, "MemObject->storeId: " << storeId_);
179 }
180 
181 HttpReply const *
getReply() const182 MemObject::getReply() const
183 {
184     return _reply;
185 }
186 
187 void
replaceHttpReply(HttpReply * newrep)188 MemObject::replaceHttpReply(HttpReply *newrep)
189 {
190     HTTPMSGUNLOCK(_reply);
191     _reply = newrep;
192     HTTPMSGLOCK(_reply);
193 }
194 
195 struct LowestMemReader : public unary_function<store_client, void> {
LowestMemReaderLowestMemReader196     LowestMemReader(int64_t seed):current(seed) {}
197 
operator ()LowestMemReader198     void operator() (store_client const &x) {
199         if (x.memReaderHasLowerOffset(current))
200             current = x.copyInto.offset;
201     }
202 
203     int64_t current;
204 };
205 
206 struct StoreClientStats : public unary_function<store_client, void> {
StoreClientStatsStoreClientStats207     StoreClientStats(MemBuf *anEntry):where(anEntry),index(0) {}
208 
operator ()StoreClientStats209     void operator()(store_client const &x) {
210         x.dumpStats(where, index);
211         ++index;
212     }
213 
214     MemBuf *where;
215     size_t index;
216 };
217 
218 void
stat(MemBuf * mb) const219 MemObject::stat(MemBuf * mb) const
220 {
221     mb->appendf("\t" SQUIDSBUFPH " %s\n", SQUIDSBUFPRINT(method.image()), logUri());
222     if (!vary_headers.isEmpty())
223         mb->appendf("\tvary_headers: " SQUIDSBUFPH "\n", SQUIDSBUFPRINT(vary_headers));
224     mb->appendf("\tinmem_lo: %" PRId64 "\n", inmem_lo);
225     mb->appendf("\tinmem_hi: %" PRId64 "\n", data_hdr.endOffset());
226     mb->appendf("\tswapout: %" PRId64 " bytes queued\n", swapout.queue_offset);
227 
228     if (swapout.sio.getRaw())
229         mb->appendf("\tswapout: %" PRId64 " bytes written\n", (int64_t) swapout.sio->offset());
230 
231     if (xitTable.index >= 0)
232         mb->appendf("\ttransient index: %d state: %d\n", xitTable.index, xitTable.io);
233     if (memCache.index >= 0)
234         mb->appendf("\tmem-cache index: %d state: %d offset: %" PRId64 "\n", memCache.index, memCache.io, memCache.offset);
235     if (object_sz >= 0)
236         mb->appendf("\tobject_sz: %" PRId64 "\n", object_sz);
237 
238     StoreClientStats statsVisitor(mb);
239 
240     for_each<StoreClientStats>(clients, statsVisitor);
241 }
242 
243 int64_t
endOffset() const244 MemObject::endOffset () const
245 {
246     return data_hdr.endOffset();
247 }
248 
249 void
markEndOfReplyHeaders()250 MemObject::markEndOfReplyHeaders()
251 {
252     const int hdr_sz = endOffset();
253     assert(hdr_sz >= 0);
254     assert(_reply);
255     _reply->hdr_sz = hdr_sz;
256 }
257 
258 int64_t
size() const259 MemObject::size() const
260 {
261     if (object_sz < 0)
262         return endOffset();
263 
264     return object_sz;
265 }
266 
267 int64_t
expectedReplySize() const268 MemObject::expectedReplySize() const
269 {
270     debugs(20, 7, HERE << "object_sz: " << object_sz);
271     if (object_sz >= 0) // complete() has been called; we know the exact answer
272         return object_sz;
273 
274     if (_reply) {
275         const int64_t clen = _reply->bodySize(method);
276         debugs(20, 7, HERE << "clen: " << clen);
277         if (clen >= 0 && _reply->hdr_sz > 0) // yuck: HttpMsg sets hdr_sz to 0
278             return clen + _reply->hdr_sz;
279     }
280 
281     return -1; // not enough information to predict
282 }
283 
284 void
reset()285 MemObject::reset()
286 {
287     assert(swapout.sio == NULL);
288     data_hdr.freeContent();
289     inmem_lo = 0;
290     /* Should we check for clients? */
291 }
292 
293 int64_t
lowestMemReaderOffset() const294 MemObject::lowestMemReaderOffset() const
295 {
296     LowestMemReader lowest (endOffset() + 1);
297 
298     for_each <LowestMemReader>(clients, lowest);
299 
300     return lowest.current;
301 }
302 
303 /* XXX: This is wrong. It breaks *badly* on range combining */
304 bool
readAheadPolicyCanRead() const305 MemObject::readAheadPolicyCanRead() const
306 {
307     const bool canRead = endOffset() - getReply()->hdr_sz <
308                          lowestMemReaderOffset() + Config.readAheadGap;
309 
310     if (!canRead) {
311         debugs(19, 9, "no: " << endOffset() << '-' << getReply()->hdr_sz <<
312                " < " << lowestMemReaderOffset() << '+' << Config.readAheadGap);
313     }
314 
315     return canRead;
316 }
317 
318 void
addClient(store_client * aClient)319 MemObject::addClient(store_client *aClient)
320 {
321     ++nclients;
322     dlinkAdd(aClient, &aClient->node, &clients);
323 }
324 
325 #if URL_CHECKSUM_DEBUG
326 void
checkUrlChecksum() const327 MemObject::checkUrlChecksum () const
328 {
329     assert(chksum == url_checksum(urlXXX()));
330 }
331 
332 #endif
333 
334 /*
335  * How much of the object data is on the disk?
336  */
337 int64_t
objectBytesOnDisk() const338 MemObject::objectBytesOnDisk() const
339 {
340     /*
341      * NOTE: storeOffset() represents the disk file size,
342      * not the amount of object data on disk.
343      *
344      * If we don't have at least 'swap_hdr_sz' bytes
345      * then none of the object data is on disk.
346      *
347      * This should still be safe if swap_hdr_sz == 0,
348      * meaning we haven't even opened the swapout file
349      * yet.
350      */
351 
352     if (swapout.sio.getRaw() == NULL)
353         return 0;
354 
355     int64_t nwritten = swapout.sio->offset();
356 
357     if (nwritten <= (int64_t)swap_hdr_sz)
358         return 0;
359 
360     return (nwritten - swap_hdr_sz);
361 }
362 
363 int64_t
policyLowestOffsetToKeep(bool swap) const364 MemObject::policyLowestOffsetToKeep(bool swap) const
365 {
366     /*
367      * Careful.  lowest_offset can be greater than endOffset(), such
368      * as in the case of a range request.
369      */
370     int64_t lowest_offset = lowestMemReaderOffset();
371 
372     if (endOffset() < lowest_offset ||
373             endOffset() - inmem_lo > (int64_t)Config.Store.maxInMemObjSize ||
374             (swap && !Config.onoff.memory_cache_first))
375         return lowest_offset;
376 
377     return inmem_lo;
378 }
379 
380 void
trimSwappable()381 MemObject::trimSwappable()
382 {
383     int64_t new_mem_lo = policyLowestOffsetToKeep(1);
384     /*
385      * We should only free up to what we know has been written
386      * to disk, not what has been queued for writing.  Otherwise
387      * there will be a chunk of the data which is not in memory
388      * and is not yet on disk.
389      * The -1 makes sure the page isn't freed until storeSwapOut has
390      * walked to the next page.
391      */
392     int64_t on_disk;
393 
394     if ((on_disk = objectBytesOnDisk()) - 1 < new_mem_lo)
395         new_mem_lo = on_disk - 1;
396 
397     if (new_mem_lo == -1)
398         new_mem_lo = 0; /* the above might become -1 */
399 
400     data_hdr.freeDataUpto(new_mem_lo);
401 
402     inmem_lo = new_mem_lo;
403 }
404 
405 void
trimUnSwappable()406 MemObject::trimUnSwappable()
407 {
408     if (const int64_t new_mem_lo = policyLowestOffsetToKeep(false)) {
409         assert (new_mem_lo > 0);
410         data_hdr.freeDataUpto(new_mem_lo);
411         inmem_lo = new_mem_lo;
412     } // else we should not trim anything at this time
413 }
414 
415 bool
isContiguous() const416 MemObject::isContiguous() const
417 {
418     bool result = data_hdr.hasContigousContentRange (Range<int64_t>(inmem_lo, endOffset()));
419     /* XXX : make this higher level */
420     debugs (19, result ? 4 :3, "MemObject::isContiguous: Returning " << (result ? "true" : "false"));
421     return result;
422 }
423 
424 int
mostBytesWanted(int max,bool ignoreDelayPools) const425 MemObject::mostBytesWanted(int max, bool ignoreDelayPools) const
426 {
427 #if USE_DELAY_POOLS
428     if (!ignoreDelayPools) {
429         /* identify delay id with largest allowance */
430         DelayId largestAllowance = mostBytesAllowed ();
431         return largestAllowance.bytesWanted(0, max);
432     }
433 #endif
434 
435     return max;
436 }
437 
438 void
setNoDelay(bool const newValue)439 MemObject::setNoDelay(bool const newValue)
440 {
441 #if USE_DELAY_POOLS
442 
443     for (dlink_node *node = clients.head; node; node = node->next) {
444         store_client *sc = (store_client *) node->data;
445         sc->delayId.setNoDelay(newValue);
446     }
447 
448 #endif
449 }
450 
451 void
delayRead(DeferredRead const & aRead)452 MemObject::delayRead(DeferredRead const &aRead)
453 {
454 #if USE_DELAY_POOLS
455     if (readAheadPolicyCanRead()) {
456         if (DelayId mostAllowedId = mostBytesAllowed()) {
457             mostAllowedId.delayRead(aRead);
458             return;
459         }
460     }
461 #endif
462     deferredReads.delayRead(aRead);
463 }
464 
465 void
kickReads()466 MemObject::kickReads()
467 {
468     deferredReads.kickReads(-1);
469 }
470 
471 #if USE_DELAY_POOLS
472 DelayId
mostBytesAllowed() const473 MemObject::mostBytesAllowed() const
474 {
475     int j;
476     int jmax = -1;
477     DelayId result;
478 
479     for (dlink_node *node = clients.head; node; node = node->next) {
480         store_client *sc = (store_client *) node->data;
481 #if 0
482         /* This test is invalid because the client may be writing data
483          * and thus will want data immediately.
484          * If we include the test, there is a race condition when too much
485          * data is read - if all sc's are writing when a read is scheduled.
486          * XXX: fixme.
487          */
488 
489         if (!sc->callbackPending())
490             /* not waiting for more data */
491             continue;
492 
493 #endif
494 
495         j = sc->delayId.bytesWanted(0, sc->copyInto.length);
496 
497         if (j > jmax) {
498             jmax = j;
499             result = sc->delayId;
500         }
501     }
502 
503     return result;
504 }
505 
506 #endif
507 
508 int64_t
availableForSwapOut() const509 MemObject::availableForSwapOut() const
510 {
511     return endOffset() - swapout.queue_offset;
512 }
513 
514