/*
- * $Id: cbdata.cc,v 1.14 1998/01/12 04:30:35 wessels Exp $
+ * $Id$
*
* DEBUG: section 45 Callback Data Registry
- * AUTHOR: Duane Wessels
+ * ORIGINAL AUTHOR: Duane Wessels
+ * Modified by Moez Mahfoudh (08/12/2000)
+ * History added by Robert Collins (2002-10-25)
*
- * SQUID Internet Object Cache http://squid.nlanr.net/Squid/
- * --------------------------------------------------------
+ * SQUID Web Proxy Cache http://www.squid-cache.org/
+ * ----------------------------------------------------------
*
- * Squid is the result of efforts by numerous individuals from the
- * Internet community. Development is led by Duane Wessels of the
- * National Laboratory for Applied Network Research and funded by
- * the National Science Foundation.
+ * Squid is the result of efforts by numerous individuals from
+ * the Internet community; see the CONTRIBUTORS file for full
+ * details. Many organizations have provided support for Squid's
+ * development; see the SPONSORS file for full details. Squid is
+ * Copyrighted (C) 2001 by the Regents of the University of
+ * California; see the COPYRIGHT file for full details. Squid
+ * incorporates software developed and/or copyrighted by other
+ * sources; see the CREDITS file for full details.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
- *
+ *
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
+ *
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
- * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- *
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
+ *
*/
-/*
+/**
+ \defgroup CBDATAInternal Callback Data Allocator Internals
+ \ingroup CBDATAAPI
+ *
* These routines manage a set of registered callback data pointers.
- * One of the easiest ways to make Squid coredump is to issue a
+ * One of the easiest ways to make Squid coredump is to issue a
* callback to for some data structure which has previously been
* freed. With these routines, we register (add) callback data
* pointers, lock them just before registering the callback function,
* validate them before issuing the callback, and then free them
* when finished.
- *
- * In terms of time, the sequence goes something like this:
- *
- * foo = xcalloc(sizeof(foo));
- * cbdataAdd(foo);
- * ...
- * cbdataLock(foo);
- * some_blocking_operation(..., callback_func, foo);
- * ...
- * some_blocking_operation_completes()
- * if (cbdataValid(foo))
- * callback_func(..., foo)
- * cbdataUnlock(foo);
- * ...
- * cbdataFree(foo);
- *
- * The nice thing is that, we do not need to require that Unlock
- * occurs before Free. If the Free happens first, then the
- * callback data is marked invalid and the callback will never
- * be made. When we Unlock and the lock count reaches zero,
- * we free the memory if it is marked invalid.
*/
-#include "squid.h"
+#include "cbdata.h"
+#include "CacheManager.h"
+#include "Store.h"
+#if CBDATA_DEBUG
+#include "Stack.h"
+#endif
+#include "Generic.h"
-static hash_table *htable = NULL;
+#if WITH_VALGRIND
+#define HASHED_CBDATA 1
+#endif
static int cbdataCount = 0;
+#if CBDATA_DEBUG
+dlink_list cbdataEntries;
+#endif
+
+#if CBDATA_DEBUG
+
+class CBDataCall
+{
+
+public:
+ CBDataCall (char const *callLabel, char const *aFile, int aLine) : label(callLabel), file(aFile), line(aLine) {}
+
+ char const *label;
+ char const *file;
+ int line;
+};
+
+#endif
+
+/// \ingroup CBDATAInternal
+#define OFFSET_OF(TYPE, MEMBER) ((size_t) &(((TYPE) *)0)->(MEMBER))
+
+/// \ingroup CBDATAInternal
+class cbdata
+{
+ /** \todo examine making cbdata templated on this - so we get type
+ * safe access to data - RBC 20030902 */
+public:
+#if HASHED_CBDATA
+ hash_link hash; // Must be first
+#endif
+
+#if CBDATA_DEBUG
+
+ void dump(StoreEntry *)const;
+#endif
+
+#if !HASHED_CBDATA
+ void *operator new(size_t size, void *where);
+ void operator delete(void *where, void *where2);
+#else
+ MEMPROXY_CLASS(cndata);
+#endif
-typedef struct _cbdata {
- const void *key;
- struct _cbdata *next;
+ ~cbdata();
int valid;
int locks;
- mem_type mem_type;
+ cbdata_type type;
#if CBDATA_DEBUG
+
+ void addHistory(char const *label, char const *file, int line) {
+ if (calls.size() > 1000)
+ return;
+
+ calls.push_back(new CBDataCall(label, file, line));
+ }
+
+ dlink_node link;
const char *file;
int line;
+ Stack<CBDataCall*> calls;
#endif
-} cbdata;
-static HASHCMP cbdata_cmp;
-static HASHHASH cbdata_hash;
+ /* cookie used while debugging */
+ long cookie;
+ void check(int line) const {assert(cookie == ((long)this ^ Cookie));}
+ static const long Cookie;
+
+#if !HASHED_CBDATA
+ size_t dataSize() const { return sizeof(data);}
+ static long MakeOffset();
+ static const long Offset;
+ /* MUST be the last per-instance member */
+ void *data;
+#endif
+
+};
+
+const long cbdata::Cookie((long)0xDEADBEEF);
+#if !HASHED_CBDATA
+const long cbdata::Offset(MakeOffset());
+
+void *
+cbdata::operator new(size_t size, void *where)
+{
+ // assert (size == sizeof(cbdata));
+ return where;
+}
+
+/**
+ * Only ever invoked when placement new throws
+ * an exception. Used to prevent an incorrect
+ * free.
+ */
+void
+cbdata::operator delete(void *where, void *where2)
+{
+ ; // empty.
+}
+
+long
+cbdata::MakeOffset()
+{
+ cbdata *zero = (cbdata *)0L;
+ void **dataOffset = &zero->data;
+ return (long)dataOffset;
+}
+#else
+MEMPROXY_CLASS_INLINE(cbdata);
+#endif
+
+static OBJH cbdataDump;
+#ifdef CBDATA_DEBUG
+static OBJH cbdataDumpHistory;
+#endif
+
+/// \ingroup CBDATAInternal
+struct CBDataIndex {
+ MemAllocator *pool;
+ FREE *free_func;
+}
+*cbdata_index = NULL;
+
+/// \ingroup CBDATAInternal
+int cbdata_types = 0;
+
+#if HASHED_CBDATA
+static hash_table *cbdata_htable = NULL;
static int
cbdata_cmp(const void *p1, const void *p2)
{
return ((unsigned long) p >> 8) % mod;
}
+#endif
-void
-cbdataInit(void)
+cbdata::~cbdata()
{
- debug(45, 3) ("cbdataInit\n");
- htable = hash_create(cbdata_cmp, 1 << 8, cbdata_hash);
+#if CBDATA_DEBUG
+ CBDataCall *aCall;
+
+ while ((aCall = calls.pop()))
+ delete aCall;
+
+#endif
+
+ FREE *free_func = cbdata_index[type].free_func;
+
+#if HASHED_CBDATA
+ void *p = hash.key;
+#else
+ void *p = &data;
+#endif
+
+ if (free_func)
+ free_func(p);
+}
+
+static void
+cbdataInternalInitType(cbdata_type type, const char *name, int size, FREE * free_func)
+{
+ char *label;
+ assert (type == cbdata_types + 1);
+
+ cbdata_index = (CBDataIndex *)xrealloc(cbdata_index, (type + 1) * sizeof(*cbdata_index));
+ memset(&cbdata_index[type], 0, sizeof(*cbdata_index));
+ cbdata_types = type;
+
+ label = (char *)xmalloc(strlen(name) + 20);
+
+ snprintf(label, strlen(name) + 20, "cbdata %s (%d)", name, (int) type);
+
+#if !HASHED_CBDATA
+ assert((size_t)cbdata::Offset == (sizeof(cbdata) - ((cbdata *)NULL)->dataSize()));
+ size += cbdata::Offset;
+#endif
+
+ cbdata_index[type].pool = memPoolCreate(label, size);
+
+ cbdata_index[type].free_func = free_func;
+
+#if HASHED_CBDATA
+ if (!cbdata_htable)
+ cbdata_htable = hash_create(cbdata_cmp, 1 << 12, cbdata_hash);
+#endif
+}
+
+cbdata_type
+cbdataInternalAddType(cbdata_type type, const char *name, int size, FREE * free_func)
+{
+ if (type)
+ return type;
+
+ type = (cbdata_type)(cbdata_types + 1);
+
+ cbdataInternalInitType(type, name, size, free_func);
+
+ return type;
}
void
+cbdataRegisterWithCacheManager(void)
+{
+ CacheManager *manager=CacheManager::GetInstance();
+ manager->registerAction("cbdata",
+ "Callback Data Registry Contents",
+ cbdataDump, 0, 1);
#if CBDATA_DEBUG
-cbdataAddDbg(const void *p, mem_type, const char *file, int line)
+
+ manager->registerAction("cbdatahistory",
+ "Detailed call history for all current cbdata contents",
+ cbdataDumpHistory, 0, 1);
+#endif
+}
+
+void *
+#if CBDATA_DEBUG
+cbdataInternalAllocDbg(cbdata_type type, const char *file, int line)
#else
-cbdataAdd(const void *p, mem_type mem_type)
+cbdataInternalAlloc(cbdata_type type)
#endif
{
cbdata *c;
- assert(p);
- debug(45, 3) ("cbdataAdd: %p\n", p);
- assert(htable != NULL);
- assert(hash_lookup(htable, p) == NULL);
- c = xcalloc(1, sizeof(cbdata));
- c->key = p;
+ void *p;
+ assert(type > 0 && type <= cbdata_types);
+ /* placement new: the pool alloc gives us cbdata + user type memory space
+ * and we init it with cbdata at the start of it
+ */
+#if HASHED_CBDATA
+ c = new cbdata;
+ p = cbdata_index[type].pool->alloc();
+ c->hash.key = p;
+ hash_join(cbdata_htable, &c->hash);
+#else
+ c = new (cbdata_index[type].pool->alloc()) cbdata;
+ p = (void *)&c->data;
+#endif
+
+ c->type = type;
c->valid = 1;
- c->mem_type = mem_type;
+ c->locks = 0;
+ c->cookie = (long) c ^ cbdata::Cookie;
+ cbdataCount++;
#if CBDATA_DEBUG
+
c->file = file;
c->line = line;
+ c->calls = Stack<CBDataCall *> ();
+ c->addHistory("Alloc", file, line);
+ dlinkAdd(c, &c->link, &cbdataEntries);
+ debugs(45, 3, "cbdataAlloc: " << p << " " << file << ":" << line);
#endif
- hash_join(htable, (hash_link *) c);
- cbdataCount++;
+
+ return p;
}
-void
-cbdataFree(void *p)
+void *
+#if CBDATA_DEBUG
+cbdataInternalFreeDbg(void *p, const char *file, int line)
+#else
+cbdataInternalFree(void *p)
+#endif
{
- cbdata *c = (cbdata *) hash_lookup(htable, p);
- mem_type mem_type;
- assert(p);
- debug(45, 3) ("cbdataFree: %p\n", p);
- assert(c != NULL);
+ cbdata *c;
+#if HASHED_CBDATA
+ c = (cbdata *) hash_lookup(cbdata_htable, p);
+#else
+ c = (cbdata *) (((char *) p) - cbdata::Offset);
+#endif
+#if CBDATA_DEBUG
+
+ debugs(45, 3, "cbdataFree: " << p << " " << file << ":" << line);
+#else
+
+ debugs(45, 9, "cbdataFree: " << p);
+#endif
+
+ c->check(__LINE__);
+ assert(c->valid);
c->valid = 0;
+#if CBDATA_DEBUG
+
+ c->addHistory("Free", file, line);
+#endif
+
if (c->locks) {
- debug(45, 3) ("cbdataFree: %p has %d locks, not freeing\n",
- p, c->locks);
- return;
+ debugs(45, 9, "cbdataFree: " << p << " has " << c->locks << " locks, not freeing");
+ return NULL;
}
- hash_remove_link(htable, (hash_link *) c);
+
cbdataCount--;
- mem_type = c->mem_type;
- xfree(c);
- debug(45, 3) ("cbdataFree: freeing %p\n", p);
- if (mem_type == MEM_NONE)
- xfree(p);
- else
- memFree(mem_type, p);
+ debugs(45, 9, "cbdataFree: Freeing " << p);
+#if CBDATA_DEBUG
+
+ dlinkDelete(&c->link, &cbdataEntries);
+#endif
+
+ /* This is ugly. But: operator delete doesn't get
+ * the type parameter, so we can't use that
+ * to free the memory.
+ * So, we free it ourselves.
+ * Note that this means a non-placement
+ * new would be a seriously bad idea.
+ * Lastly, if we where a templated class,
+ * we could use the normal delete operator
+ * and it would Just Work. RBC 20030902
+ */
+ cbdata_type theType = c->type;
+#if HASHED_CBDATA
+ hash_remove_link(cbdata_htable, &c->hash);
+ delete c;
+ cbdata_index[theType].pool->free((void *)p);
+#else
+ c->cbdata::~cbdata();
+ cbdata_index[theType].pool->free(c);
+#endif
+ return NULL;
}
void
-cbdataLock(const void *p)
+#if CBDATA_DEBUG
+cbdataInternalLockDbg(const void *p, const char *file, int line)
+#else
+cbdataInternalLock(const void *p)
+#endif
{
cbdata *c;
+
if (p == NULL)
- return;
- c = (cbdata *) hash_lookup(htable, p);
- debug(45, 3) ("cbdataLock: %p\n", p);
- assert(c != NULL);
+ return;
+
+#if HASHED_CBDATA
+ c = (cbdata *) hash_lookup(cbdata_htable, p);
+#else
+ c = (cbdata *) (((char *) p) - cbdata::Offset);
+#endif
+
+#if CBDATA_DEBUG
+
+ debugs(45, 3, "cbdataLock: " << p << "=" << (c ? c->locks + 1 : -1) << " " << file << ":" << line);
+
+ c->addHistory("Reference", file, line);
+
+#else
+
+ debugs(45, 9, "cbdataLock: " << p << "=" << (c ? c->locks + 1 : -1));
+
+#endif
+
+ c->check(__LINE__);
+
+ assert(c->locks < 65535);
+
c->locks++;
}
void
-cbdataUnlock(const void *p)
+#if CBDATA_DEBUG
+cbdataInternalUnlockDbg(const void *p, const char *file, int line)
+#else
+cbdataInternalUnlock(const void *p)
+#endif
{
cbdata *c;
+
if (p == NULL)
- return;
- c = (cbdata *) hash_lookup(htable, p);
- debug(45, 3) ("cbdataUnlock: %p\n", p);
+ return;
+
+#if HASHED_CBDATA
+ c = (cbdata *) hash_lookup(cbdata_htable, p);
+#else
+ c = (cbdata *) (((char *) p) - cbdata::Offset);
+#endif
+
+#if CBDATA_DEBUG
+
+ debugs(45, 3, "cbdataUnlock: " << p << "=" << (c ? c->locks - 1 : -1) << " " << file << ":" << line);
+
+ c->addHistory("Dereference", file, line);
+
+#else
+
+ debugs(45, 9, "cbdataUnlock: " << p << "=" << (c ? c->locks - 1 : -1));
+
+#endif
+
+ c->check(__LINE__);
+
assert(c != NULL);
+
assert(c->locks > 0);
+
c->locks--;
+
if (c->valid || c->locks)
- return;
- hash_remove_link(htable, (hash_link *) c);
+ return;
+
cbdataCount--;
- xfree(c);
- debug(45, 3) ("cbdataUnlock: Freeing %p\n", p);
- xfree((void *) p);
+
+ debugs(45, 9, "cbdataUnlock: Freeing " << p);
+
+#if CBDATA_DEBUG
+
+ dlinkDelete(&c->link, &cbdataEntries);
+
+#endif
+
+ /* This is ugly. But: operator delete doesn't get
+ * the type parameter, so we can't use that
+ * to free the memory.
+ * So, we free it ourselves.
+ * Note that this means a non-placement
+ * new would be a seriously bad idea.
+ * Lastly, if we where a templated class,
+ * we could use the normal delete operator
+ * and it would Just Work. RBC 20030902
+ */
+ cbdata_type theType = c->type;
+#if HASHED_CBDATA
+ hash_remove_link(cbdata_htable, &c->hash);
+ delete c;
+ cbdata_index[theType].pool->free((void *)p);
+#else
+ c->cbdata::~cbdata();
+ cbdata_index[theType].pool->free(c);
+#endif
}
int
-cbdataValid(const void *p)
+cbdataReferenceValid(const void *p)
{
cbdata *c;
+
if (p == NULL)
- return 0;
- c = (cbdata *) hash_lookup(htable, p);
- debug(45, 3) ("cbdataValid: %p\n", p);
- assert(c != NULL);
+ return 1; /* A NULL pointer cannot become invalid */
+
+ debugs(45, 9, "cbdataReferenceValid: " << p);
+
+#if HASHED_CBDATA
+ c = (cbdata *) hash_lookup(cbdata_htable, p);
+#else
+ c = (cbdata *) (((char *) p) - cbdata::Offset);
+#endif
+
+ c->check(__LINE__);
+
assert(c->locks > 0);
+
return c->valid;
}
+int
+#if CBDATA_DEBUG
+cbdataInternalReferenceDoneValidDbg(void **pp, void **tp, const char *file, int line)
+#else
+cbdataInternalReferenceDoneValid(void **pp, void **tp)
+#endif
+{
+ void *p = (void *) *pp;
+ int valid = cbdataReferenceValid(p);
+ *pp = NULL;
+#if CBDATA_DEBUG
+
+ cbdataInternalUnlockDbg(p, file, line);
+#else
+
+ cbdataInternalUnlock(p);
+#endif
+
+ if (valid) {
+ *tp = p;
+ return 1;
+ } else {
+ *tp = NULL;
+ return 0;
+ }
+}
+#if CBDATA_DEBUG
void
+cbdata::dump(StoreEntry *sentry) const
+{
+#if HASHED_CBDATA
+ void *p = (void *)hash.key;
+#else
+ void *p = (void *)&data;
+#endif
+ storeAppendPrintf(sentry, "%c%p\t%d\t%d\t%20s:%-5d\n", valid ? ' ' :
+ '!', p, type, locks, file, line);
+}
+
+struct CBDataDumper : public unary_function<cbdata, void> {
+ CBDataDumper(StoreEntry *anEntry):where(anEntry) {}
+
+ void operator()(cbdata const &x) {
+ x.dump(where);
+ }
+
+ StoreEntry *where;
+};
+
+#endif
+
+static void
cbdataDump(StoreEntry * sentry)
{
- hash_link *hptr;
- cbdata *c;
storeAppendPrintf(sentry, "%d cbdata entries\n", cbdataCount);
- for (hptr = hash_first(htable); hptr; hptr = hash_next(htable)) {
- c = (cbdata *) hptr;
#if CBDATA_DEBUG
- storeAppendPrintf(sentry, "%20p %10s %d locks %s:%d\n",
- c->key,
- c->valid ? "VALID" : "NOT VALID",
- c->locks,
- c->file, c->line);
+
+ storeAppendPrintf(sentry, "Pointer\tType\tLocks\tAllocated by\n");
+ CBDataDumper dumper(sentry);
+ for_each (cbdataEntries, dumper);
+ storeAppendPrintf(sentry, "\n");
+ storeAppendPrintf(sentry, "types\tsize\tallocated\ttotal\n");
+
+ for (int i = 1; i < cbdata_types; i++) {
+ MemAllocator *pool = cbdata_index[i].pool;
+
+ if (pool) {
+#if HASHED_CBDATA
+ int obj_size = pool->objectSize();
#else
- storeAppendPrintf(sentry, "%20p %10s %d locks\n",
- c->key,
- c->valid ? "VALID" : "NOT VALID",
- c->locks);
+ int obj_size = pool->objectSize() - cbdata::Offset;
#endif
+ storeAppendPrintf(sentry, "%s\t%d\t%ld\t%ld\n", pool->objectType() + 7, obj_size, (long int)pool->getMeter().inuse.level, (long int)obj_size * pool->getMeter().inuse.level);
+ }
}
+
+#else
+ storeAppendPrintf(sentry, "detailed allocation information only available when compiled with CBDATA_DEBUG\n");
+
+#endif
+
+ storeAppendPrintf(sentry, "\nsee also \"Memory utilization\" for detailed per type statistics\n");
}
+
+CBDATA_CLASS_INIT(generic_cbdata);
+
+#if CBDATA_DEBUG
+
+struct CBDataCallDumper : public unary_function<CBDataCall, void> {
+ CBDataCallDumper (StoreEntry *anEntry):where(anEntry) {}
+
+ void operator()(CBDataCall const &x) {
+ storeAppendPrintf(where, "%s\t%s\t%d\n", x.label, x.file, x.line);
+ }
+
+ StoreEntry *where;
+};
+
+struct CBDataHistoryDumper : public CBDataDumper {
+ CBDataHistoryDumper(StoreEntry *anEntry):CBDataDumper(anEntry),where(anEntry), callDumper(anEntry) {}
+
+ void operator()(cbdata const &x) {
+ CBDataDumper::operator()(x);
+ storeAppendPrintf(where, "\n");
+ storeAppendPrintf(where, "Action\tFile\tLine\n");
+ for_each (x.calls,callDumper);
+ storeAppendPrintf(where, "\n");
+ }
+
+ StoreEntry *where;
+ CBDataCallDumper callDumper;
+};
+
+void
+cbdataDumpHistory(StoreEntry *sentry)
+{
+ storeAppendPrintf(sentry, "%d cbdata entries\n", cbdataCount);
+ storeAppendPrintf(sentry, "Pointer\tType\tLocks\tAllocated by\n");
+ CBDataHistoryDumper dumper(sentry);
+ for_each (cbdataEntries, dumper);
+}
+
+#endif