Squid's extensive use of callback functions makes it very susceptible to memory access errors. To address this all callback functions make use of a construct called "cbdata". This allows functions doing callbacks to verify that the caller is still valid before making the callback.
Note: cbdata is intended for callback data and is tailored specifically to make callbacks less dangerous leaving as few windows of errors as possible. It is not suitable or intended as a generic referencecounted memory allocator.
CBDATA_TYPE(datatype);
Macro that defines a new cbdata datatype. Similar to a variable or struct definition. Scope is always local to the file/block where it is defined and all allocations must be within this scope. Allocated entries referenced or freed anywhere with no restrictions on scope.
/* Module header file */ external CBDATA_GLOBAL_TYPE(datatype); /* Module main C file */ CBDATA_GLOBAL_TYPE(datatype);
Defines a global cbdata type that can be referenced anywhere in the code.
CBDATA_INIT_TYPE(datatype); /* or */ CBDATA_INIT_TYPE_FREECB(datatype, FREE *freehandler);
Initializes the cbdatatype. Must be called prior to the first use of cbdataAlloc() for the type.
The freehandler is called when the last known reference to a allocated entry goes away.
pointer = cbdataAlloc(datatype);
Allocates a new entry of a registered cbdata type.
cbdataFree(pointer);
Frees a entry allocated by cbdataAlloc().
Note: If there are active references to the entry then the entry will be freed with the last reference is removed. However, cbdataReferenceValid() will return false for those references.
reference = cbdataReference(pointer);
Creates a new reference to a cbdata entry. Used when you need to store a reference in another structure. The reference can later be verified for validity by cbdataReferenceValid().
Note: The reference variable is a pointer to the entry, in all aspects identical to the original pointer. But semantically it is quite different. It is best if the reference is thought of and handled as a "void *".
cbdataReferenceDone(reference);
Removes a reference created by cbdataReference().
Note: The reference variable will be automatically cleared to NULL.
if (cbdataReferenceValid(reference)) { ... }
cbdataReferenceValid() returns false if a reference is stale (refers to a entry freed by cbdataFree).
void *pointer; bool cbdataReferenceValidDone(reference, &pointer);
Removes a reference created by cbdataReference() and checks it for validity.
Meant to be used on the last dereference
void *cbdata; ... if (cbdataReferenceValidDone(reference, &cbdata)) != NULL) callback(..., cbdata);
Note: The reference variable will be automatically cleared to NULL.
For a blocking operation with callback functions, the normal sequence of events is as follows:
callback_data = malloc(...); ... fooOperationStart(bar, callback_func, callback_data); ... fooOperationComplete(...); callback_func(callback_data, ....); ... free(callback_data);However, things become more interesting if we want or need to free the callback_data, or otherwise cancel the callback, before the operation completes.
The callback data allocator lets us do this in a uniform and safe manner. The callback data allocator is used to allocate, track and free memory pool objects used during callback operations. Allocated memory is locked while the blocking operation executes elsewhere, and is freed when the operation completes. The normal sequence of events is:
/* initialization */ type_of_data callback_data; ... callback_data = cbdataAlloc(type_of_data); ... /* calling "foo" */ fooOperationStart(..., callback_func, callback_data); ... /* being destroyed */ cbdataFree(callback_data); /* foo */ void fooOperationStart(..., callback_func, void *callback_data) { void *local_pointer = cbdataReference(callback_data); .... } void fooOperationComplete(...) { void *cbdata; ... if (cbdataReferenceValidDone(local_pointer, &cbdata)) callback_func(...., cbdata); }
With this scheme, nothing bad happens if cbdataFree
gets called
before fooOperantionComplete(...).
callback_data = cbdataAlloc(...); ... fooOperationStart(bar, callback_func, callback_data); local_pointer = cbdataReference(callback_data); ... cbdataFree(callback_data); ... fooOperationComplete(...); void *cbdata; if (cbdataReferenceValidDone(local_pointer, cbdata)) callback_func(cbdata, ....);In this case, when
cbdataFree
is called before
cbdataReferenceValidDone
, the callback_data gets marked as invalid.
Before executing the callback function, cbdataReferenceValidDone
will return 0
and callback_func is never executed. When cbdataReferenceValidDone
gets
called, it notices that the callback_data is invalid and will
then call cbdataFree
.
To add new module specific data types to the allocator one uses the macros CBDATA_TYPE and CBDATA_INIT_TYPE. These creates a local cbdata definition (file or block scope). Any cbdataAlloc calls must be made within this scope. However, cbdataFree might be called from anywhere.
/* First the cbdata type needs to be defined in the module. This * is usually done at file scope, but it can also be local to a * function or block.. */ CBDATA_TYPE(type_of_data); /* Then in the code somewhere before the first allocation * (can be called multiple times with only a minimal overhead) */ CBDATA_INIT_TYPE(type_of_data); /* Or if a free function is associated with the data type */ CBDATA_INIT_TYPE_FREECB(type_of_data, free_function);
To add new global data types one have to add them to the cbdata_type enum in enums.h, and a corresponding CREATE_CBDATA call in cbdata.c:cbdataInit(). Or alternatively add a CBDATA_GLOBAL_TYPE definition to globals.h and use CBDATA_INIT_TYPE as described above.
extern CBDATA_GLOBAL_TYPE(type_of_data); /* CBDATA_UNDEF */