/* * include/haproxy/event_hdl.h * event handlers management * * Copyright 2022 HAProxy Technologies * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, version 2.1 * exclusively. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _HAPROXY_EVENT_HDL_H # define _HAPROXY_EVENT_HDL_H #include #include /* preprocessor trick to extract function calling place * __FILE__:__LINE__ */ #define _EVENT_HDL_CALLING_PLACE2(line) #line #define _EVENT_HDL_CALLING_PLACE1(line) _EVENT_HDL_CALLING_PLACE2(line) #define _EVENT_HDL_CALLING_PLACE __FILE__":"_EVENT_HDL_CALLING_PLACE1(__LINE__) /* ------ PUBLIC EVENT_HDL API ------ */ /* You will find a lot of useful information/comments in this file, but if you're looking * for a step by step documentation please check out 'doc/internals/api/event_hdl.txt' */ /* Note: API helper macros are used in this file to make event_hdl functions usage * simpler, safer and more consistent between sync mode and async mode */ /* ======================================= EVENT_HDL_SYNC handlers ===================================== * must be used only with extreme precautions * sync handlers are directly called under the function that published the event. * Hence, all the processing done within such function will impact the caller. * * For this reason, you must be extremely careful when using sync mode, because trying to lock something * that is already held by the caller, or depending on something external to the current thread will * prevent the caller from running. * * Please consider using async handlers in this case, they are specifically made to solve this limitation. * * On the other hand, sync handlers are really useful when you directly depend on callers' provided data * (example: pointer to data) or you need to perform something before the caller keeps going. * A good example could be a cleanup function that will take care of freeing data, closing fds... related * to event data before caller's flow keeps going (interrupting the process while dealing with the event). */ /* ===================================== EVENT_HDL_ASYNC handlers ====================================== * async handlers are run in independent tasks, so that the caller (that published the event) can safely * return to its own processing. * * async handlers may access safe event data safely with guaranteed consistency. */ /* ================================ IDENTIFIED vs ANONYMOUS EVENT_HDL ================================= * When registering a sync or async event handler, you are free to provide a unique identifier (hash). * * id can be computed using event_hdl_id function. * * Not providing an id results in the subscription being considered as anonymous subscription. * 0 is not a valid identifier (should be > 0) * * Identified subscription is guaranteed to be unique for a given subscription list, * whereas anonymous subscriptions don't provide such guarantees. * * Identified subscriptions provide the ability to be later queried or unregistered from external code * using dedicated id/hash for the lookups. * * On the other hand, anonymous subscriptions don't, the only other way to reference an anonymous subscription * is to use a subscription pointer. * */ /* general purpose hashing function when you want to compute * an ID based on x * It is your responsibility to make sure is not used * elsewhere in the code (or that you are fine with sharing * the scope). */ uint64_t event_hdl_id(const char *scope, const char *name); /* ------ EVENT SUBSCRIPTIONS FUNCTIONS ------ */ /* macro helper: * sync version * * identified subscription * * <_id>: subscription id that could be used later * to perform subscription lookup by id * : pointer to 'event_hdl_cb_sync' prototyped function * <_private>: pointer to private data that will be handled to * <_private_free>: pointer to 'event_hdl_private_free' prototyped function * that will be called with when unsubscription is performed */ #define EVENT_HDL_ID_SYNC(_id, func, _private, _private_free) \ (struct event_hdl){ .id = _id, \ .dorigin = _EVENT_HDL_CALLING_PLACE, \ .async = 0, \ .sync_ptr = func, \ .private = _private, \ .private_free = _private_free } /* macro helper: * sync version * * anonymous subscription (no lookup by id) * * : pointer to 'event_hdl_cb_sync' prototyped function * <_private>: pointer to private data that will be handled to * <_private_free>: pointer to 'event_hdl_private_free' prototyped function * that will be called with when unsubscription is performed */ #define EVENT_HDL_SYNC(func, _private, _private_free) \ EVENT_HDL_ID_SYNC(0, func, _private, _private_free) /* macro helper: * async version * * identified subscription * * <_id>: subscription id that could be used later * to perform subscription lookup by id * : pointer to 'event_hdl_cb_sync' prototyped function * <_private>: pointer to private data that will be handled to * <_private_free>: pointer to 'event_hdl_private_free' prototyped function * that will be called with after unsubscription is performed, * when no more events can refer to . */ #define EVENT_HDL_ID_ASYNC(_id, func, _private, _private_free) \ (struct event_hdl){ .id = _id, \ .dorigin = _EVENT_HDL_CALLING_PLACE, \ .async = EVENT_HDL_ASYNC_MODE_NORMAL, \ .async_ptr = func, \ .private = _private, \ .private_free = _private_free } /* macro helper: * async version * * anonymous subscription (no lookup by id) * * : pointer to 'event_hdl_cb_sync' prototyped function * <_private>: pointer to private data that will be handled to * <_private_free>: pointer to 'event_hdl_private_free' prototyped function * that will be called with after unsubscription is performed, * when no more events can refer to . */ #define EVENT_HDL_ASYNC(func, _private, _private_free) \ EVENT_HDL_ID_ASYNC(0, func, _private, _private_free) /* macro helper: * async version * same than EVENT_HDL_ID_ASYNC - advanced mode: * you directly provide task and event_queue list. * * identified subscription * * <_id>: subscription id that could be used later * to perform subscription lookup by id * : pointer to event_hdl_async_event queue where the pending * events will be pushed. Cannot be NULL. * : pointer to task(let) responsible for consuming the events. * Cannot be NULL. * <_private>: pointer to private data that will be handled to * <_private_free>: pointer to 'event_hdl_private_free' prototyped function * that will be called with after unsubscription is performed, * when no more events can refer to . */ #define EVENT_HDL_ID_ASYNC_TASK(_id, equeue, task, _private, _private_free) \ (struct event_hdl){ .id = _id, \ .dorigin = _EVENT_HDL_CALLING_PLACE, \ .async = EVENT_HDL_ASYNC_MODE_ADVANCED, \ .async_task = (struct tasklet *)task, \ .async_equeue = equeue, \ .private = _private, \ .private_free = _private_free } /* macro helper: * async version * same than EVENT_HDL_ASYNC - advanced mode: * you directly provide task and event_queue list. * * anonymous subscription (no lookup by id) * * : pointer to event_hdl_async_event queue where the pending * events will be pushed. Cannot be NULL. * : pointer to task(let) responsible for consuming the events * Cannot be NULL. * <_private>: pointer to private data that will be handled to * <_private_free>: pointer to 'event_hdl_private_free' prototyped function * that will be called with after unsubscription is performed, * when no more events can refer to . */ #define EVENT_HDL_ASYNC_TASK(equeue, task, _private, _private_free) \ EVENT_HDL_ID_ASYNC_TASK(0, equeue, task, _private, _private_free) /* register a new event subscription in * that will handle events * * This function requires you to use * EVENT_HDL_(TASK_)(A)SYNC() EVENT_HDL_ID_(TASK_)(A)SYNC() (choose wisely) * macro helpers to provide argument * * If is not specified (equals NULL): * global subscription list (process wide) will be used. * * For identified subscriptions (EVENT_HDL_ID_*), the function is safe against * concurrent subscriptions attempts with the same ID: the ID will only be * inserted once in the list and subsequent attempts will yield an error. * However, trying to register the same ID multiple times is considered as * an error (no specific error code is returned in this case) so the check should * be performed by the caller if it is expected. (The caller must ensure that the ID * is unique to prevent the error from being raised) * * Returns 1 in case of success, 0 in case of failure (invalid argument / memory error) */ int event_hdl_subscribe(event_hdl_sub_list *sub_list, struct event_hdl_sub_type e_type, struct event_hdl hdl); /* same as event_hdl_subscribe, but * returns the subscription ptr in case of success * or NULL in case of failure * subscription refcount is automatically incremented by 1 * so that ptr remains valid while you use it. * You must call event_hdl_drop() when you no longer * use it or event_hdl_unsubscribe() to unregister the * subscription */ struct event_hdl_sub *event_hdl_subscribe_ptr(event_hdl_sub_list *sub_list, struct event_hdl_sub_type e_type, struct event_hdl hdl); /* update subscription type: * if new type family does not match current family, does nothing * only subtype update is supported * Returns 1 for SUCCESS and 0 for FAILURE (update not supported) */ int event_hdl_resubscribe(struct event_hdl_sub *cur_sub, struct event_hdl_sub_type type); /* unregister an existing subscription * will automatically call event_hdl_drop() */ void event_hdl_unsubscribe(struct event_hdl_sub *sub); /* decrease subscription refcount by 1 * use this when you no longer use sub ptr * provided by event_hdl_subscribe_ptr or * to cancel previous event_hdl_take() */ void event_hdl_drop(struct event_hdl_sub *sub); /* increase subscription refcount by 1 * event_hdl_drop is needed when ptr * is not longer used * or event_hdl_unsubscribe to end the subscription */ void event_hdl_take(struct event_hdl_sub *sub); /* ------ EVENT_HDL_LOOKUP: subscription lookup operations from external code ------ */ /* use this function to unregister the subscription * within list. * If is NULL, global subscription list will be used. * Returns 1 for SUCCESS and 0 if not found */ int event_hdl_lookup_unsubscribe(event_hdl_sub_list *sub_list, uint64_t lookup_id); /* use this function to update subscription by within list * if new type family does not match current family, does nothing * only subtype update is supported * If is NULL, global subscription list will be used. * Returns 1 for SUCCESS and 0 if not found or not supported */ int event_hdl_lookup_resubscribe(event_hdl_sub_list *sub_list, uint64_t lookup_id, struct event_hdl_sub_type type); /* use this function to get a new reference ptr to the subscription * identified by * or event_hdl_unsubscribe to end the subscription * If is NULL, global subscription list will be used. * returns NULL if not found * returned ptr should be called with event_hdl_drop when no longer used */ struct event_hdl_sub *event_hdl_lookup_take(event_hdl_sub_list *sub_list, uint64_t lookup_id); /* pause an existing subscription * the subscription will no longer receive events (reversible) * This can be reverted thanks to _resume() function */ void event_hdl_pause(struct event_hdl_sub *sub); /* resume an existing subscription * that was previously paused using _pause() function */ void event_hdl_resume(struct event_hdl_sub *sub); /* Same as event_hdl_pause() for identified subscriptions: * use this function to pause the subscription * within list. * If is NULL, global subscription list will be used. * Returns 1 for SUCCESS and 0 if not found */ int event_hdl_lookup_pause(event_hdl_sub_list *sub_list, uint64_t lookup_id); /* Same as event_hdl_resume() for identified subscriptions: * use this function to resume the subscription * within list. * If is NULL, global subscription list will be used. * Returns 1 for SUCCESS and 0 if not found */ int event_hdl_lookup_resume(event_hdl_sub_list *sub_list, uint64_t lookup_id); /* ------ PUBLISHING FUNCTIONS ------ */ /* this macro is provided as an internal helper to automatically populate * data for fixed length structs as required by event_hdl publish function */ #define _EVENT_HDL_CB_DATA_ASSERT(size) \ ({ \ /* if this fails to compile \ * it means you need to fix \ * EVENT_HDL_ASYNC_EVENT_DATA \ * size in event_hdl-t.h \ */ \ __attribute__((unused)) \ char __static_assert[(size <= EVENT_HDL_ASYNC_EVENT_DATA) ? 1 : -1];\ (size); \ }) #define _EVENT_HDL_CB_DATA(data,size,mfree) \ (&(struct event_hdl_cb_data){ ._ptr = data, \ ._size = size, \ ._mfree = mfree }) /* Use this when 'safe' data is completely standalone */ #define EVENT_HDL_CB_DATA(data) \ _EVENT_HDL_CB_DATA(data, \ _EVENT_HDL_CB_DATA_ASSERT(sizeof(*data)), \ NULL) /* Use this when 'safe' data points to dynamically allocated members * that require freeing when the event is completely consumed * (data in itself may be statically allocated as with * EVENT_HDL_CB_DATA since the publish function will take * care of copying it for async handlers) * * mfree function will be called with data as argument * (or copy of data in async context) when the event is completely * consumed (sync and async handlers included). This will give you * enough context to perform the required cleanup steps. * * mfree should be prototyped like this: * void (*mfree)(const void *data) */ #define EVENT_HDL_CB_DATA_DM(data, mfree) \ _EVENT_HDL_CB_DATA(data, \ _EVENT_HDL_CB_DATA_ASSERT(sizeof(*data)), \ mfree) /* event publishing function * this function should be called from anywhere in the code to notify * about an and provide some relevant * that will be provided to subscriptions in * that are subscribed to . * should be provided using EVENT_HDL_CB_DATA helper macro * * Example: * struct event_hdl_cb_data_server cb_data; * * /... * cb_data initialization * .../ * * event_hdl_publish(NULL, EVENT_HDL_SUB_SERVER_UP, EVENT_HDL_CB_DATA(&cb_data)); */ int event_hdl_publish(event_hdl_sub_list *sub_list, struct event_hdl_sub_type e_type, const struct event_hdl_cb_data *data); /* ------ MISC/HELPER FUNCTIONS ------ */ /* returns a statically allocated string that is * the printable representation of * or "N/A" if does not exist */ const char *event_hdl_sub_type_to_string(struct event_hdl_sub_type sub_type); /* returns the internal sub_type corresponding * to the printable representation * or EVENT_HDL_SUB_NONE if no such event exists * (see event_hdl-t.h for the complete list of supported types) */ struct event_hdl_sub_type event_hdl_string_to_sub_type(const char *name); /* Use this from sync hdl to ensure the function is executed * in sync mode (and thus unsafe data is safe to use from this ctx) * This macro is meant to prevent unsafe data access * if code from sync function is copy pasted into * async function (or if sync handler is changed * to async handler without adapting the code) * FIXME: do we BUG_ON, or simply warn and return from the function? */ #define EVENT_HDL_ASSERT_SYNC(cb) BUG_ON(!cb->_sync) /* check if a and b sub types are part of the same family */ static inline int event_hdl_sub_family_equal(struct event_hdl_sub_type a, struct event_hdl_sub_type b) { return (a.family == b.family); } /* compares 2 event_hdl_sub_type structs * returns 1 if equal, 0 if not equal */ static inline int event_hdl_sub_type_equal(struct event_hdl_sub_type a, struct event_hdl_sub_type b) { return (a.family == b.family && a.subtype == b.subtype); } /* performs subtraction between A and B event_hdl_sub_type */ static inline struct event_hdl_sub_type event_hdl_sub_type_del(struct event_hdl_sub_type a, struct event_hdl_sub_type b) { if (unlikely(!a.family)) a.family = b.family; if (unlikely(a.family != b.family)) return a; a.subtype &= ~b.subtype; return a; } /* performs addition between A and B event_hdl_sub_type */ static inline struct event_hdl_sub_type event_hdl_sub_type_add(struct event_hdl_sub_type a, struct event_hdl_sub_type b) { if (unlikely(!a.family)) a.family = b.family; if (unlikely(a.family != b.family)) return a; a.subtype |= b.subtype; return a; } /* use this function when you consumed an event in async handler * (this will free the event so you must ensure that the event * is already removed from the event queue and that you * no longer make use of it) */ void event_hdl_async_free_event(struct event_hdl_async_event *e); /* use this for advanced async mode to initialize event queue */ static inline void event_hdl_async_equeue_init(event_hdl_async_equeue *queue) { MT_LIST_INIT(&queue->head); queue->size = 0; } /* use this for advanced async mode to pop an event from event queue */ static inline struct event_hdl_async_event *event_hdl_async_equeue_pop(event_hdl_async_equeue *queue) { struct event_hdl_async_event *event; event = MT_LIST_POP(&queue->head, struct event_hdl_async_event *, mt_list); if (event) HA_ATOMIC_DEC(&queue->size); return event; } /* use this for advanced async mode to check if the event queue is empty */ static inline int event_hdl_async_equeue_isempty(event_hdl_async_equeue *queue) { return MT_LIST_ISEMPTY(&queue->head); } /* use this for advanced async mode to check if the event queue size */ static inline uint32_t event_hdl_async_equeue_size(event_hdl_async_equeue *queue) { return HA_ATOMIC_LOAD(&queue->size); } /* use this to initialize event subscription list */ void event_hdl_sub_list_init(event_hdl_sub_list *sub_list); /* use this function when you need to destroy * event subscription list * All subscriptions will be removed and properly freed according * to their types */ void event_hdl_sub_list_destroy(event_hdl_sub_list *sub_list); /* event_hdl tunables */ extern struct event_hdl_tune event_hdl_tune; #endif /* _HAPROXY_EVENT_HDL_H */