Squid's extensive use of callback functions makes it very susceptible to memory access errors. 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:
type_of_data callback_data; ... callback_data = cbdataAlloc(type_of_data); ... cbdataLock(callback_data); fooOperationStart(bar, callback_func, callback_data); ... fooOperationComplete(...); if (cbdataValid(callback_data)) { callback_func(callback_data, ....); cbdataUnlock(callback_data); cbdataFree(callback_data);
With this scheme, nothing bad happens if cbdataFree
gets called
before cbdataUnlock
:
callback_data = cbdataAlloc(...); ... cbdataLock(callback_data); fooOperationStart(bar, callback_func, callback_data); ... cbdataFree(callback_data); ... fooOperationComplete(...); if (cbdataValid(callback_data)) { callback_func(callback_data, ....); cbdataUnlock(callback_data);In this case, when
cbdataFree
is called before
cbdataUnlock
, the callback_data gets marked as invalid. Before
executing the callback function, cbdataValid
will return 0
and callback_func is never executed. When cbdataUnlock
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 */