Next Previous Contents

20. Callback Data Allocator

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 */


Next Previous Contents