🔗 回调数据分配器
Squid广泛使用回调函数,使其非常容易出现内存访问错误。为了解决这个问题,所有回调函数都使用一个名为“cbdata”的结构。这允许进行回调的函数在执行回调之前验证调用方仍然有效。
注意:cbdata用于回调数据,并专门用于减少回调的危险性,尽量减少错误的可能性。它不适用于或不打算作为通用的引用计数内存分配器。
🔗 API
🔗 CBDATA_TYPE
CBDATA_TYPE(datatype);
定义一个新的cbdata数据类型的宏。类似于变量或结构定义。范围始终局限于定义它的文件/块。为此类型的所有 cbdataAlloc 调用必须与 CBDATA_TYPE 声明在同一作用域内。分配的条目可以在任何地方被引用或释放,不受范围限制。
🔗 CBDATA_GLOBAL_TYPE
/* Module header file */
external CBDATA_GLOBAL_TYPE(datatype);
/* Module main C file */
CBDATA_GLOBAL_TYPE(datatype);
定义一个可以在代码中任何位置引用的全局 cbdata 类型。
🔗 CBDATA_INIT_TYPE
CBDATA_INIT_TYPE(datatype);
/* or */
CBDATA_INIT_TYPE_FREECB(datatype, FREE *freehandler);
初始化 cbdatatype。必须在首次使用该类型的 cbdataAlloc() 之前调用。
当分配的条目的最后一个已知引用消失时,将调用 freehandler。
🔗 cbdataAlloc
pointer = cbdataAlloc(datatype);
为已注册的 cbdata 类型分配一个新条目。
🔗 cbdataFree
cbdataFree(pointer);
释放由 cbdataAlloc() 分配的条目。
注意:如果条目存在活动的引用,则在移除最后一个引用时将释放该条目。但是,cbdataReferenceValid() 将对这些引用返回 false。
🔗 cbdataReference
reference = cbdataReference(pointer);
创建对 cbdata 条目的新引用。当您需要将引用存储在另一个结构中时使用。之后可以通过 cbdataReferenceValid() 验证引用的有效性。
注意:引用变量是指向条目的指针,在所有方面与原始指针相同。但语义上却大不相同。最好将引用视为“void *”并以此处理。
🔗 cbdataReferenceDone
cbdataReferenceDone(reference);
移除由 cbdataReference() 创建的引用。
注意:引用变量将自动清除为 NULL。
🔗 cbdataReferenceValid
if (cbdataReferenceValid(reference)) {
...
}
如果引用陈旧(指向被 cbdataFree 释放的条目),cbdataReferenceValid() 将返回 false。
🔗 cbdataReferenceValidDone
void *pointer;
bool cbdataReferenceValidDone(reference, &pointer);
移除由 cbdataReference() 创建的引用并检查其有效性。指向被引用数据的临时指针(如果有效)在 \&pointer 参数中返回。
用于最后一次解引用,通常用于进行回调。
void *cbdata;
...
if (cbdataReferenceValidDone(reference, &cbdata)) != NULL)
callback(..., cbdata);
注意:引用变量将自动清除为 NULL。
🔗 示例
在这里,您可以找到一些关于如何使用 cbdata 的示例,以及为什么
🔗 不使用 cbdata 的异步操作,展示了 cbdata 的必要性
对于带有回调函数的异步操作,在不使用 cbdata 的程序中,正常的事件顺序如下
/* initialization */
type_of_data our_data;
...
our_data = malloc(...);
...
/* Initiate a asyncronous operation, with our_data as callback_data */
fooOperationStart(bar, callback_func, our_data);
...
/* The asyncronous operation completes and makes the callback */
callback_func(callback_data, ....);
/* Some time later we clean up our data */
free(our_data);
然而,如果我们想要或需要释放 callback_data,或者在操作完成之前取消回调,情况会变得更有趣。在这种结构中,您很容易导致 callback_data 指向的内存被释放,而在回调被调用之前,这会导致程序失败或内存损坏
/* initialization */
type_of_data our_data;
...
our_data = malloc(...);
...
/* Initiate a asyncronous operation, with our_data as callback_data */
fooOperationStart(bar, callback_func, our_data);
...
/* ouch, something bad happened elsewhere.. try to cleanup
* but the programmer forgot there is a callback pending from
* fooOperationsStart() (an easy thing to forget when writing code
* to deal with errors, especially if there may be many different
* pending operation)
*/
free(our_data);
...
/* The asyncronous operation completes and makes the callback */
callback_func(callback_data, ....);
/* CRASH, the memory pointer to by callback_data is no longer valid
* at the time of the callback
*/
🔗 使用 cbdata 的异步操作
回调数据分配器使我们能够以统一且安全的方式进行此操作。回调数据分配器用于分配、跟踪和释放回调操作期间使用的内存池对象。分配的内存将在异步操作在其他地方执行时被锁定,并在操作完成后被释放。正常的事件顺序是
/* initialization */
type_of_data our_data;
...
our_data = cbdataAlloc(type_of_data);
...
/* Initiate a asyncronous operation, with our_data as callback_data */
fooOperationStart(..., callback_func, our_data);
...
/* foo */
void *local_pointer = cbdataReference(callback_data);
....
/* The asyncronous operation completes and makes the callback */
void *cbdata;
if (cbdataReferenceValidDone(local_pointer, &cbdata))
callback_func(...., cbdata);
...
cbdataFree(our_data);
🔗 通过 cbdata 取消的异步操作
在这种机制下,如果 cbdataFree 在 fooOperantionComplete(…) 之前被调用,也不会发生任何 bad thing。
/* initialization */
type_of_data our_data;
...
our_data = cbdataAlloc(type_of_data);
...
/* Initiate a asyncronous operation, with our_data as callback_data */
fooOperationStart(..., callback_func, our_data);
...
/* foo */
void *local_pointer = cbdataReference(callback_data);
....
/* something bad happened elsewhere.. cleanup */
cbdataFree(our_data);
...
/* The asyncronous operation completes and tries to make the callback */
void *cbdata;
if (cbdataReferenceValidDone(local_pointer, &cbdata))
/* won't be called, as the data is no longer valid */
callback_func(...., cbdata);
在这种情况下,当 cbdataFree 在 cbdataReferenceValidDone 之前被调用时,callback_data 会被标记为无效。当 callback_data 在执行回调函数之前无效时,cbdataReferenceValidDone 将返回 0,并且 callback_func 永远不会被执行。
🔗 添加新的 cbdata 注册类型
要向分配器添加新的模块特定数据类型,可以使用宏 CBDATA_TYPE 和 CBDATA_INIT_TYPE。这些宏会创建一个本地的 cbdata 定义(文件或块作用域)。任何 cbdataAlloc 调用都必须在该作用域内进行。但是,cbdataFree 可以从任何地方调用。
/* 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. This
* function is responsible for cleaning up any dependencies etc
* referenced by the structure and is called on cbdataFree or
* when the last reference is deleted by cbdataReferenceDone /
* cbdataReferenceValidDone
*/
CBDATA_INIT_TYPE_FREECB(type_of_data, free_function);
🔗 全局添加新的 cbdata 注册数据类型
要添加可以在代码中任何位置分配的新全局数据类型,必须将其添加到 enums.h 中的 cbdata_type 枚举中,并在 cbdata.c:cbdataInit() 中进行相应的 CREATE_CBDATA 调用。或者,可以像下面所示那样在 globals.h 中添加 CBDATA_GLOBAL_TYPE 定义,并在适当的位置使用 CBDATA_INIT_TYPE,如上所述。
extern CBDATA_GLOBAL_TYPE(type_of_data); /* CBDATA_UNDEF */