]> git.ipfire.org Git - thirdparty/squid.git/blob - src/cbdata.cc
Docs: Copyright updates for 2018 (#114)
[thirdparty/squid.git] / src / cbdata.cc
1 /*
2 * Copyright (C) 1996-2018 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 45 Callback Data Registry */
10
11 #include "squid.h"
12 #include "cbdata.h"
13 #include "Generic.h"
14 #include "mem/Pool.h"
15 #include "mgr/Registration.h"
16 #include "Store.h"
17
18 #include <climits>
19
20 #if USE_CBDATA_DEBUG
21 #include <algorithm>
22 #include <vector>
23 #endif
24
25 #if WITH_VALGRIND
26 #include <map>
27 #endif
28
29 static int cbdataCount = 0;
30 #if USE_CBDATA_DEBUG
31 dlink_list cbdataEntries;
32 #endif
33
34 #if USE_CBDATA_DEBUG
35
36 class CBDataCall
37 {
38
39 public:
40 CBDataCall (char const *callLabel, char const *aFile, int aLine) : label(callLabel), file(aFile), line(aLine) {}
41
42 char const *label;
43 char const *file;
44 int line;
45 };
46
47 #endif
48
49 #define OFFSET_OF(TYPE, MEMBER) ((size_t) &(((TYPE) *)0)->(MEMBER))
50
51 /**
52 * Manage a set of registered callback data pointers.
53 * One of the easiest ways to make Squid coredump is to issue a
54 * callback to for some data structure which has previously been
55 * freed. With this class, we register (add) callback data
56 * pointers, lock them just before registering the callback function,
57 * validate them before issuing the callback, and then free them
58 * when finished.
59 */
60 class cbdata
61 {
62 #if !WITH_VALGRIND
63 public:
64 void *operator new(size_t, void *where) {return where;}
65 /**
66 * Only ever invoked when placement new throws
67 * an exception. Used to prevent an incorrect free.
68 */
69 void operator delete(void *, void *) {}
70 #else
71 MEMPROXY_CLASS(cbdata);
72 #endif
73
74 /** \todo examine making cbdata templated on this - so we get type
75 * safe access to data - RBC 20030902 */
76 public:
77 #if USE_CBDATA_DEBUG
78
79 void dump(StoreEntry *)const;
80 #endif
81 cbdata() :
82 valid(0),
83 locks(0),
84 type(CBDATA_UNKNOWN),
85 #if USE_CBDATA_DEBUG
86 file(NULL),
87 line(0),
88 #endif
89 cookie(0),
90 data(NULL)
91 {}
92 ~cbdata();
93
94 int valid;
95 int32_t locks;
96 cbdata_type type;
97 #if USE_CBDATA_DEBUG
98
99 void addHistory(char const *label, char const *aFile, int aLine) {
100 if (calls.size() > 1000)
101 return;
102
103 calls.push_back(new CBDataCall(label, aFile, aLine));
104 }
105
106 dlink_node link;
107 const char *file;
108 int line;
109 std::vector<CBDataCall*> calls; // used as a stack with random access operator
110 #endif
111
112 /* cookie used while debugging */
113 long cookie;
114 void check(int) const {assert(cookie == ((long)this ^ Cookie));}
115 static const long Cookie;
116
117 #if !WITH_VALGRIND
118 size_t dataSize() const { return sizeof(data);}
119 static long MakeOffset();
120 static const long Offset;
121 #endif
122 /* MUST be the last per-instance member */
123 void *data;
124 };
125
126 const long cbdata::Cookie((long)0xDEADBEEF);
127 #if !WITH_VALGRIND
128 const long cbdata::Offset(MakeOffset());
129
130 long
131 cbdata::MakeOffset()
132 {
133 cbdata *zero = (cbdata *)0L;
134 void **dataOffset = &zero->data;
135 return (long)dataOffset;
136 }
137 #endif
138
139 static OBJH cbdataDump;
140 #if USE_CBDATA_DEBUG
141 static OBJH cbdataDumpHistory;
142 #endif
143
144 struct CBDataIndex {
145 MemAllocator *pool;
146 }
147 *cbdata_index = NULL;
148
149 int cbdata_types = 0;
150
151 #if WITH_VALGRIND
152 static std::map<const void *, cbdata *> cbdata_htable;
153 #endif
154
155 cbdata::~cbdata()
156 {
157 #if USE_CBDATA_DEBUG
158
159 while (!calls.empty()) {
160 delete calls.back();
161 calls.pop_back();
162 }
163
164 #endif
165
166 #if WITH_VALGRIND
167 void *p = data;
168 #else
169 void *p = this;
170 #endif
171 cbdata_index[type].pool->freeOne(p);
172 }
173
174 static void
175 cbdataInternalInitType(cbdata_type type, const char *name, int size)
176 {
177 char *label;
178 assert (type == cbdata_types + 1);
179
180 cbdata_index = (CBDataIndex *)xrealloc(cbdata_index, (type + 1) * sizeof(*cbdata_index));
181 memset(&cbdata_index[type], 0, sizeof(*cbdata_index));
182 cbdata_types = type;
183
184 label = (char *)xmalloc(strlen(name) + 20);
185
186 snprintf(label, strlen(name) + 20, "cbdata %s (%d)", name, (int) type);
187
188 #if !WITH_VALGRIND
189 assert((size_t)cbdata::Offset == (sizeof(cbdata) - ((cbdata *)NULL)->dataSize()));
190 size += cbdata::Offset;
191 #endif
192
193 cbdata_index[type].pool = memPoolCreate(label, size);
194 }
195
196 cbdata_type
197 cbdataInternalAddType(cbdata_type type, const char *name, int size)
198 {
199 if (type)
200 return type;
201
202 type = (cbdata_type)(cbdata_types + 1);
203
204 cbdataInternalInitType(type, name, size);
205
206 return type;
207 }
208
209 void
210 cbdataRegisterWithCacheManager(void)
211 {
212 Mgr::RegisterAction("cbdata",
213 "Callback Data Registry Contents",
214 cbdataDump, 0, 1);
215 #if USE_CBDATA_DEBUG
216
217 Mgr::RegisterAction("cbdatahistory",
218 "Detailed call history for all current cbdata contents",
219 cbdataDumpHistory, 0, 1);
220 #endif
221 }
222
223 void *
224 cbdataInternalAlloc(cbdata_type type, const char *file, int line)
225 {
226 cbdata *c;
227 void *p;
228 assert(type > 0 && type <= cbdata_types);
229 /* placement new: the pool alloc gives us cbdata + user type memory space
230 * and we init it with cbdata at the start of it
231 */
232 #if WITH_VALGRIND
233 c = new cbdata;
234 p = cbdata_index[type].pool->alloc();
235 c->data = p;
236 cbdata_htable.emplace(p,c);
237 #else
238 c = new (cbdata_index[type].pool->alloc()) cbdata;
239 p = (void *)&c->data;
240 #endif
241
242 c->type = type;
243 c->valid = 1;
244 c->locks = 0;
245 c->cookie = (long) c ^ cbdata::Cookie;
246 ++cbdataCount;
247 #if USE_CBDATA_DEBUG
248
249 c->file = file;
250 c->line = line;
251 c->calls = std::vector<CBDataCall *> ();
252 c->addHistory("Alloc", file, line);
253 dlinkAdd(c, &c->link, &cbdataEntries);
254 debugs(45, 3, "Allocating " << p << " " << file << ":" << line);
255 #else
256 debugs(45, 9, "Allocating " << p);
257 #endif
258
259 return p;
260 }
261
262 void
263 cbdataRealFree(cbdata *c, const char *file, const int line)
264 {
265 #if WITH_VALGRIND
266 void *p = c->data;
267 #else
268 void *p = (void *)&c->data;
269 #endif
270
271 --cbdataCount;
272 #if USE_CBDATA_DEBUG
273 debugs(45, 3, "Freeing " << p << ' ' << file << ':' << line);
274 dlinkDelete(&c->link, &cbdataEntries);
275 #else
276 debugs(45, 9, "Freeing " << p);
277 #endif
278
279 #if WITH_VALGRIND
280 cbdata_htable.erase(p);
281 delete c;
282 #else
283 /* This is ugly. But: operator delete doesn't get
284 * the type parameter, so we can't use that
285 * to free the memory.
286 * So, we free it ourselves.
287 * Note that this means a non-placement
288 * new would be a seriously bad idea.
289 * Lastly, if we where a templated class,
290 * we could use the normal delete operator
291 * and it would Just Work. RBC 20030902
292 */
293 c->cbdata::~cbdata();
294 #endif
295 }
296
297 void *
298 cbdataInternalFree(void *p, const char *file, int line)
299 {
300 cbdata *c;
301 #if WITH_VALGRIND
302 c = cbdata_htable.at(p);
303 #else
304 c = (cbdata *) (((char *) p) - cbdata::Offset);
305 #endif
306 #if USE_CBDATA_DEBUG
307 debugs(45, 3, p << " " << file << ":" << line);
308 #else
309 debugs(45, 9, p);
310 #endif
311
312 c->check(__LINE__);
313 assert(c->valid);
314 c->valid = 0;
315 #if USE_CBDATA_DEBUG
316
317 c->addHistory("Free", file, line);
318 #endif
319
320 if (c->locks) {
321 debugs(45, 9, p << " has " << c->locks << " locks, not freeing");
322 return NULL;
323 }
324
325 cbdataRealFree(c, file, line);
326 return NULL;
327 }
328
329 void
330 #if USE_CBDATA_DEBUG
331 cbdataInternalLockDbg(const void *p, const char *file, int line)
332 #else
333 cbdataInternalLock(const void *p)
334 #endif
335 {
336 cbdata *c;
337
338 if (p == NULL)
339 return;
340
341 #if WITH_VALGRIND
342 c = cbdata_htable.at(p);
343 #else
344 c = (cbdata *) (((char *) p) - cbdata::Offset);
345 #endif
346
347 #if USE_CBDATA_DEBUG
348 debugs(45, 3, p << "=" << (c ? c->locks + 1 : -1) << " " << file << ":" << line);
349 c->addHistory("Reference", file, line);
350 #else
351 debugs(45, 9, p << "=" << (c ? c->locks + 1 : -1));
352 #endif
353
354 c->check(__LINE__);
355
356 assert(c->locks < INT_MAX);
357
358 ++ c->locks;
359 }
360
361 void
362 #if USE_CBDATA_DEBUG
363 cbdataInternalUnlockDbg(const void *p, const char *file, int line)
364 #else
365 cbdataInternalUnlock(const void *p)
366 #endif
367 {
368 cbdata *c;
369
370 if (p == NULL)
371 return;
372
373 #if WITH_VALGRIND
374 c = cbdata_htable.at(p);
375 #else
376 c = (cbdata *) (((char *) p) - cbdata::Offset);
377 #endif
378
379 #if USE_CBDATA_DEBUG
380 debugs(45, 3, p << "=" << (c ? c->locks - 1 : -1) << " " << file << ":" << line);
381 c->addHistory("Dereference", file, line);
382 #else
383 debugs(45, 9, p << "=" << (c ? c->locks - 1 : -1));
384 #endif
385
386 c->check(__LINE__);
387
388 assert(c != NULL);
389
390 assert(c->locks > 0);
391
392 -- c->locks;
393
394 if (c->locks)
395 return;
396
397 if (c->valid) {
398 #if USE_CBDATA_DEBUG
399 debugs(45, 3, "CBDATA valid with no references ... cbdata=" << p << " " << file << ":" << line);
400 #endif
401 return;
402 }
403
404 #if USE_CBDATA_DEBUG
405 cbdataRealFree(c, file, line);
406 #else
407 cbdataRealFree(c, NULL, 0);
408 #endif
409 }
410
411 int
412 cbdataReferenceValid(const void *p)
413 {
414 cbdata *c;
415
416 if (p == NULL)
417 return 1; /* A NULL pointer cannot become invalid */
418
419 debugs(45, 9, p);
420
421 #if WITH_VALGRIND
422 c = cbdata_htable.at(p);
423 #else
424 c = (cbdata *) (((char *) p) - cbdata::Offset);
425 #endif
426
427 c->check(__LINE__);
428
429 assert(c->locks > 0);
430
431 return c->valid;
432 }
433
434 int
435 #if USE_CBDATA_DEBUG
436 cbdataInternalReferenceDoneValidDbg(void **pp, void **tp, const char *file, int line)
437 #else
438 cbdataInternalReferenceDoneValid(void **pp, void **tp)
439 #endif
440 {
441 void *p = (void *) *pp;
442 int valid = cbdataReferenceValid(p);
443 *pp = NULL;
444 #if USE_CBDATA_DEBUG
445
446 cbdataInternalUnlockDbg(p, file, line);
447 #else
448
449 cbdataInternalUnlock(p);
450 #endif
451
452 if (valid) {
453 *tp = p;
454 return 1;
455 } else {
456 *tp = NULL;
457 return 0;
458 }
459 }
460
461 #if USE_CBDATA_DEBUG
462 void
463 cbdata::dump(StoreEntry *sentry) const
464 {
465 #if WITH_VALGRIND
466 void *p = data;
467 #else
468 void *p = (void *)&data;
469 #endif
470 storeAppendPrintf(sentry, "%c%p\t%d\t%d\t%20s:%-5d\n", valid ? ' ' :
471 '!', p, type, locks, file, line);
472 }
473
474 struct CBDataDumper : public unary_function<cbdata, void> {
475 CBDataDumper(StoreEntry *anEntry):where(anEntry) {}
476
477 void operator()(cbdata const &x) {
478 x.dump(where);
479 }
480
481 StoreEntry *where;
482 };
483
484 #endif
485
486 static void
487 cbdataDump(StoreEntry * sentry)
488 {
489 storeAppendPrintf(sentry, "%d cbdata entries\n", cbdataCount);
490 #if USE_CBDATA_DEBUG
491
492 storeAppendPrintf(sentry, "Pointer\tType\tLocks\tAllocated by\n");
493 CBDataDumper dumper(sentry);
494 for_each (cbdataEntries, dumper);
495 storeAppendPrintf(sentry, "\n");
496 storeAppendPrintf(sentry, "types\tsize\tallocated\ttotal\n");
497
498 for (int i = 1; i < cbdata_types; ++i) {
499 MemAllocator *pool = cbdata_index[i].pool;
500
501 if (pool) {
502 #if WITH_VALGRIND
503 int obj_size = pool->objectSize();
504 #else
505 int obj_size = pool->objectSize() - cbdata::Offset;
506 #endif
507 storeAppendPrintf(sentry, "%s\t%d\t%ld\t%ld\n", pool->objectType() + 7, obj_size, (long int)pool->getMeter().inuse.currentLevel(), (long int)obj_size * pool->getMeter().inuse.currentLevel());
508 }
509 }
510
511 #else
512 storeAppendPrintf(sentry, "detailed allocation information only available when compiled with --enable-debug-cbdata\n");
513
514 #endif
515
516 storeAppendPrintf(sentry, "\nsee also \"Memory utilization\" for detailed per type statistics\n");
517 }
518
519 CallbackData &
520 CallbackData::operator =(const CallbackData &other)
521 {
522 if (data_ != other.data_) { // assignment to self and no-op assignments
523 auto old = data_;
524 data_ = cbdataReference(other.data_);
525 cbdataReferenceDone(old);
526 }
527 return *this;
528 }
529
530 CBDATA_CLASS_INIT(generic_cbdata);
531
532 #if USE_CBDATA_DEBUG
533
534 struct CBDataCallDumper : public unary_function<CBDataCall, void> {
535 CBDataCallDumper (StoreEntry *anEntry):where(anEntry) {}
536
537 void operator()(CBDataCall * const &x) {
538 storeAppendPrintf(where, "%s\t%s\t%d\n", x->label, x->file, x->line);
539 }
540
541 StoreEntry *where;
542 };
543
544 struct CBDataHistoryDumper : public CBDataDumper {
545 CBDataHistoryDumper(StoreEntry *anEntry):CBDataDumper(anEntry),where(anEntry), callDumper(anEntry) {}
546
547 void operator()(cbdata const &x) {
548 CBDataDumper::operator()(x);
549 storeAppendPrintf(where, "\n");
550 storeAppendPrintf(where, "Action\tFile\tLine\n");
551 std::for_each (x.calls.begin(), x.calls.end(), callDumper);
552 storeAppendPrintf(where, "\n");
553 }
554
555 StoreEntry *where;
556 CBDataCallDumper callDumper;
557 };
558
559 void
560 cbdataDumpHistory(StoreEntry *sentry)
561 {
562 storeAppendPrintf(sentry, "%d cbdata entries\n", cbdataCount);
563 storeAppendPrintf(sentry, "Pointer\tType\tLocks\tAllocated by\n");
564 CBDataHistoryDumper dumper(sentry);
565 for_each (cbdataEntries, dumper);
566 }
567
568 #endif
569