]> git.ipfire.org Git - thirdparty/squid.git/blob - src/Transients.cc
235cfb7511df85348334605b253a1d109b12396e
[thirdparty/squid.git] / src / Transients.cc
1 /*
2 * Copyright (C) 1996-2020 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 20 Storage Manager */
10
11 #include "squid.h"
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"
22 #include "tools.h"
23 #include "Transients.h"
24
25 #include <limits>
26
27 /// shared memory segment path to use for Transients map
28 static const SBuf MapLabel("transients_map");
29
30 Transients::Transients(): map(NULL), locals(NULL)
31 {
32 }
33
34 Transients::~Transients()
35 {
36 delete map;
37 delete locals;
38 }
39
40 void
41 Transients::init()
42 {
43 assert(Enabled());
44 const int64_t entryLimit = EntryLimit();
45 assert(entryLimit > 0);
46
47 Must(!map);
48 map = new TransientsMap(MapLabel);
49 map->cleaner = this;
50 map->disableHitValidation(); // Transients lacks slices to validate
51
52 locals = new Locals(entryLimit, 0);
53 }
54
55 void
56 Transients::getStats(StoreInfoStats &stats) const
57 {
58 #if TRANSIENT_STATS_SUPPORTED
59 const size_t pageSize = Ipc::Mem::PageSize();
60
61 stats.mem.shared = true;
62 stats.mem.capacity =
63 Ipc::Mem::PageLimit(Ipc::Mem::PageId::cachePage) * pageSize;
64 stats.mem.size =
65 Ipc::Mem::PageLevel(Ipc::Mem::PageId::cachePage) * pageSize;
66 stats.mem.count = currentCount();
67 #endif
68 }
69
70 void
71 Transients::stat(StoreEntry &e) const
72 {
73 storeAppendPrintf(&e, "\n\nTransient Objects\n");
74
75 storeAppendPrintf(&e, "Maximum Size: %.0f KB\n", maxSize()/1024.0);
76 storeAppendPrintf(&e, "Current Size: %.2f KB %.2f%%\n",
77 currentSize() / 1024.0,
78 Math::doublePercent(currentSize(), maxSize()));
79
80 if (map) {
81 const int limit = map->entryLimit();
82 storeAppendPrintf(&e, "Maximum entries: %9d\n", limit);
83 if (limit > 0) {
84 storeAppendPrintf(&e, "Current entries: %" PRId64 " %.2f%%\n",
85 currentCount(), (100.0 * currentCount() / limit));
86 }
87 }
88 }
89
90 void
91 Transients::maintain()
92 {
93 // no lazy garbage collection needed
94 }
95
96 uint64_t
97 Transients::minSize() const
98 {
99 return 0; // XXX: irrelevant, but Store parent forces us to implement this
100 }
101
102 uint64_t
103 Transients::maxSize() const
104 {
105 // Squid currently does not limit the total size of all transient objects
106 return std::numeric_limits<uint64_t>::max();
107 }
108
109 uint64_t
110 Transients::currentSize() const
111 {
112 // TODO: we do not get enough information to calculate this
113 // StoreEntry should update associated stores when its size changes
114 return 0;
115 }
116
117 uint64_t
118 Transients::currentCount() const
119 {
120 return map ? map->entryCount() : 0;
121 }
122
123 int64_t
124 Transients::maxObjectSize() const
125 {
126 // Squid currently does not limit the size of a transient object
127 return std::numeric_limits<uint64_t>::max();
128 }
129
130 void
131 Transients::reference(StoreEntry &)
132 {
133 // no replacement policy (but the cache(s) storing the entry may have one)
134 }
135
136 bool
137 Transients::dereference(StoreEntry &)
138 {
139 // no need to keep e in the global store_table for us; we have our own map
140 return false;
141 }
142
143 StoreEntry *
144 Transients::get(const cache_key *key)
145 {
146 if (!map)
147 return NULL;
148
149 sfileno index;
150 const Ipc::StoreMapAnchor *anchor = map->openForReading(key, index);
151 if (!anchor)
152 return NULL;
153
154 // If we already have a local entry, the store_table should have found it.
155 // Since it did not, the local entry key must have changed from public to
156 // private. We still need to keep the private entry around for syncing as
157 // its clients depend on it, but we should not allow new clients to join.
158 if (StoreEntry *oldE = locals->at(index)) {
159 debugs(20, 3, "not joining private " << *oldE);
160 assert(EBIT_TEST(oldE->flags, KEY_PRIVATE));
161 map->closeForReadingAndFreeIdle(index);
162 return nullptr;
163 }
164
165 // store hadWriter before checking ENTRY_REQUIRES_COLLAPSING to avoid racing
166 // the writer that clears that flag and then leaves
167 const auto hadWriter = map->peekAtWriter(index);
168 if (!hadWriter && EBIT_TEST(anchor->basics.flags, ENTRY_REQUIRES_COLLAPSING)) {
169 debugs(20, 3, "not joining abandoned entry " << index);
170 map->closeForReadingAndFreeIdle(index);
171 return nullptr;
172 }
173
174 StoreEntry *e = new StoreEntry();
175 e->createMemObject();
176 e->mem_obj->xitTable.index = index;
177 e->mem_obj->xitTable.io = Store::ioReading;
178 anchor->exportInto(*e);
179
180 if (EBIT_TEST(anchor->basics.flags, ENTRY_REQUIRES_COLLAPSING)) {
181 assert(hadWriter);
182 e->setCollapsingRequirement(true);
183 }
184
185 // keep read lock to receive updates from others
186 return e;
187 }
188
189 StoreEntry *
190 Transients::findCollapsed(const sfileno index)
191 {
192 if (!map)
193 return NULL;
194
195 if (StoreEntry *oldE = locals->at(index)) {
196 debugs(20, 5, "found " << *oldE << " at " << index << " in " << MapLabel);
197 assert(oldE->mem_obj && oldE->mem_obj->xitTable.index == index);
198 return oldE;
199 }
200
201 debugs(20, 3, "no entry at " << index << " in " << MapLabel);
202 return NULL;
203 }
204
205 void
206 Transients::clearCollapsingRequirement(const StoreEntry &e)
207 {
208 assert(map);
209 assert(e.hasTransients());
210 assert(isWriter(e));
211 const auto idx = e.mem_obj->xitTable.index;
212 auto &anchor = map->writeableEntry(idx);
213 if (EBIT_TEST(anchor.basics.flags, ENTRY_REQUIRES_COLLAPSING)) {
214 EBIT_CLR(anchor.basics.flags, ENTRY_REQUIRES_COLLAPSING);
215 CollapsedForwarding::Broadcast(e);
216 }
217 }
218
219 void
220 Transients::monitorIo(StoreEntry *e, const cache_key *key, const Store::IoStatus direction)
221 {
222 if (!e->hasTransients()) {
223 addEntry(e, key, direction);
224 assert(e->hasTransients());
225 }
226
227 const auto index = e->mem_obj->xitTable.index;
228 if (const auto old = locals->at(index)) {
229 assert(old == e);
230 } else {
231 // We do not lock e because we do not want to prevent its destruction;
232 // e is tied to us via mem_obj so we will know when it is destructed.
233 locals->at(index) = e;
234 }
235 }
236
237 /// creates a new Transients entry
238 void
239 Transients::addEntry(StoreEntry *e, const cache_key *key, const Store::IoStatus direction)
240 {
241 assert(e);
242 assert(e->mem_obj);
243 assert(!e->hasTransients());
244
245 Must(map); // configured to track transients
246
247 sfileno index = 0;
248 Ipc::StoreMapAnchor *slot = map->openForWriting(key, index);
249 Must(slot); // no writer collisions
250
251 // set ASAP in hope to unlock the slot if something throws
252 e->mem_obj->xitTable.index = index;
253 e->mem_obj->xitTable.io = Store::ioWriting;
254
255 slot->set(*e, key);
256 if (direction == Store::ioWriting) {
257 // allow reading and receive remote DELETE events, but do not switch to
258 // the reading lock because transientReaders() callers want true readers
259 map->startAppending(index);
260 } else {
261 assert(direction == Store::ioReading);
262 // keep the entry locked (for reading) to receive remote DELETE events
263 map->switchWritingToReading(index);
264 e->mem_obj->xitTable.io = Store::ioReading;
265 }
266 }
267
268 bool
269 Transients::hasWriter(const StoreEntry &e)
270 {
271 if (!e.hasTransients())
272 return false;
273 return map->peekAtWriter(e.mem_obj->xitTable.index);
274 }
275
276 void
277 Transients::noteFreeMapSlice(const Ipc::StoreMapSliceId)
278 {
279 // TODO: we should probably find the entry being deleted and abort it
280 }
281
282 void
283 Transients::status(const StoreEntry &entry, Transients::EntryStatus &entryStatus) const
284 {
285 assert(map);
286 assert(entry.hasTransients());
287 const auto idx = entry.mem_obj->xitTable.index;
288 const auto &anchor = isWriter(entry) ?
289 map->writeableEntry(idx) : map->readableEntry(idx);
290 entryStatus.abortedByWriter = anchor.writerHalted;
291 entryStatus.waitingToBeFreed = anchor.waitingToBeFreed;
292 entryStatus.collapsed = EBIT_TEST(anchor.basics.flags, ENTRY_REQUIRES_COLLAPSING);
293 }
294
295 void
296 Transients::completeWriting(const StoreEntry &e)
297 {
298 assert(e.hasTransients());
299 assert(isWriter(e));
300 map->switchWritingToReading(e.mem_obj->xitTable.index);
301 e.mem_obj->xitTable.io = Store::ioReading;
302 }
303
304 int
305 Transients::readers(const StoreEntry &e) const
306 {
307 if (e.hasTransients()) {
308 assert(map);
309 return map->peekAtEntry(e.mem_obj->xitTable.index).lock.readers;
310 }
311 return 0;
312 }
313
314 void
315 Transients::evictCached(StoreEntry &e)
316 {
317 debugs(20, 5, e);
318 if (e.hasTransients()) {
319 const auto index = e.mem_obj->xitTable.index;
320 if (map->freeEntry(index)) {
321 // Delay syncCollapsed(index) which may end `e` wait for updates.
322 // Calling it directly/here creates complex reentrant call chains.
323 CollapsedForwarding::Broadcast(e, true);
324 }
325 } // else nothing to do because e must be private
326 }
327
328 void
329 Transients::evictIfFound(const cache_key *key)
330 {
331 if (!map)
332 return;
333
334 const sfileno index = map->fileNoByKey(key);
335 if (map->freeEntry(index))
336 CollapsedForwarding::Broadcast(index, true);
337 }
338
339 void
340 Transients::disconnect(StoreEntry &entry)
341 {
342 debugs(20, 5, entry);
343 if (entry.hasTransients()) {
344 auto &xitTable = entry.mem_obj->xitTable;
345 assert(map);
346 if (isWriter(entry)) {
347 map->abortWriting(xitTable.index);
348 } else {
349 assert(isReader(entry));
350 map->closeForReadingAndFreeIdle(xitTable.index);
351 }
352 locals->at(xitTable.index) = nullptr;
353 xitTable.index = -1;
354 xitTable.io = Store::ioDone;
355 }
356 }
357
358 /// calculates maximum number of entries we need to store and map
359 int64_t
360 Transients::EntryLimit()
361 {
362 return (UsingSmp() && Store::Controller::SmpAware()) ?
363 Config.shared_transient_entries_limit : 0;
364 }
365
366 bool
367 Transients::markedForDeletion(const cache_key *key) const
368 {
369 assert(map);
370 return map->markedForDeletion(key);
371 }
372
373 bool
374 Transients::isReader(const StoreEntry &e) const
375 {
376 return e.mem_obj && e.mem_obj->xitTable.io == Store::ioReading;
377 }
378
379 bool
380 Transients::isWriter(const StoreEntry &e) const
381 {
382 return e.mem_obj && e.mem_obj->xitTable.io == Store::ioWriting;
383 }
384
385 /// initializes shared memory segment used by Transients
386 class TransientsRr: public Ipc::Mem::RegisteredRunner
387 {
388 public:
389 /* RegisteredRunner API */
390 virtual void useConfig();
391 virtual ~TransientsRr();
392
393 protected:
394 virtual void create();
395
396 private:
397 TransientsMap::Owner *mapOwner = nullptr;
398 };
399
400 RunnerRegistrationEntry(TransientsRr);
401
402 void
403 TransientsRr::useConfig()
404 {
405 assert(Config.memShared.configured());
406 Ipc::Mem::RegisteredRunner::useConfig();
407 }
408
409 void
410 TransientsRr::create()
411 {
412 const int64_t entryLimit = Transients::EntryLimit();
413 if (entryLimit <= 0)
414 return; // no SMP configured or a misconfiguration
415
416 Must(!mapOwner);
417 mapOwner = TransientsMap::Init(MapLabel, entryLimit);
418 }
419
420 TransientsRr::~TransientsRr()
421 {
422 delete mapOwner;
423 }
424