2 * Copyright (C) 1996-2023 The Squid Software Foundation and contributors
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.
9 /* DEBUG: section 20 Storage Manager */
12 #include "base/RunnersRegistry.h"
13 #include "CollapsedForwarding.h"
14 #include "HttpReply.h"
15 #include "ipc/mem/Page.h"
16 #include "ipc/mem/Pages.h"
17 #include "MemObject.h"
18 #include "mime_header.h"
19 #include "SquidConfig.h"
20 #include "SquidMath.h"
21 #include "StoreStats.h"
23 #include "Transients.h"
27 /// shared memory segment path to use for Transients map
31 static const auto label
= new SBuf("transients_map");
35 Transients::Transients(): map(nullptr), locals(nullptr)
39 Transients::~Transients()
49 const int64_t entryLimit
= EntryLimit();
50 assert(entryLimit
> 0);
53 map
= new TransientsMap(MapLabel());
55 map
->disableHitValidation(); // Transients lacks slices to validate
57 locals
= new Locals(entryLimit
, nullptr);
61 Transients::getStats(StoreInfoStats
&stats
) const
63 #if TRANSIENT_STATS_SUPPORTED
64 const size_t pageSize
= Ipc::Mem::PageSize();
66 stats
.mem
.shared
= true;
68 Ipc::Mem::PageLimit(Ipc::Mem::PageId::cachePage
) * pageSize
;
70 Ipc::Mem::PageLevel(Ipc::Mem::PageId::cachePage
) * pageSize
;
71 stats
.mem
.count
= currentCount();
78 Transients::stat(StoreEntry
&e
) const
80 storeAppendPrintf(&e
, "\n\nTransient Objects\n");
82 storeAppendPrintf(&e
, "Maximum Size: %.0f KB\n", maxSize()/1024.0);
83 storeAppendPrintf(&e
, "Current Size: %.2f KB %.2f%%\n",
84 currentSize() / 1024.0,
85 Math::doublePercent(currentSize(), maxSize()));
88 const int limit
= map
->entryLimit();
89 storeAppendPrintf(&e
, "Maximum entries: %9d\n", limit
);
91 storeAppendPrintf(&e
, "Current entries: %" PRId64
" %.2f%%\n",
92 currentCount(), (100.0 * currentCount() / limit
));
98 Transients::maintain()
100 // no lazy garbage collection needed
104 Transients::minSize() const
106 return 0; // XXX: irrelevant, but Store parent forces us to implement this
110 Transients::maxSize() const
112 // Squid currently does not limit the total size of all transient objects
113 return std::numeric_limits
<uint64_t>::max();
117 Transients::currentSize() const
119 // TODO: we do not get enough information to calculate this
120 // StoreEntry should update associated stores when its size changes
125 Transients::currentCount() const
127 return map
? map
->entryCount() : 0;
131 Transients::maxObjectSize() const
133 // Squid currently does not limit the size of a transient object
134 return std::numeric_limits
<uint64_t>::max();
138 Transients::reference(StoreEntry
&)
140 // no replacement policy (but the cache(s) storing the entry may have one)
144 Transients::dereference(StoreEntry
&)
146 // no need to keep e in the global store_table for us; we have our own map
151 Transients::get(const cache_key
*key
)
157 const Ipc::StoreMapAnchor
*anchor
= map
->openForReading(key
, index
);
161 // If we already have a local entry, the store_table should have found it.
162 // Since it did not, the local entry key must have changed from public to
163 // private. We still need to keep the private entry around for syncing as
164 // its clients depend on it, but we should not allow new clients to join.
165 if (StoreEntry
*oldE
= locals
->at(index
)) {
166 debugs(20, 3, "not joining private " << *oldE
);
167 assert(EBIT_TEST(oldE
->flags
, KEY_PRIVATE
));
168 map
->closeForReadingAndFreeIdle(index
);
172 StoreEntry
*e
= new StoreEntry();
173 e
->createMemObject();
174 e
->mem_obj
->xitTable
.open(index
, Store::ioReading
);
176 // keep read lock to receive updates from others
181 Transients::findCollapsed(const sfileno index
)
186 if (StoreEntry
*oldE
= locals
->at(index
)) {
187 debugs(20, 5, "found " << *oldE
<< " at " << index
<< " in " << MapLabel());
188 assert(oldE
->mem_obj
&& oldE
->mem_obj
->xitTable
.index
== index
);
192 debugs(20, 3, "no entry at " << index
<< " in " << MapLabel());
197 Transients::monitorIo(StoreEntry
*e
, const cache_key
*key
, const Store::IoStatus direction
)
199 if (!e
->hasTransients()) {
200 addEntry(e
, key
, direction
);
201 assert(e
->hasTransients());
204 const auto index
= e
->mem_obj
->xitTable
.index
;
205 if (const auto old
= locals
->at(index
)) {
208 // We do not lock e because we do not want to prevent its destruction;
209 // e is tied to us via mem_obj so we will know when it is destructed.
210 locals
->at(index
) = e
;
214 /// creates a new Transients entry
216 Transients::addEntry(StoreEntry
*e
, const cache_key
*key
, const Store::IoStatus direction
)
220 assert(!e
->hasTransients());
222 Must(map
); // configured to track transients
224 if (direction
== Store::ioWriting
)
225 return addWriterEntry(*e
, key
);
227 assert(direction
== Store::ioReading
);
228 addReaderEntry(*e
, key
);
231 /// addEntry() helper used for cache entry creators/writers
233 Transients::addWriterEntry(StoreEntry
&e
, const cache_key
*key
)
236 const auto anchor
= map
->openForWriting(key
, index
);
238 throw TextException("writer collision", Here());
240 // set ASAP in hope to unlock the slot if something throws
241 // and to provide index to such methods as hasWriter()
242 e
.mem_obj
->xitTable
.open(index
, Store::ioWriting
);
245 // allow reading and receive remote DELETE events, but do not switch to
246 // the reading lock because transientReaders() callers want true readers
247 map
->startAppending(index
);
250 /// addEntry() helper used for cache readers
251 /// readers do not modify the cache, but they must create a Transients entry
253 Transients::addReaderEntry(StoreEntry
&e
, const cache_key
*key
)
256 const auto anchor
= map
->openOrCreateForReading(key
, index
);
258 throw TextException("reader collision", Here());
260 e
.mem_obj
->xitTable
.open(index
, Store::ioReading
);
261 // keep the entry locked (for reading) to receive remote DELETE events
265 Transients::hasWriter(const StoreEntry
&e
)
267 if (!e
.hasTransients())
269 return map
->peekAtWriter(e
.mem_obj
->xitTable
.index
);
273 Transients::noteFreeMapSlice(const Ipc::StoreMapSliceId
)
275 // TODO: we should probably find the entry being deleted and abort it
279 Transients::status(const StoreEntry
&entry
, Transients::EntryStatus
&entryStatus
) const
282 assert(entry
.hasTransients());
283 const auto idx
= entry
.mem_obj
->xitTable
.index
;
284 const auto &anchor
= isWriter(entry
) ?
285 map
->writeableEntry(idx
) : map
->readableEntry(idx
);
286 entryStatus
.hasWriter
= anchor
.writing();
287 entryStatus
.waitingToBeFreed
= anchor
.waitingToBeFreed
;
291 Transients::completeWriting(const StoreEntry
&e
)
294 assert(e
.hasTransients());
296 map
->switchWritingToReading(e
.mem_obj
->xitTable
.index
);
297 e
.mem_obj
->xitTable
.io
= Store::ioReading
;
298 CollapsedForwarding::Broadcast(e
);
302 Transients::readers(const StoreEntry
&e
) const
304 if (e
.hasTransients()) {
306 return map
->peekAtEntry(e
.mem_obj
->xitTable
.index
).lock
.readers
;
312 Transients::evictCached(StoreEntry
&e
)
315 if (e
.hasTransients()) {
316 const auto index
= e
.mem_obj
->xitTable
.index
;
317 if (map
->freeEntry(index
)) {
318 // Delay syncCollapsed(index) which may end `e` wait for updates.
319 // Calling it directly/here creates complex reentrant call chains.
320 CollapsedForwarding::Broadcast(e
, true);
322 } // else nothing to do because e must be private
326 Transients::evictIfFound(const cache_key
*key
)
331 const sfileno index
= map
->fileNoByKey(key
);
332 if (map
->freeEntry(index
))
333 CollapsedForwarding::Broadcast(index
, true);
337 Transients::disconnect(StoreEntry
&entry
)
339 debugs(20, 5, entry
);
340 if (entry
.hasTransients()) {
341 auto &xitTable
= entry
.mem_obj
->xitTable
;
343 if (isWriter(entry
)) {
344 // completeWriting() was not called, so there could be an active
345 // Store writer out there, but we should not abortWriting() here
346 // because another writer may have succeeded, making readers happy.
347 // If none succeeded, the readers will notice the lack of writers.
348 map
->closeForWriting(xitTable
.index
);
349 CollapsedForwarding::Broadcast(entry
);
351 assert(isReader(entry
));
352 map
->closeForReadingAndFreeIdle(xitTable
.index
);
354 locals
->at(xitTable
.index
) = nullptr;
359 /// calculates maximum number of entries we need to store and map
361 Transients::EntryLimit()
363 return (UsingSmp() && Store::Controller::SmpAware()) ?
364 Config
.shared_transient_entries_limit
: 0;
368 Transients::markedForDeletion(const cache_key
*key
) const
371 return map
->markedForDeletion(key
);
375 Transients::isReader(const StoreEntry
&e
) const
377 return e
.mem_obj
&& e
.mem_obj
->xitTable
.io
== Store::ioReading
;
381 Transients::isWriter(const StoreEntry
&e
) const
383 return e
.mem_obj
&& e
.mem_obj
->xitTable
.io
== Store::ioWriting
;
386 /// initializes shared memory segment used by Transients
387 class TransientsRr
: public Ipc::Mem::RegisteredRunner
390 /* RegisteredRunner API */
391 void useConfig() override
;
392 ~TransientsRr() override
;
395 void create() override
;
398 TransientsMap::Owner
*mapOwner
= nullptr;
401 DefineRunnerRegistrator(TransientsRr
);
404 TransientsRr::useConfig()
406 assert(Config
.memShared
.configured());
407 Ipc::Mem::RegisteredRunner::useConfig();
411 TransientsRr::create()
413 const int64_t entryLimit
= Transients::EntryLimit();
415 return; // no SMP configured or a misconfiguration
418 mapOwner
= TransientsMap::Init(MapLabel(), entryLimit
);
421 TransientsRr::~TransientsRr()