/*
- * Copyright (C) 1996-2015 The Squid Software Foundation and contributors
+ * 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.
/// shared memory segment path to use for Transients map
static const SBuf MapLabel("transients_map");
-/// shared memory segment path to use for Transients map extras
-static const char *ExtrasLabel = "transients_ex";
-Transients::Transients(): map(NULL), locals(NULL)
+Transients::Transients(): map(nullptr), locals(nullptr)
{
}
void
Transients::init()
{
+ assert(Enabled());
const int64_t entryLimit = EntryLimit();
- if (entryLimit <= 0)
- return; // no SMP support or a misconfiguration
+ assert(entryLimit > 0);
Must(!map);
map = new TransientsMap(MapLabel);
map->cleaner = this;
+ map->disableHitValidation(); // Transients lacks slices to validate
- extras = shm_old(TransientsMapExtras)(ExtrasLabel);
-
- locals = new Locals(entryLimit, 0);
+ locals = new Locals(entryLimit, nullptr);
}
void
stats.mem.size =
Ipc::Mem::PageLevel(Ipc::Mem::PageId::cachePage) * pageSize;
stats.mem.count = currentCount();
+#else
+ (void)stats;
#endif
}
}
bool
-Transients::dereference(StoreEntry &, bool)
+Transients::dereference(StoreEntry &)
{
// no need to keep e in the global store_table for us; we have our own map
return false;
}
-int
-Transients::callback()
-{
- return 0;
-}
-
-StoreSearch *
-Transients::search(String const, HttpRequest *)
-{
- fatal("not implemented");
- return NULL;
-}
-
StoreEntry *
Transients::get(const cache_key *key)
{
if (!map)
- return NULL;
+ return nullptr;
sfileno index;
const Ipc::StoreMapAnchor *anchor = map->openForReading(key, index);
if (!anchor)
- return NULL;
+ return nullptr;
// If we already have a local entry, the store_table should have found it.
// Since it did not, the local entry key must have changed from public to
if (StoreEntry *oldE = locals->at(index)) {
debugs(20, 3, "not joining private " << *oldE);
assert(EBIT_TEST(oldE->flags, KEY_PRIVATE));
- } else if (StoreEntry *newE = copyFromShm(index)) {
- return newE; // keep read lock to receive updates from others
+ map->closeForReadingAndFreeIdle(index);
+ return nullptr;
}
- // private entry or loading failure
- map->closeForReading(index);
- return NULL;
-}
-
-StoreEntry *
-Transients::copyFromShm(const sfileno index)
-{
- const TransientsMapExtras::Item &extra = extras->items[index];
-
- // create a brand new store entry and initialize it with stored info
- StoreEntry *e = storeCreatePureEntry(extra.url, extra.url,
- extra.reqFlags, extra.reqMethod);
+ StoreEntry *e = new StoreEntry();
+ e->createMemObject();
+ anchorEntry(*e, index, *anchor);
- assert(e->mem_obj);
- e->mem_obj->method = extra.reqMethod;
- e->mem_obj->xitTable.io = MemObject::ioReading;
- e->mem_obj->xitTable.index = index;
-
- e->setPublicKey();
- assert(e->key);
-
- // How do we know its SMP- and not just locally-collapsed? A worker gets
- // locally-collapsed entries from the local store_table, not Transients.
- // TODO: Can we remove smpCollapsed by not syncing non-transient entries?
- e->mem_obj->smpCollapsed = true;
-
- assert(!locals->at(index));
- // We do not lock e because we do not want to prevent its destruction;
- // e is tied to us via mem_obj so we will know when it is destructed.
- locals->at(index) = e;
+ // keep read lock to receive updates from others
return e;
}
-void
-Transients::get(String const, STOREGETCLIENT, void *)
-{
- // XXX: not needed but Store parent forces us to implement this
- fatal("Transients::get(key,callback,data) should not be called");
-}
-
StoreEntry *
Transients::findCollapsed(const sfileno index)
{
if (!map)
- return NULL;
+ return nullptr;
if (StoreEntry *oldE = locals->at(index)) {
debugs(20, 5, "found " << *oldE << " at " << index << " in " << MapLabel);
}
debugs(20, 3, "no entry at " << index << " in " << MapLabel);
- return NULL;
+ return nullptr;
}
void
-Transients::startWriting(StoreEntry *e, const RequestFlags &reqFlags,
- const HttpRequestMethod &reqMethod)
+Transients::monitorIo(StoreEntry *e, const cache_key *key, const Store::IoStatus direction)
{
- assert(e);
- assert(e->mem_obj);
- assert(e->mem_obj->xitTable.index < 0);
-
- if (!map) {
- debugs(20, 5, "No map to add " << *e);
- return;
- }
-
- sfileno index = 0;
- Ipc::StoreMapAnchor *slot = map->openForWriting(reinterpret_cast<const cache_key *>(e->key), index);
- if (!slot) {
- debugs(20, 5, "collision registering " << *e);
- return;
+ if (!e->hasTransients()) {
+ addEntry(e, key, direction);
+ assert(e->hasTransients());
}
- try {
- if (copyToShm(*e, index, reqFlags, reqMethod)) {
- slot->set(*e);
- e->mem_obj->xitTable.io = MemObject::ioWriting;
- e->mem_obj->xitTable.index = index;
- map->startAppending(index);
- // keep write lock -- we will be supplying others with updates
- return;
- }
- // fall through to the error handling code
- } catch (const std::exception &x) { // TODO: should we catch ... as well?
- debugs(20, 2, "error keeping entry " << index <<
- ' ' << *e << ": " << x.what());
- // fall through to the error handling code
+ const auto index = e->mem_obj->xitTable.index;
+ if (const auto old = locals->at(index)) {
+ assert(old == e);
+ } else {
+ // We do not lock e because we do not want to prevent its destruction;
+ // e is tied to us via mem_obj so we will know when it is destructed.
+ locals->at(index) = e;
}
-
- map->abortWriting(index);
}
-/// copies all relevant local data to shared memory
-bool
-Transients::copyToShm(const StoreEntry &e, const sfileno index,
- const RequestFlags &reqFlags,
- const HttpRequestMethod &reqMethod)
+/// creates a new Transients entry
+void
+Transients::addEntry(StoreEntry *e, const cache_key *key, const Store::IoStatus direction)
{
- TransientsMapExtras::Item &extra = extras->items[index];
+ assert(e);
+ assert(e->mem_obj);
+ assert(!e->hasTransients());
+
+ Must(map); // configured to track transients
- const char *url = e.url();
- const size_t urlLen = strlen(url);
- Must(urlLen < sizeof(extra.url)); // we have space to store it all, plus 0
- strncpy(extra.url, url, sizeof(extra.url));
- extra.url[urlLen] = '\0';
+ if (direction == Store::ioWriting)
+ return addWriterEntry(*e, key);
- extra.reqFlags = reqFlags;
+ assert(direction == Store::ioReading);
+ addReaderEntry(*e, key);
+}
- Must(reqMethod != Http::METHOD_OTHER);
- extra.reqMethod = reqMethod.id();
+/// addEntry() helper used for cache entry creators/writers
+void
+Transients::addWriterEntry(StoreEntry &e, const cache_key *key)
+{
+ sfileno index = 0;
+ const auto anchor = map->openForWriting(key, index);
+ if (!anchor)
+ throw TextException("writer collision", Here());
- return true;
+ // set ASAP in hope to unlock the slot if something throws
+ // and to provide index to such methods as hasWriter()
+ auto &xitTable = e.mem_obj->xitTable;
+ xitTable.index = index;
+ xitTable.io = Store::ioWriting;
+
+ anchor->set(e, key);
+ // allow reading and receive remote DELETE events, but do not switch to
+ // the reading lock because transientReaders() callers want true readers
+ map->startAppending(index);
}
+/// addEntry() helper used for cache readers
+/// readers do not modify the cache, but they must create a Transients entry
void
-Transients::noteFreeMapSlice(const Ipc::StoreMapSliceId)
+Transients::addReaderEntry(StoreEntry &e, const cache_key *key)
{
- // TODO: we should probably find the entry being deleted and abort it
+ sfileno index = 0;
+ const auto anchor = map->openOrCreateForReading(key, index, e);
+ if (!anchor)
+ throw TextException("reader collision", Here());
+
+ anchorEntry(e, index, *anchor);
+ // keep the entry locked (for reading) to receive remote DELETE events
}
+/// fills (recently created) StoreEntry with information currently in Transients
void
-Transients::abandon(const StoreEntry &e)
+Transients::anchorEntry(StoreEntry &e, const sfileno index, const Ipc::StoreMapAnchor &anchor)
{
- assert(e.mem_obj && map);
- map->freeEntry(e.mem_obj->xitTable.index); // just marks the locked entry
- CollapsedForwarding::Broadcast(e);
- // We do not unlock the entry now because the problem is most likely with
- // the server resource rather than a specific cache writer, so we want to
- // prevent other readers from collapsing requests for that resource.
+ // set ASAP in hope to unlock the slot if something throws
+ // and to provide index to such methods as hasWriter()
+ auto &xitTable = e.mem_obj->xitTable;
+ xitTable.index = index;
+ xitTable.io = Store::ioReading;
+
+ anchor.exportInto(e);
}
bool
-Transients::abandoned(const StoreEntry &e) const
+Transients::hasWriter(const StoreEntry &e)
{
- assert(e.mem_obj);
- return abandonedAt(e.mem_obj->xitTable.index);
+ if (!e.hasTransients())
+ return false;
+ return map->peekAtWriter(e.mem_obj->xitTable.index);
}
-/// whether an in-transit entry at the index is now abandoned by its writer
-bool
-Transients::abandonedAt(const sfileno index) const
+void
+Transients::noteFreeMapSlice(const Ipc::StoreMapSliceId)
+{
+ // TODO: we should probably find the entry being deleted and abort it
+}
+
+void
+Transients::status(const StoreEntry &entry, Transients::EntryStatus &entryStatus) const
{
assert(map);
- return map->readableEntry(index).waitingToBeFreed;
+ assert(entry.hasTransients());
+ const auto idx = entry.mem_obj->xitTable.index;
+ const auto &anchor = isWriter(entry) ?
+ map->writeableEntry(idx) : map->readableEntry(idx);
+ entryStatus.hasWriter = anchor.writing();
+ entryStatus.waitingToBeFreed = anchor.waitingToBeFreed;
}
void
Transients::completeWriting(const StoreEntry &e)
{
- if (e.mem_obj && e.mem_obj->xitTable.index >= 0) {
- assert(e.mem_obj->xitTable.io == MemObject::ioWriting);
- // there will be no more updates from us after this, so we must prevent
- // future readers from joining
- map->freeEntry(e.mem_obj->xitTable.index); // just marks the locked entry
- map->closeForWriting(e.mem_obj->xitTable.index);
- e.mem_obj->xitTable.index = -1;
- e.mem_obj->xitTable.io = MemObject::ioDone;
- }
+ debugs(20, 5, e);
+ assert(e.hasTransients());
+ assert(isWriter(e));
+ map->switchWritingToReading(e.mem_obj->xitTable.index);
+ e.mem_obj->xitTable.io = Store::ioReading;
+ CollapsedForwarding::Broadcast(e);
}
int
Transients::readers(const StoreEntry &e) const
{
- if (e.mem_obj && e.mem_obj->xitTable.index >= 0) {
+ if (e.hasTransients()) {
assert(map);
return map->peekAtEntry(e.mem_obj->xitTable.index).lock.readers;
}
}
void
-Transients::markForUnlink(StoreEntry &e)
+Transients::evictCached(StoreEntry &e)
+{
+ debugs(20, 5, e);
+ if (e.hasTransients()) {
+ const auto index = e.mem_obj->xitTable.index;
+ if (map->freeEntry(index)) {
+ // Delay syncCollapsed(index) which may end `e` wait for updates.
+ // Calling it directly/here creates complex reentrant call chains.
+ CollapsedForwarding::Broadcast(e, true);
+ }
+ } // else nothing to do because e must be private
+}
+
+void
+Transients::evictIfFound(const cache_key *key)
{
- if (e.mem_obj && e.mem_obj->xitTable.io == MemObject::ioWriting)
- abandon(e);
+ if (!map)
+ return;
+
+ const sfileno index = map->fileNoByKey(key);
+ if (map->freeEntry(index))
+ CollapsedForwarding::Broadcast(index, true);
}
void
-Transients::disconnect(MemObject &mem_obj)
+Transients::disconnect(StoreEntry &entry)
{
- if (mem_obj.xitTable.index >= 0) {
+ debugs(20, 5, entry);
+ if (entry.hasTransients()) {
+ auto &xitTable = entry.mem_obj->xitTable;
assert(map);
- if (mem_obj.xitTable.io == MemObject::ioWriting) {
- map->abortWriting(mem_obj.xitTable.index);
+ if (isWriter(entry)) {
+ // completeWriting() was not called, so there could be an active
+ // Store writer out there, but we should not abortWriting() here
+ // because another writer may have succeeded, making readers happy.
+ // If none succeeded, the readers will notice the lack of writers.
+ map->closeForWriting(xitTable.index);
+ CollapsedForwarding::Broadcast(entry);
} else {
- assert(mem_obj.xitTable.io == MemObject::ioReading);
- map->closeForReading(mem_obj.xitTable.index);
+ assert(isReader(entry));
+ map->closeForReadingAndFreeIdle(xitTable.index);
}
- locals->at(mem_obj.xitTable.index) = NULL;
- mem_obj.xitTable.index = -1;
- mem_obj.xitTable.io = MemObject::ioDone;
+ locals->at(xitTable.index) = nullptr;
+ xitTable.index = -1;
+ xitTable.io = Store::ioDone;
}
}
int64_t
Transients::EntryLimit()
{
- // TODO: we should also check whether any SMP-aware caching is configured
- if (!UsingSmp() || !Config.onoff.collapsed_forwarding)
- return 0; // no SMP collapsed forwarding possible or needed
+ return (UsingSmp() && Store::Controller::SmpAware()) ?
+ Config.shared_transient_entries_limit : 0;
+}
- return 16*1024; // TODO: make configurable?
+bool
+Transients::markedForDeletion(const cache_key *key) const
+{
+ assert(map);
+ return map->markedForDeletion(key);
+}
+
+bool
+Transients::isReader(const StoreEntry &e) const
+{
+ return e.mem_obj && e.mem_obj->xitTable.io == Store::ioReading;
+}
+
+bool
+Transients::isWriter(const StoreEntry &e) const
+{
+ return e.mem_obj && e.mem_obj->xitTable.io == Store::ioWriting;
}
/// initializes shared memory segment used by Transients
{
public:
/* RegisteredRunner API */
- TransientsRr(): mapOwner(NULL), extrasOwner(NULL) {}
- virtual void useConfig();
- virtual ~TransientsRr();
+ void useConfig() override;
+ ~TransientsRr() override;
protected:
- virtual void create();
+ void create() override;
private:
- TransientsMap::Owner *mapOwner;
- Ipc::Mem::Owner<TransientsMapExtras> *extrasOwner;
+ TransientsMap::Owner *mapOwner = nullptr;
};
RunnerRegistrationEntry(TransientsRr);
void
TransientsRr::create()
{
- if (!Config.onoff.collapsed_forwarding)
- return;
-
const int64_t entryLimit = Transients::EntryLimit();
if (entryLimit <= 0)
return; // no SMP configured or a misconfiguration
Must(!mapOwner);
mapOwner = TransientsMap::Init(MapLabel, entryLimit);
- Must(!extrasOwner);
- extrasOwner = shm_new(TransientsMapExtras)(ExtrasLabel, entryLimit);
}
TransientsRr::~TransientsRr()
{
- delete extrasOwner;
delete mapOwner;
}