haproxy/include/haproxy/event_hdl.h
Aurelien DARRAGON 68e692da02 MINOR: event_hdl: add event handler base api
Adding base code to provide subscribe/publish API for internal
events processing.

event_hdl provides two complementary APIs, both are implemented
in src/event_hdl.c and include/haproxy/event_hdl{-t.h,.h}:

	One API targeting developers that want to register event handlers
	that will be notified on specific events.
	(SUBSCRIBE)

	One API targeting developers that want to notify registered handlers
	about an event.
	(PUBLISH)

This feature is being considered to address the following scenarios:
	- mailers code refactoring (getting rid of deprecated
	tcp-check ruleset implementation)
	- server events from lua code (registering user defined
	lua function that is executed with relevant data when a
	server is dynamically added/removed or on server state change)
	- providing a stable and easy to use API for upcoming
	developments that rely on specific events to perform actions.
	(e.g: ressource cleanup when a server is deleted from haproxy)

At this time though, we don't have much use cases in mind in addition to
server events handling, but the API is aimed at being multipurpose
so that new event families, with their own particularities, can be
easily implemented afterwards (and hopefully) without requiring breaking
changes to the API.

Moreover, you should know that the API was not designed to cope well
with high rate event publishing.
Mostly because publishing means iterating over unsorted subscriber list.
So it won't scale well as subscriber list increases, but it is intended in
order to keep the code simple and versatile.

Instead, it is assumed that events implemented using this API
should be periodic events, and that events related to critical
io/networking processing should be handled using
dedicated facilities anyway.
(After all, this is meant to be a general purpose event API)

Apart from being easily extensible, one of the main goals of this API is
to make subscriber code as simple and safe as possible.

This is done by offering multiple event handling modes:
	- SYNC mode:
		publishing code directly
		leverages handler code (callback function)
		and handler code has a direct access to "live" event data
		(pointers mostly, alongside with lock hints/context
		so that accessing data pointers can be done properly)
	- normal ASYNC mode:
		handler is executed in a backward compatible way with sync mode,
		so that it is easy to switch from and to SYNC/ASYNC mode.
		Only here the handler has access to "offline" event data, and
		not "live" data (ptrs) so that data consistency is guaranteed.
		By offline, you should understand "snapshot" of relevant data
		at the time of the event, so that the handler can consume it
		later (even if associated ressource is not valid anymore)
	- advanced ASYNC mode
		same as normal ASYNC mode, but here handler is not a function
		that is executed with event data passed as argument: handler is a
		user defined tasklet that is notified when event occurs.
		The tasklet may consume pending events and associated data
		through its own message queue.

ASYNC mode should be considered first if you don't rely on live event
data and you wan't to make sure that your code has the lowest impact
possible on publisher code. (ie: you don't want to break stuff)

Internal API documentation will follow:
	You will find more details about the notions we roughly approached here.
2022-12-02 09:40:52 +01:00

437 lines
16 KiB
C

/*
* 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 <haproxy/event_hdl-t.h>
#include <haproxy/list.h>
/* 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 <scope> x <name>
* It is your responsibility to make sure <scope> 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
* <func>: pointer to 'event_hdl_cb_sync' prototyped function
* <_private>: pointer to private data that will be handled to <func>
* <_private_free>: pointer to 'event_hdl_private_free' prototyped function
* that will be called with <private> 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)
*
* <func>: pointer to 'event_hdl_cb_sync' prototyped function
* <_private>: pointer to private data that will be handled to <func>
* <_private_free>: pointer to 'event_hdl_private_free' prototyped function
* that will be called with <private> 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
* <func>: pointer to 'event_hdl_cb_sync' prototyped function
* <_private>: pointer to private data that will be handled to <func>
* <_private_free>: pointer to 'event_hdl_private_free' prototyped function
* that will be called with <private> after unsubscription is performed,
* when no more events can refer to <private>.
*/
#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)
*
* <func>: pointer to 'event_hdl_cb_sync' prototyped function
* <_private>: pointer to private data that will be handled to <func>
* <_private_free>: pointer to 'event_hdl_private_free' prototyped function
* that will be called with <private> after unsubscription is performed,
* when no more events can refer to <private>.
*/
#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
* <equeue>: pointer to event_hdl_async_event queue where the pending
* events will be pushed. Cannot be NULL.
* <task>: pointer to tasklet responsible for consuming the events.
* Cannot be NULL.
* <_private>: pointer to private data that will be handled to <func>
* <_private_free>: pointer to 'event_hdl_private_free' prototyped function
* that will be called with <private> after unsubscription is performed,
* when no more events can refer to <private>.
*/
#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 = 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)
*
* <equeue>: pointer to event_hdl_async_event queue where the pending
* events will be pushed. Cannot be NULL.
* <task>: pointer to tasklet responsible for consuming the events
* Cannot be NULL.
* <_private>: pointer to private data that will be handled to <func>
* <_private_free>: pointer to 'event_hdl_private_free' prototyped function
* that will be called with <private> after unsubscription is performed,
* when no more events can refer to <private>.
*/
#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 <sub_list>
* that will handle <e_type> events
*
* This function requires you to use
* EVENT_HDL_(TASK_)(A)SYNC() EVENT_HDL_ID_(TASK_)(A)SYNC() (choose wisely)
* macro helpers to provide <hdl> argument
*
* If <sub_list> is not specified (equals NULL):
* global subscription list (process wide) will be used.
*
* 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 <sub>
* 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 <lookup_ip>
* within <sub_list> list.
* If <sub_list> 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 <lookup_id> within <sub_list> list
* if new type family does not match current family, does nothing
* only subtype update is supported
* If <sub_list> 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 <id>
* or event_hdl_unsubscribe to end the subscription
* If <sub_list> 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);
/* ------ PUBLISHING FUNCTIONS ------ */
/* this macro is provided as an internal helper for EVENT_HDL_TRIGGER to automatically
* populate data 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) \
(&(struct event_hdl_cb_data){ ._ptr = data, \
._size = size })
#define EVENT_HDL_CB_DATA(data) _EVENT_HDL_CB_DATA(data, _EVENT_HDL_CB_DATA_ASSERT(sizeof(*data)))
/* event publishing function
* this function should be called from anywhere in the code to notify
* about an <e_type> and provide some relevant <data>
* that will be provided to subscriptions in <sub_list>
* that are subscribed to <e_type>.
* <data> 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 <sub_type>
* or "N/A" if <sub_type> 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 <name>
* 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);
}
/* 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)
{
return MT_LIST_POP(queue, struct event_hdl_async_event *, mt_list);
}
/* use this to initialize an event subscription list
* (event_hdl_sub_list)
*/
static inline void event_hdl_sub_list_init(event_hdl_sub_list *sub_list)
{
MT_LIST_INIT(sub_list);
}
/* use this function when you need to destroy <sub_list>
* subscription list
* All subscriptions will be removed and properly freed according
* to their types
* If <sub_list> is NULL, global subscription list will be used.
*/
void event_hdl_sub_list_destroy(event_hdl_sub_list *sub_list);
#endif /* _HAPROXY_EVENT_HDL_H */