From b46aad6df449445a9fc4aa7b32bd40005438e3f7 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 13 Apr 2024 14:18:05 +0200 Subject: Adding upstream version 2.9.5. Signed-off-by: Daniel Baumann --- doc/internals/api/event_hdl.txt | 1015 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 1015 insertions(+) create mode 100644 doc/internals/api/event_hdl.txt (limited to 'doc/internals/api/event_hdl.txt') diff --git a/doc/internals/api/event_hdl.txt b/doc/internals/api/event_hdl.txt new file mode 100644 index 0000000..72eeff8 --- /dev/null +++ b/doc/internals/api/event_hdl.txt @@ -0,0 +1,1015 @@ + ----------------------------------------- + event_hdl Guide - version 2.8 + ( Last update: 2022-11-14 ) + ------------------------------------------ + +ABSTRACT +-------- + +The event_hdl support is a new feature of HAProxy 2.7. It is a way to easily +handle general events in a simple to maintain fashion, while keeping core code +impact to the bare minimum. + +This document first describes how to use already supported events, +then how to add support for your very own events. + +This feature is quite new for now. The API is not frozen and will be +updated/modified/improved/extended as needed. + +SUMMARY +------- + + 1. event_hdl introduction + 2. How to handle existing events + 2.1 SYNC mode + 2.2 ASYNC mode + 2.2.1 normal version + 2.2.2 task version + 2.3 Advanced features + 2.3.1 sub_mgmt + 2.3.2 subscription external lookups + 2.3.3 subscription ptr + 2.3.4 private_free + 3. How to add support for new events + 3.1 Declaring a new event data structure + 3.2 Publishing an event + 4. Subscription lists + 5. misc/helper functions + + +1. EVENT_HDL INTRODUCTION +----------------------- + +EVENT_HDL provides two complementary APIs, both are implemented +in src/event_hdl.c and include/haproxy/event_hdl(-t).h: + +One API targeting developers that want to register event +handlers that will be notified when specific events occur in the process. +(See section 2.) + +One API targeting developers that want to notify registered handlers about +an event that is happening in the process. +(See section 3.) + +2. HOW TO HANDLE EXISTING EVENTS +--------------------- + +To handle existing events, you must first decide which events you're +interested in. + +event types are defined as follow: + +``` + /* type for storing event subscription type */ + typedef struct event_hdl_sub_type + { + /* up to 256 families, non cumulative, adjust if needed */ + uint8_t family; + /* up to 16 sub types using bitmasks, adjust if needed */ + uint16_t subtype; + } event_hdl_sub_type; +``` + +For an up to date list of already supported events, +please refer to include/haproxy/event_hdl-t.h +At the end of the file you will find existing event types. + +Each event family provides an unique data structure that will +be provided to the event handler (registered to one or more +event subtypes) when such events occur. + +An event handler can subscribe to a single event family type at a time, but +within the family type it can subscribe to multiple event subtypes. + + For example, let's consider the SERVER family type. + + Let's assume it provides the event_hdl_cb_data_server data structure. + + We can register a handler that will be notified for + every SERVER event types using: + EVENT_HDL_SUB_SERVER + + This will include EVENT_HDL_SUB_SERVER_ADD, + EVENT_HDL_SUB_SERVER_DEL [...] + + But we can also subscribe to a specific subtype only, + for example server deletion: + EVENT_HDL_SUB_SERVER_DEL + + You can even combine multiple SERVER subtypes using + event_hdl_sub_type_add function helper: + event_hdl_sub_type_add(EVENT_HDL_SUB_SERVER_DEL, + EVENT_HDL_SUB_SERVER_ADD) + + (will refer to server deletion as well as server addition) + +Registering a handler comes into multiple flavors: + + SYNC mode: + handler is called in a blocking manner directly from the + thread that publishes the event. + This mode should be used with precaution because it could + slow the caller or cause deadlocks if used improperly. + + Sync mode is useful when you directly depend on data or + state consistency from the caller. + + Sync mode gives you access to unsafe elements in the data structure + provided by the caller (again, see event_hdl-t.h for more details). + The data structure may provide lock hints in the unsafe section + so that you know which locks are already held within the + calling context, hopefully preventing you from relocking + an already locked element and preventing deadlocks. + + ASYNC mode: + handler is called in a non-blocking manner + (in a dedicated tasklet), + thus, the caller (that published the event) is not affected + by the handler. (time wise and data wise) + + This is the safest way to handle events, + but it also comes with a limitation: + + unsafe elements in the data structure provided by + the caller SHOULD be used under NO circumstances. + Indeed, only safe elements are meant to be used + when handling the event in async mode. + + ASYNC mode is declined in 2 different versions: + normal: + handler is simply a function pointer + (same prototype as sync mode), + that is called asynchronously with relevant data + when the event is published. Only difference with + sync mode here is that 'unsafe' data provided + by the data structure may not be used. + task: + handler is a user defined task(let) that uses an event + queue to consume pending events. + This mode is interesting when you need to perform + advanced operations or you need to handle the event + in an already existing task context. + It is a bit more complicated to setup, but really + nothing to worry about, some examples will be + provided later in this document. + +event subscription is performed using the function: + + event_hdl_subscribe(list, event, hdl); + + The function returns 1 in case of success, + and 0 in case of failure (bad arguments, or memory error) + + The function may BUG_ON if used improperly (invalid arguments) + + is either user specified list used to store the + new subscription, or NULL if you want to store the subscription + in the process global list. + + is also asked when publishing an event, + so specifying list could be useful, if, for example, + you only want to subscribe to a specific subscription list + (see this as a scope for example, NULL being full scope, + and specific list being limited scope) + + We will use server events as an example: + + You could register to events for ALL servers by using the + global list (NULL), or only to a specific server events + by using the subscription list dedicated to a single server. + + are the events (family.subtypes) you're subscribing to + + contains required handler options, it must be provided using + EVENT_HDL_(TASK_)(A)SYNC() and EVENT_HDL_ID_(TASK_)(A)SYNC() + helper macros. + + See include/haproxy/event_hdl.h or below to know which macro + best suits your needs. + + When registering a handler, you have the ability to provide an + unique ID (using EVENT_HDL_ID_ macro family) that could be used + later to perform lookups on the subscription. + ID is stored as an uint64_t hash that is expected to be computed using + general purpose event_hdl_id inline function provided by event_hdl.h. + Not providing an ID (using EVENT_HDL_ macro family) + results in the subscription being considered as anonymous. + As the name implies, anonymous subscriptions don't support lookups. + +2.1 SYNC MODE +--------------------- + +Example, you want to register a sync handler that will be called when +a new server is added. + +Here is what the handler function will look like: +``` +void my_sync_handler(const struct event_hdl_cb *cb, void *private) +{ + const struct event_hdl_cb_data_server *server = cb->e_data; + + /* using EVENT_HDL_ASSERT_SYNC is a good practice to ensure + * that the function breaks if used in async mode + * (because we will access unsafe data in this function that + * is sync mode only) + */ + EVENT_HDL_ASSERT_SYNC(cb); + printf("I've been called for '%s', private = %p\n", + event_hdl_sub_type_to_string(cb->e_type), private); + printf("server name is '%s'\n", server->safe.name); + + /* here it is safe to use unsafe data */ + printf("server ptr is '%p'\n", server->unsafe.ptr); + + /* from here you have the possibility to manage the subscription + * cb->sub_mgmt->unsub(cb->sub_mgmt); + * // hdl will be removed from the subscription list + */ +} +``` + +Here is how you perform the subscription: + +anonymous subscription: +``` + int private = 10; + + event_hdl_subscribe(NULL, EVENT_HDL_SUB_SERVER_ADD, + EVENT_HDL_SYNC(my_sync_handler, &private, NULL)); +``` + +identified subscription: +``` + int private = 10; + uint64_t id = event_hdl_id("test", "sync"); + + event_hdl_subscribe(NULL, EVENT_HDL_SUB_SERVER_ADD, + EVENT_HDL_ID_SYNC(id, + my_sync_handler, + &private, + NULL)); + +``` + +identified subscription where freeing private is required when subscription ends: +(also works for anonymous) +(more on this feature in 2.3.4) +``` + int *private = malloc(sizeof(*private)); + uint64_t id = event_hdl_id("test", "sync_free"); + + BUG_ON(!private); + *private = 10; + + /* passing free as 'private_free' function so that + * private can be freed when unregistering is performed + */ + event_hdl_subscribe(NULL, EVENT_HDL_SUB_SERVER_ADD, + EVENT_HDL_ID_SYNC(id, + my_sync_handler, + private, + free)); + + + /* ... */ + + // unregistering the identified hdl + if (event_hdl_lookup_unsubscribe(NULL, id)) { + printf("private will automatically be freed!\n"); + } +``` + +2.2 ASYNC MODE +--------------------- + +As mentioned before, async mode comes in 2 flavors, normal and task. + +2.2.1 NORMAL VERSION +--------------------- + +Normal is meant to be really easy to use, and highly compatible with sync mode. + +(Handler can easily be converted or copy pasted from async to sync mode +and vice versa) + +Quick warning about sync to async handler conversion: + +please always use EVENT_HDL_ASSERT_SYNC whenever you develop a +sync handler that performs unsafe data access. + +This way, if the handler were to be converted or copy pasted as is to +async mode without removing unsafe data accesses, +the handler will forcefully fail to indicate an error so that you +know something has to be fixed in your handler code. + +Back to our async handler, let's say you want to declare an +async handler that will be called when a new server is added. + +Here is what the handler function will look like: +``` +void my_async_handler(const struct event_hdl_cb *cb, void *private) +{ + const struct event_hdl_cb_data_server *server = cb->e_data; + + printf("I've been called for '%s', private = %p\n", + event_hdl_sub_type_to_string(cb->e_type), private); + printf("server name is '%s'\n", server->safe.name); + + /* here it is not safe to use unsafe data */ + + /* from here you have the possibility to manage the subscription + * cb->sub_mgmt->unsub(cb->sub_mgmt); + * // hdl will be removed from the subscription list + */ +} +``` + +Note that it is pretty similar to sync handler, except +for unsafe data access. + +Here is how you declare the subscription: + +anonymous subscription: +``` + int private = 10; + + event_hdl_subscribe(NULL, EVENT_HDL_SUB_SERVER_ADD, + EVENT_HDL_ASYNC(my_async_handler, &private, NULL)); +``` + +identified subscription: +``` + int private = 10; + uint64_t id = event_hdl_id("test", "async"); + + event_hdl_subscribe(NULL, EVENT_HDL_SUB_SERVER_ADD, + EVENT_HDL_ID_ASYNC(id, + my_async_handler, + &private, + NULL)); + +``` + +identified subscription where freeing private is required when subscription ends: +(also works for anonymous) +``` + int *private = malloc(sizeof(*private)); + uint64_t id = event_hdl_id("test", "async_free"); + + BUG_ON(!private); + *private = 10; + + /* passing free as 'private_free' function so that + * private can be freed when unregistering is performed + */ + event_hdl_subscribe(NULL, EVENT_HDL_SUB_SERVER_ADD, + EVENT_HDL_ID_ASYNC(id, + my_async_handler, + private, + free)); + + /* ... */ + + // unregistering the identified hdl + if (event_hdl_lookup_unsubscribe(NULL, id)) { + printf("private will automatically be freed when " + "all pending events referencing private " + "are consumed!\n"); + } +``` + +2.2.2 TASK VERSION +--------------------- + +task version requires a bit more setup, but it's pretty +straightforward actually. + + +First, you need to initialize an event queue that will be used +by event_hdl facility to push you events according to your subscription: + +``` + event_hdl_async_equeue my_q; + + event_hdl_async_equeue_init(&my_q); +``` + + +Then, you need to declare a task(let) (or reuse existing task(let)) + +It is your responsibility to make sure that the task(let) still exists +(is not freed) when calling the subscribe function +(and that the task remains valid as long as the subscription is). + +When a subscription referencing your task is over +(either ended because of list purge, external code or from the handler itself), +you will receive the EVENT_HDL_SUB_END event. +When you receive this event, you must free it as usual and you can safely +assume that the related subscription won't be sending you any more events. + +Here is what your task will look like (involving a single event queue): + +``` +struct task *event_hdl_async_task_my(struct task *task, + void *ctx, unsigned int state) +{ + struct tasklet *tl = (struct tasklet *)task; + event_hdl_async_equeue *queue = ctx; + struct event_hdl_async_event *event; + struct event_hdl_cb_data_server *srv; + uint8_t done = 0; + + while ((event = event_hdl_async_equeue_pop(queue))) + { + if (event_hdl_sub_type_equal(event->type, EVENT_HDL_SUB_END)) { + done = 1; + event_hdl_async_free_event(event); + printf("no more events to come, " + "subscription is over\n"); + break; + } + + srv = event->data; + + printf("task event %s, %d (name = %s)\n", + event_hdl_sub_type_to_string(event->type), + *((int *)event->private), srv->safe.name); + event_hdl_async_free_event(event); + } + + if (done) { + /* our job is done, subscription is over: + * no more events to come + */ + tasklet_free(tl); + return NULL; + } + return task; +} + +``` + +Here is how we would initialize the task event_hdl_async_task_my: +``` + struct tasklet *my_task; + + my_task = tasklet_new(); + BUG_ON(!my_task); + my_task->context = &my_q; // we declared my_q previously in this example + /* we declared event_hdl_async_task_my previously + * in this example + */ + my_task->process = event_hdl_async_task_my; + +``` + +Given our task and our previously initialized event queue, here is how +to perform the subscription: +``` + int test_val = 11; + uint64_t id = event_hdl_id("test", "my_task"); + + /* anonymous variant */ + event_hdl_subscribe(NULL, EVENT_HDL_SUB_SERVER_ADD, + EVENT_HDL_ASYNC_TASK(&my_q, + my_task, + &test_val, + NULL)); + /* identified variant */ + event_hdl_subscribe(NULL, EVENT_HDL_SUB_SERVER_ADD, + EVENT_HDL_ID_ASYNC_TASK(id, + &my_q, + my_task, + &test_val, + NULL)); +``` + +Note: it is not recommended to perform multiple subscriptions + that share the same event queue or same task(let) (or both) + + That is, having more than one subscription waking a task(let) + and/or feeding the same event queue. + + No check is performed on this when registering, so the API + won't prevent you from doing it. + + If you are going to do this anyway despite this warning: + + In the case you need to stop the task prematurely + (if this is not going to happen please skip this paragraph): + You are responsible for acknowledging the end of every + active subscriptions that refer to your task or + your event queue(s). + And you really don't want a subscription associated with + your task or event queue to keep going when the task + is not active anymore because: + 1: there will be memory leak + (event queue might continue to receive new events) + 2: there is a 100% chance of process crash in case of event + because we will try to wake a task (your task) + that might already be freed. Thus UAF will occur. + +2.3 ADVANCED FEATURES +----------------------- + +We've already covered some of these features in the previous examples. +Here is a documented recap. + + +2.3.1 SUB MGMT +----------------------- + +From an event handler context, either sync or async mode: + You have the ability to directly manage the subscription + that provided the event. + +As of today, these actions are supported: + - Consulting the subscription. + - Modifying the subscription (resubscribing within same family) + - Unregistering the subscription (unsubscribing). + +To do this, consider the following structure: +``` + struct event_hdl_sub_mgmt + { + /* manage subscriptions from event + * this must not be used directly because + * locking might be required + */ + struct event_hdl_sub *this; + /* safe functions than can be used from + * event context (sync and async mode) + */ + struct event_hdl_sub_type (*getsub)(const struct event_hdl_sub_mgmt *); + int (*resub)(const struct event_hdl_sub_mgmt *, struct event_hdl_sub_type); + void (*unsub)(const struct event_hdl_sub_mgmt *); + }; + +``` +A reference to this structure is provided in every handler mode. + +Sync mode and normal async mode (directly from the callback data pointer): +``` + const struct event_hdl_cb *cb; + // cb->sub_mgmt + // cb->sub_mgmt->getsub(cb->sub_mgmt); + // cb->sub_mgmt->unsub(cb->sub_mgmt); +``` + +task and notify async modes (from the event): +``` + struct event_hdl_async_event *event; + // event->sub_mgmt + // event->sub_mgmt.getsub(&event->sub_mgmt); + // event->sub_mgmt.unsub(&event->sub_mgmt); +``` + +2.3.2 SUBSCRIPTION EXTERNAL LOOKUPS +----------------------- + +As you've seen in 2.3.1, managing the subscription directly +from the handler is a possibility. + +But for identified subscriptions, you also have the ability to +perform lookups and management operations on specific subscriptions +within a list based on their ID, anywhere in the code. + +/!\ This feature is not available for anonymous subscriptions /!\ + +Here are the actions already supported: + + - unregistering a subscription (unsubscribing) + - updating a subscription (resubscribing within same family) + - getting a ptr/reference to the subscription + +Those functions are documented in event_hdl.h +(search for EVENT_HDL_LOOKUP section). + +To select a specific subscription, you must provide +the unique identifier (uint64_t hash) that was provided when subscribing. +(using event_hdl_id(scope, name) function) + +Notes: + "id" is only unique within a given subscription list. + + When using event_hdl_id to provide the id: + It is your responsibility to make sure that you "own" + the scope if you rely on name to be "free". + + As ID computation is backed by xxhash hash API, + you should be aware that hash collisions could occur, + but are extremely rare and are thus considered safe + enough for this usage. + (see event_hdl.h for implementation details) + + Please consider ptr based subscription management if + these limitations don't fit your requirements. + +Here are some examples: + +unsubscribing: +``` + /* registering "scope":"name" subscription */ + event_hdl_subscribe(NULL, EVENT_HDL_SUB_SERVER_ADD, + EVENT_HDL_ID_SYNC(event_hdl_id("scope", "name"), + my_sync_handler, + NULL, + NULL)); + /* unregistering "scope":"name" subscription */ + event_hdl_lookup_unsubscribe(NULL, event_hdl_id("scope", "name")); +``` + +2.3.3 SUBSCRIPTION PTR +----------------------- + +To manage existing subscriptions from external code, +we already talked about identified subscriptions that +allow lookups within list. + +But there is another way to accomplish this. + +When subscribing, you can use the event_hdl_subscribe_ptr() function +variant (same arguments as event_hdl_subscribe()). + +What this function does, is instead of returning 1 in case of +success and 0 in case of failure: it returns a valid subscription ptr +for success and NULL for failure. + +Returned ptr is guaranteed to remain valid even if subscription +is ended meanwhile because the ptr is internally guarded with a refcount. + +Thus, as long as you don't explicitly unregister the subscription with +event_hdl_unsubscribe() or drop the reference using event_hdl_drop(), +subscription ptr won't be freed. + +This ptr will allow you to use the following subscription +management functions from external code: + + - event_hdl_take() to increment subscription ptr refcount + (automatically incremented when using event_hdl_subscribe_ptr) + - event_hdl_drop() to decrement subscription ptr refcount + - event_hdl_resubscribe() to modify subscription subtype + - event_hdl_unsubscribe() to end the subscription + (refcount will be automatically decremented) + +Here is an example: +``` + struct event_hdl_sub *sub_ptr; + + /* registering a subscription with subscribe_ptr */ + sub_ptr = event_hdl_subscribe_ptr(NULL, EVENT_HDL_SUB_SERVER_ADD, + EVENT_HDL_SYNC(my_sync_handler, + NULL, + NULL)); + + /* ... */ + + /* unregistering the subscription */ + event_hdl_unsubscribe(sub_ptr); +``` + +Regarding identified subscriptions that were registered using the non ptr +subscribe function: + +You still have the ability to get a reference to the related subscription +(if it still exists), by using event_hdl_lookup_take(list, id) function. +event_hdl_lookup_take will return a subscription ptr in case of success +and NULL in case of failure. +Returned ptr reference is automatically incremented, so it is safe to use. + +Please don't forget to drop the reference +when holding the ptr is no longer needed. + +Example: +``` + struct event_hdl_sub *sub_ptr = NULL; + + /* registering subscription id "test":"ptr" with normal subscribe */ + if (event_hdl_subscribe(NULL, EVENT_HDL_SUB_SERVER_ADD, + EVENT_HDL_ID_SYNC(event_hdl_id("test", "ptr"), + my_sync_handler, + NULL, + NULL))) { + /* fetch ref to subscription "test":"ptr" */ + sub_ptr = event_hdl_lookup_take(NULL, + event_hdl_id("test", "ptr")); + + /* unregister the subscription using lookup */ + event_hdl_lookup_unsubscribe(NULL, + event_hdl_id("test", "ptr")); + } + + /* ... */ + + /* unregistering the subscription with ptr + * will do nothing because subscription was + * already ended by lookup_unsubscribe, but + * here the catch is that sub_ptr is still + * valid so this won't crash the program + */ + if (sub_ptr) { + event_hdl_unsubscribe(sub_ptr); + /* unsubscribe will also result in subscription + * reference drop, thus subscription will be freed here + * because sub_ptr was the last active reference. + * You must not use sub_ptr anymore past this point + * or UAF could occur + */ + } + +``` + +2.3.4 PRIVATE FREE +----------------------- + +Upon handler subscription, you have the ability to provide +a private data pointer that will be passed to the handler +when subscribed events occur. + +Sometimes this private data pointer will rely on dynamically allocated memory. +And in such cases, you have no way of knowing when +freeing this pointer can be done safely. + +You could be tempted to think that freeing right after performing +the unsubscription could be safe. +But this is not the case, remember we could be dealing with async handlers +that might still consume pending events even though unsubscription +has been performed from external code. + +To deal with this, you may want to provide the private_free +function pointer upon subscription. +This way, private_free function will automatically be called +(with private as argument) when private is no longer be used. + +Example: +First we declare our private free function: +``` +void my_private_free(void *my_private_data) { + /* here we only call free, + * but you could do more sophisticated stuff + */ + free(my_private_data); +} +``` +Then: +``` + char *my_private_data = strdup("this string needs to be freed"); + + BUG_ON(!my_private_data); + + event_hdl_subscribe(NULL, EVENT_HDL_SUB_SERVER_DEL, + EVENT_HDL_ID_ASYNC(event_hdl_id("test", "private"), + my_async_handler, + my_private_data, + my_private_free)); + + /* freeing my_private_data is not required anymore, + * it will be automatically freed by our private free + * function when subscription ends + */ + + /* unregistering "test":"private" subscription */ + event_hdl_lookup_unsubscribe(NULL, event_hdl_id("test", "private")); + + /* my_private_free will be automatically summoned when my_private_data + * is not referenced anymore + */ +``` + +3 HOW TO ADD SUPPORT FOR NEW EVENTS +----------------------- + +Adding support for a new event is pretty straightforward. + +First, you need to declare a new event subtype in event_hdl-t.h file +(bottom of the file). + +You might want to declare a whole new event family, in which case +you declare both the new family and the associated subtypes (if any). + +``` + #define EVENT_HDL_SUB_NEW_FAMILY EVENT_HDL_SUB_FAMILY(4) + #define EVENT_HDL_SUB_NEW_FAMILY_SUBTYPE_1 EVENT_HDL_SUB_TYPE(4,0) +``` + +Then, you need to update the event_hdl_sub_type_map map, +defined in src/event_hdl.c file (top of the file) +to add string to event type and event type to string conversion support. +You just need to add the missing entries corresponding to +the event family / subtypes you've defined. + +Please follow this procedure: + You only added a new subtype to existing family: go to section 3.2 + You added a new family: go to section 3.1 + +3.1 DECLARING A NEW EVENT DATA STRUCTURE +----------------------- + +You have the ability to provide additional data for a given +event family when such events occur. + +Note that it is not mandatory: you could simply declare a new event family +that does not provide any data. +If this is your case, you can skip this section and go to 3.2 section. + +Now, take a look at this event data structure template +(also defined at the top of event_hdl-t.h file): +``` + /* event data struct are defined as followed */ + struct event_hdl_cb_data_template { + struct { + /* safe data can be safely used from both + * sync and async functions + * data consistency is guaranteed + */ + } safe; + struct { + /* unsafe data may only be used from sync functions: + * in async mode, data consistency cannot be guaranteed + * and unsafe data may already be stale, thus using + * it is highly discouraged because it + * could lead to undefined behavior + * (UAF, null dereference...) + */ + } unsafe; + }; +``` + +This structure template allows you to easily create a new event +data structure that can be provided with your new event family. + +You should name it after 'struct event_hdl_cb_data_new_family' so that it is +easy to guess the event family it relates to. + +Indeed, each event data structure is to be associated with an +unique event family type. +For each subtypes within a family type, the associated data structure +should be provided when publishing the event. + +The event data struct declaration should not be performed +directly under event_hdl-t.h file: + + It should be done in the header files of the corresponding + facility that will publish/provide this event. + + Example: struct event_hdl_cb_data_server, provided for the + EVENT_HDL_SUB_SERVER event family, is going to be declared in + include/haproxy/server-t.h file. + + However, in event_hdl-t.h, where you declare event family/subtypes, + you should add comments or links to the file containing the relevant + data struct declaration. This way we make sure all events related + information is centralized in event_hdl-t.h while keeping it clean + and not depending on any additional includes (you are free to + depend on specific data types within your custom event data structure). + +Please make sure that EVENT_HDL_ASYNC_EVENT_DATA (defined in event_hdl-t.h) +is greater than sizeof(event_hdl_cb_data_new_family). + +It is required for async handlers to properly consume event data. + +You are free to adjust EVENT_HDL_ASYNC_EVENT_DATA size if needed. + +If EVENT_HDL_ASYNC_EVENT_DATA is not big enough to store your new +event family struct, a compilation assert triggered by EVENT_HDL_CB_DATA +will occur. In addition to this, an extra runtime BUG_ON will make +sure the condition is met when publishing the event. +The goal here is to force haproxy to fail explicitly so you know that +something must be done on your side. + +3.1 PUBLISHING AN EVENT +----------------------- + +Publishing an event is really simple. +It relies on the event_hdl_publish function. + +The function is defined as follow: +``` + int event_hdl_publish(event_hdl_sub_list *sub_list, + event_hdl_sub_type e_type, + const struct event_hdl_cb_data *data); +``` + +We will ignore sub_list argument for now. +In the examples below, we will use sub_list = NULL. +Go to section 4 for a full picture about this feature. + +: the event type that should be published. + All subscriptions referring to this event within + a subscription list context will be notified about the event. +: data provided for the event family of + If .family does not provide additional data, + data should be set to NULL. + If .family does provide additional data, data should be set + using EVENT_HDL_CB_DATA macro. + (see the example below) + +The function returns 1 in case of SUCCESS (handlers successfully notified) +and 0 in case of FAILURE (no handlers notified, because of memory error). + +Event publishing can be performed from anywhere in the code. +(this example does not compile) +``` + struct event_hdl_cb_data_new_family event_data; + + /* first we need to prepare event data + * that will be provided to event handlers + */ + + /* safe data, available from both sync and async contexts */ + event_data.safe.my_custom_data = x; + + /* unsafe data, only available from sync contexts */ + event_data.unsafe.my_unsafe_data = y; + + /* once data is prepared, we can publish the event */ + event_hdl_publish(NULL, + EVENT_HDL_SUB_NEW_FAMILY_SUBTYPE_1, + EVENT_HDL_CB_DATA(&event_data)); + + /* EVENT_HDL_SUB_NEW_FAMILY_SUBTYPE_1 event was + * successfully published in global subscription list + */ +``` + +-------------------------------------------------------------------------------- +|You should know that there is currently a limitation about publish function: | +|The function should not be used from critical places | +|(where the calling frequency is high | +|or where timing sensitivity is high). | +| | +|Because in current implementation, subscription list lookups are not | +|optimized for such uses cases. | +-------------------------------------------------------------------------------- + +4 SUBSCRIPTION LISTS +----------------------- + +As you may already know, EVENT_HDL API main functions rely on +subscription lists. +Providing NULL where subscription list argument is required +allows to use the implicit global subscription list. + +But you can also provide a specific subscription list, example: + subscription list associated with a single entity so that you only + subscribe to events of this single entity + +A subscription list is of type event_hdl_sub_list. +It is defined in event_hdl-t.h + +To make use of this feature, you should know about these 2 functions: + +event_hdl_sub_list_init(list): use this fcn to initialize + a new subscription list. + +Example: +``` + event_hdl_sub_list my_custom_list; + + event_hdl_sub_list_init(&my_custom_list); +``` + +event_hdl_sub_list_destroy(list): use this fcn to destroy + an existing subscription list. + +Example: +``` + event_hdl_sub_list_init(&my_custom_list); +``` + + Using this function will cause all the existing subscriptions + within the provided sub_list to be properly unregistered + and deleted according to their types. + +Now we'll take another quick look at event_hdl_publish() function: + +Remember that the function is defined as follow: +``` + int event_hdl_publish(event_hdl_sub_list *sub_list, + event_hdl_sub_type e_type, + const struct event_hdl_cb_data *data); +``` + +In the previous examples, we used sub_list = NULL. + +if sub_list is NULL: + event will be published in in global list +else + event will be published in user specified sub_list + +5 MISC/HELPER FUNCTIONS +----------------------- + +Don't forget to take a look at MISC/HELPER FUNCTIONS in +include/haproxy/event_hdl.h (end of the file) for a +complete list of helper functions / macros. + +We've already used some, if not the vast majority +in the examples shown in this document. + +This includes, to name a few: + - event types manipulation + - event types comparison + - lookup id computing + - subscriber list management (covered in section 4) + - sync/async handler helpers -- cgit v1.2.3