Next Previous Contents

20. Callback Data Allocator

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.

20.1 API

CBDATA_TYPE

        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.

CBDATA_GLOBAL_TYPE

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

        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.

cbdataAlloc

        pointer = cbdataAlloc(datatype);

Allocates a new entry of a registered cbdata type.

cbdataFree

        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.

cbdataReference

        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

        cbdataReferenceDone(reference);

Removes a reference created by cbdataReference().

Note: The reference variable will be automatically cleared to NULL.

cbdataReferenceValid

        if (cbdataReferenceValid(reference)) {
            ...
        }

cbdataReferenceValid() returns false if a reference is stale (refers to a entry freed by cbdataFree).

cbdataReferenceValidDone

        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.

20.2 Examples

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


Next Previous Contents