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.
This commit is contained in:
parent
6d6787ba7c
commit
68e692da02
2
Makefile
2
Makefile
@ -935,7 +935,7 @@ OBJS += src/mux_h2.o src/mux_fcgi.o src/mux_h1.o src/tcpcheck.o \
|
||||
src/sha1.o src/proto_sockpair.o src/mailers.o src/lb_fwlc.o \
|
||||
src/ebmbtree.o src/cfgcond.o src/action.o src/xprt_handshake.o \
|
||||
src/protocol.o src/proto_uxst.o src/proto_udp.o src/lb_map.o \
|
||||
src/fix.o src/ev_select.o src/arg.o src/sock_inet.o \
|
||||
src/fix.o src/ev_select.o src/arg.o src/sock_inet.o src/event_hdl.o \
|
||||
src/mworker-prog.o src/hpack-dec.o src/cfgparse-tcp.o \
|
||||
src/sock_unix.o src/shctx.o src/proto_uxdg.o src/fcgi.o \
|
||||
src/eb64tree.o src/clock.o src/chunk.o src/cfgdiag.o src/signal.o \
|
||||
|
253
include/haproxy/event_hdl-t.h
Normal file
253
include/haproxy/event_hdl-t.h
Normal file
@ -0,0 +1,253 @@
|
||||
/*
|
||||
* include/haproxy/event_hdl-t.h
|
||||
* event handlers management definitions
|
||||
*
|
||||
* 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_T_H
|
||||
# define _HAPROXY_EVENT_HDL_T_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <haproxy/list-t.h>
|
||||
|
||||
/* 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 handlers
|
||||
* data consistency is guaranteed
|
||||
*/
|
||||
} safe;
|
||||
struct {
|
||||
/* unsafe data may only be used from sync handlers:
|
||||
* 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;
|
||||
};
|
||||
|
||||
/* FIXME: adjust if needed! Should be large enough
|
||||
* to support every struct event_hdl_cb_data_x types
|
||||
* BUG_ON check in publish/async_mode and static assert
|
||||
* in EVENT_HDL_CB_DATA will ensure this
|
||||
*/
|
||||
#define EVENT_HDL_ASYNC_EVENT_DATA (768)
|
||||
/* used internally to store a single copy of event data when dealing with
|
||||
* async handlers.
|
||||
* The same copy can be provided to multiple handlers to prevent memory waste:
|
||||
* refcount is used to keep track of references so that
|
||||
* data can be freed when not used anymore
|
||||
*/
|
||||
struct event_hdl_async_event_data
|
||||
{
|
||||
/* internal storage */
|
||||
char data[EVENT_HDL_ASYNC_EVENT_DATA];
|
||||
uint32_t refcount;
|
||||
};
|
||||
|
||||
/* type for storing event subscription type */
|
||||
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_list is an alias to mt_list (please use this for portability) */
|
||||
typedef struct mt_list event_hdl_sub_list;
|
||||
/* event_hdl_async_equeue is an alias to mt_list (please use this for portability) */
|
||||
typedef struct mt_list event_hdl_async_equeue;
|
||||
|
||||
/* subscription mgmt from event */
|
||||
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 *);
|
||||
};
|
||||
|
||||
/* single event structure pushed into async event queue
|
||||
* used by tasks async handlers
|
||||
*/
|
||||
struct event_hdl_async_event
|
||||
{
|
||||
struct mt_list mt_list;
|
||||
struct event_hdl_sub_type type;
|
||||
/* data wrapper - should not be used directly */
|
||||
struct event_hdl_async_event_data *_data;
|
||||
/* for easy data access,
|
||||
* points to _data->data if data is available
|
||||
*/
|
||||
void *data;
|
||||
void *private;
|
||||
struct event_hdl_sub_mgmt sub_mgmt;
|
||||
};
|
||||
|
||||
/* internal structure provided to function event_hdl_publish()
|
||||
* It contains ptr to data relevant to the event
|
||||
*/
|
||||
struct event_hdl_cb_data {
|
||||
/* internal use: ptr to struct event_hdl_cb_data_type */
|
||||
void *_ptr;
|
||||
/* internal use: holds actual data size*/
|
||||
size_t _size;
|
||||
};
|
||||
|
||||
/* struct provided to event_hdl_cb_* handlers
|
||||
* contains data related to the event
|
||||
* that triggered the handler
|
||||
*/
|
||||
struct event_hdl_cb
|
||||
{
|
||||
/* event type */
|
||||
struct event_hdl_sub_type e_type;
|
||||
/* event data */
|
||||
void *e_data;
|
||||
/* manage the subscription responsible for handing the event to us */
|
||||
const struct event_hdl_sub_mgmt *sub_mgmt;
|
||||
|
||||
/* used for the function wants to make sure
|
||||
* it runs in sync mode, and thus is eligible to access unsafe data.
|
||||
* This could save the day when users are copy-pasting function
|
||||
* logic from a sync handler to an async handler without
|
||||
* taking appropriate precautions and unsafe accesses are performed.
|
||||
*/
|
||||
uint8_t _sync;
|
||||
};
|
||||
|
||||
/* prototype for event_hdl_cb_sync function pointer */
|
||||
typedef void (*event_hdl_cb_sync)(const struct event_hdl_cb *cb, void *private);
|
||||
/* prototype for event_hdl_cb async function pointer */
|
||||
typedef void (*event_hdl_cb_async)(const struct event_hdl_cb *cb, void *private);
|
||||
/* prototype for event_hdl_private_free function pointer */
|
||||
typedef void (*event_hdl_private_free)(void *private);
|
||||
|
||||
/* tasklet forward declaration */
|
||||
struct tasklet;
|
||||
/* enum for sync mode */
|
||||
enum event_hdl_async_mode
|
||||
{
|
||||
EVENT_HDL_ASYNC_MODE_NORMAL = 1,
|
||||
EVENT_HDL_ASYNC_MODE_ADVANCED = 2
|
||||
};
|
||||
|
||||
/* event hdl, used when subscribing (and then associated with a subscription) */
|
||||
struct event_hdl {
|
||||
/* optional unique id (hash) for lookup */
|
||||
uint64_t id;
|
||||
/* handler debug: origin (initial event subscription calling place) */
|
||||
const char *dorigin;
|
||||
/* handler requires async mode:
|
||||
* EVENT_HDL_ASYNC_MODE_NORMAL = normal
|
||||
* EVENT_HDL_ASYNC_MODE_ADVANCED = advanced, single task wakeup
|
||||
*/
|
||||
uint8_t async;
|
||||
|
||||
union {
|
||||
event_hdl_cb_sync sync_ptr; /* if !async */
|
||||
event_hdl_cb_async async_ptr; /* only used if async==1 (normal) */
|
||||
};
|
||||
|
||||
/* ptr to async task responsible for consuming events */
|
||||
struct tasklet *async_task;
|
||||
/* used by async tasks to consume pending events */
|
||||
event_hdl_async_equeue *async_equeue;
|
||||
/* function ptr automatically called by:
|
||||
* async task when hdl is unregistered and private is no longer referenced
|
||||
* sync context when unregistering is performed
|
||||
*/
|
||||
event_hdl_private_free private_free;
|
||||
/* it is not safe to assume that private will not
|
||||
* be used anymore once hdl is unregistered:
|
||||
* with async handlers, private could still be referenced
|
||||
* in pending events to be consumed later by the task (by design).
|
||||
* If freeing private is needed, you must provide async_private_free
|
||||
* function pointer when registering.
|
||||
* It will be called when private is no longer used
|
||||
* after unregistering hdl to perform private cleanup.
|
||||
* (please use this even in sync mode so that subscription
|
||||
* can easily be turned into async mode later without breaking stuff)
|
||||
*/
|
||||
void *private;
|
||||
};
|
||||
|
||||
/* list elem: subscription (handler subscribed to specific events)
|
||||
*/
|
||||
struct event_hdl_sub {
|
||||
struct mt_list mt_list;
|
||||
/* event type subscription */
|
||||
struct event_hdl_sub_type sub;
|
||||
/* event handler */
|
||||
struct event_hdl hdl;
|
||||
/* used to guarantee that END event will be delivered
|
||||
* (memory is allocated when registering, no memory failure can occur at runtime)
|
||||
*/
|
||||
struct event_hdl_async_event *async_end;
|
||||
/* > 0 : subscription is referenced, don't free yet
|
||||
* use atomic OPS to write and read from it
|
||||
*/
|
||||
uint32_t refcount;
|
||||
/* TODO: atomic_call_counter for stats?! */
|
||||
};
|
||||
|
||||
#define ESUB_INDEX(n) (1 << n)
|
||||
|
||||
#define EVENT_HDL_SUB_TYPE(_family, _type) ((struct event_hdl_sub_type){ .family = _family, .subtype = ESUB_INDEX(_type) })
|
||||
#define EVENT_HDL_SUB_FAMILY(_family) ((struct event_hdl_sub_type){ .family = _family, .subtype = ~0 })
|
||||
|
||||
#define EVENT_HDL_SUB_NONE ((struct event_hdl_sub_type){ .family = 0, .subtype = 0})
|
||||
/* for async tasks: subscription is ending */
|
||||
#define EVENT_HDL_SUB_END ((struct event_hdl_sub_type){ .family = 0, .subtype = 1})
|
||||
|
||||
/* --------------------------------------- */
|
||||
|
||||
/* user defined event types are listed here
|
||||
* please reflect any change in theses macros in the subtype map
|
||||
* defined below that is used to perform string to event type and
|
||||
* event type to string conversions
|
||||
*/
|
||||
|
||||
/* TODO */
|
||||
|
||||
/* SERVER FAMILY, provides event_hdl_cb_data_server struct
|
||||
* (will be defined in haproxy/server-t.h)
|
||||
*/
|
||||
#define EVENT_HDL_SUB_SERVER EVENT_HDL_SUB_FAMILY(1)
|
||||
#define EVENT_HDL_SUB_SERVER_ADD EVENT_HDL_SUB_TYPE(1,1)
|
||||
#define EVENT_HDL_SUB_SERVER_DEL EVENT_HDL_SUB_TYPE(1,2)
|
||||
|
||||
/* --------------------------------------- */
|
||||
|
||||
/* Please reflect changes above in event_hdl_sub_type_map defined
|
||||
* in event_hdl.c file
|
||||
*/
|
||||
struct event_hdl_sub_type_map {
|
||||
const char *name;
|
||||
struct event_hdl_sub_type type;
|
||||
};
|
||||
|
||||
#endif /* _HAPROXY_EVENT_HDL_T_H */
|
436
include/haproxy/event_hdl.h
Normal file
436
include/haproxy/event_hdl.h
Normal file
@ -0,0 +1,436 @@
|
||||
/*
|
||||
* 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 */
|
758
src/event_hdl.c
Normal file
758
src/event_hdl.c
Normal file
@ -0,0 +1,758 @@
|
||||
/*
|
||||
* general purpose event handlers management
|
||||
*
|
||||
* Copyright 2022 HAProxy Technologies
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version
|
||||
* 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <haproxy/event_hdl.h>
|
||||
#include <haproxy/compiler.h>
|
||||
#include <haproxy/task.h>
|
||||
#include <haproxy/tools.h>
|
||||
#include <haproxy/errors.h>
|
||||
#include <haproxy/xxhash.h>
|
||||
|
||||
/* event types changes in event_hdl-t.h file should be reflected in the
|
||||
* map below to allow string to type and type to string conversions
|
||||
*/
|
||||
static struct event_hdl_sub_type_map event_hdl_sub_type_map[] = {
|
||||
{"NONE", EVENT_HDL_SUB_NONE},
|
||||
{"SERVER", EVENT_HDL_SUB_SERVER},
|
||||
{"SERVER_ADD", EVENT_HDL_SUB_SERVER_ADD},
|
||||
{"SERVER_DEL", EVENT_HDL_SUB_SERVER_DEL},
|
||||
};
|
||||
|
||||
/* internal types (only used in this file) */
|
||||
struct event_hdl_async_task_default_ctx
|
||||
{
|
||||
event_hdl_async_equeue e_queue; /* event queue list */
|
||||
event_hdl_cb_async func; /* event handling func */
|
||||
};
|
||||
|
||||
/* memory pools declarations */
|
||||
DECLARE_STATIC_POOL(pool_head_sub, "ehdl_sub", sizeof(struct event_hdl_sub));
|
||||
DECLARE_STATIC_POOL(pool_head_sub_event, "ehdl_sub_e", sizeof(struct event_hdl_async_event));
|
||||
DECLARE_STATIC_POOL(pool_head_sub_event_data, "ehdl_sub_ed", sizeof(struct event_hdl_async_event_data));
|
||||
DECLARE_STATIC_POOL(pool_head_sub_taskctx, "ehdl_sub_tctx", sizeof(struct event_hdl_async_task_default_ctx));
|
||||
|
||||
/* global subscription list (implicit where NULL is used as sublist argument) */
|
||||
static struct mt_list global_event_hdl_sub_list = MT_LIST_HEAD_INIT(global_event_hdl_sub_list);
|
||||
|
||||
/* TODO: will become a config tunable
|
||||
* ie: tune.events.max-async-notif-at-once
|
||||
*/
|
||||
static int event_hdl_async_max_notif_at_once = 10;
|
||||
|
||||
/* 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).
|
||||
*/
|
||||
inline uint64_t event_hdl_id(const char *scope, const char *name)
|
||||
{
|
||||
XXH64_state_t state;
|
||||
|
||||
XXH64_reset(&state, 0);
|
||||
XXH64_update(&state, scope, strlen(scope));
|
||||
XXH64_update(&state, name, strlen(name));
|
||||
return XXH64_digest(&state);
|
||||
}
|
||||
|
||||
/* takes a sub_type as input, returns corresponding sub_type
|
||||
* printable string or "N/A" if not found.
|
||||
* If not found, an error will be reported to stderr so the developers
|
||||
* know that a sub_type is missing its associated string in event_hdl-t.h
|
||||
*/
|
||||
const char *event_hdl_sub_type_to_string(struct event_hdl_sub_type sub_type)
|
||||
{
|
||||
int it;
|
||||
|
||||
for (it = 0; it < (int)(sizeof(event_hdl_sub_type_map) / sizeof(event_hdl_sub_type_map[0])); it++) {
|
||||
if (sub_type.family == event_hdl_sub_type_map[it].type.family &&
|
||||
sub_type.subtype == event_hdl_sub_type_map[it].type.subtype)
|
||||
return event_hdl_sub_type_map[it].name;
|
||||
}
|
||||
ha_alert("event_hdl-t.h: missing sub_type string representation.\n"
|
||||
"Please reflect any changes in event_hdl_sub_type_map.\n");
|
||||
return "N/A";
|
||||
}
|
||||
|
||||
/* 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)
|
||||
{
|
||||
int it;
|
||||
|
||||
for (it = 0; it < (int)(sizeof(event_hdl_sub_type_map) / sizeof(event_hdl_sub_type_map[0])); it++) {
|
||||
if (!strcmp(name, event_hdl_sub_type_map[it].name))
|
||||
return event_hdl_sub_type_map[it].type;
|
||||
}
|
||||
return EVENT_HDL_SUB_NONE;
|
||||
}
|
||||
|
||||
/* Takes <subscriptions> sub list as input, returns a printable string
|
||||
* containing every sub_types contained in <subscriptions>
|
||||
* separated by '|' char.
|
||||
* Returns NULL if no sub_types are found in <subscriptions>
|
||||
* This functions leverages memprintf, thus it is up to the
|
||||
* caller to free the returned value (if != NULL) when he no longer
|
||||
* uses it.
|
||||
*/
|
||||
char *event_hdl_sub_type_print(struct event_hdl_sub_type subscriptions)
|
||||
{
|
||||
char *out = NULL;
|
||||
int it;
|
||||
uint8_t first = 1;
|
||||
|
||||
for (it = 0; it < (int)(sizeof(event_hdl_sub_type_map) / sizeof(event_hdl_sub_type_map[0])); it++) {
|
||||
if (subscriptions.family == event_hdl_sub_type_map[it].type.family &&
|
||||
((subscriptions.subtype & event_hdl_sub_type_map[it].type.subtype) ==
|
||||
event_hdl_sub_type_map[it].type.subtype)) {
|
||||
if (first) {
|
||||
memprintf(&out, "%s", event_hdl_sub_type_map[it].name);
|
||||
first--;
|
||||
}
|
||||
else
|
||||
memprintf(&out, "%s%s%s", out, "|", event_hdl_sub_type_map[it].name);
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/* event_hdl debug/reporting function */
|
||||
typedef void (*event_hdl_report_hdl_state_func)(const char *fmt, ...);
|
||||
static void event_hdl_report_hdl_state(event_hdl_report_hdl_state_func report_func,
|
||||
const struct event_hdl *hdl, const char *what, const char *state)
|
||||
{
|
||||
report_func("[event_hdl]:%s (%s)'#%llu@%s': %s\n",
|
||||
what,
|
||||
(hdl->async) ? "ASYNC" : "SYNC",
|
||||
(long long unsigned int)hdl->id,
|
||||
hdl->dorigin,
|
||||
state);
|
||||
}
|
||||
|
||||
void event_hdl_async_free_event(struct event_hdl_async_event *e)
|
||||
{
|
||||
if (unlikely(event_hdl_sub_type_equal(e->type, EVENT_HDL_SUB_END))) {
|
||||
/* last event for hdl, special case */
|
||||
/* free subscription entry as we're the last one still using it
|
||||
* (it is already removed from mt_list, no race can occur)
|
||||
*/
|
||||
event_hdl_drop(e->sub_mgmt.this);
|
||||
}
|
||||
else if (e->_data &&
|
||||
HA_ATOMIC_SUB_FETCH(&e->_data->refcount, 1) == 0) {
|
||||
/* we are the last event holding reference to event data - free required */
|
||||
pool_free(pool_head_sub_event_data, e->_data); /* data wrapper */
|
||||
}
|
||||
pool_free(pool_head_sub_event, e);
|
||||
}
|
||||
|
||||
/* task handler used for normal async subscription mode
|
||||
* if you use advanced async subscription mode, you can use this
|
||||
* as an example to implement your own task wrapper
|
||||
*/
|
||||
static struct task *event_hdl_async_task_default(struct task *task, void *ctx, unsigned int state)
|
||||
{
|
||||
struct tasklet *tl = (struct tasklet *)task;
|
||||
struct event_hdl_async_task_default_ctx *task_ctx = ctx;
|
||||
struct event_hdl_async_event *event;
|
||||
int max_notif_at_once_it = 0;
|
||||
uint8_t done = 0;
|
||||
|
||||
/* run through e_queue, and call func() for each event
|
||||
* if we read END event, it indicates we must stop:
|
||||
* no more events to come (handler is unregistered)
|
||||
* so we must free task_ctx and stop task
|
||||
*/
|
||||
while (max_notif_at_once_it < event_hdl_async_max_notif_at_once &&
|
||||
(event = event_hdl_async_equeue_pop(&task_ctx->e_queue)))
|
||||
{
|
||||
if (event_hdl_sub_type_equal(event->type, EVENT_HDL_SUB_END)) {
|
||||
done = 1;
|
||||
event_hdl_async_free_event(event);
|
||||
/* break is normally not even required, EVENT_HDL_SUB_END
|
||||
* is guaranteed to be last event of e_queue
|
||||
* (because in normal mode one sub == one e_queue)
|
||||
*/
|
||||
break;
|
||||
}
|
||||
else {
|
||||
struct event_hdl_cb cb;
|
||||
|
||||
cb.e_type = event->type;
|
||||
cb.e_data = event->data;
|
||||
cb.sub_mgmt = &event->sub_mgmt;
|
||||
cb._sync = 0;
|
||||
|
||||
/* call user function */
|
||||
task_ctx->func(&cb, event->private);
|
||||
max_notif_at_once_it++;
|
||||
}
|
||||
event_hdl_async_free_event(event);
|
||||
}
|
||||
|
||||
if (done) {
|
||||
/* our job is done, subscription is over: no more events to come */
|
||||
pool_free(pool_head_sub_taskctx, task_ctx);
|
||||
tasklet_free(tl);
|
||||
return NULL;
|
||||
}
|
||||
return task;
|
||||
}
|
||||
|
||||
/* internal subscription mgmt functions */
|
||||
static inline struct event_hdl_sub_type _event_hdl_getsub(struct event_hdl_sub *cur_sub)
|
||||
{
|
||||
return cur_sub->sub;
|
||||
}
|
||||
|
||||
static inline struct event_hdl_sub_type _event_hdl_getsub_async(struct event_hdl_sub *cur_sub)
|
||||
{
|
||||
struct mt_list lock;
|
||||
struct event_hdl_sub_type type = EVENT_HDL_SUB_NONE;
|
||||
|
||||
lock = MT_LIST_LOCK_ELT(&cur_sub->mt_list);
|
||||
if (lock.next != &cur_sub->mt_list)
|
||||
type = _event_hdl_getsub(cur_sub);
|
||||
// else already removed
|
||||
MT_LIST_UNLOCK_ELT(&cur_sub->mt_list, lock);
|
||||
return type;
|
||||
}
|
||||
|
||||
static inline int _event_hdl_resub(struct event_hdl_sub *cur_sub, struct event_hdl_sub_type type)
|
||||
{
|
||||
if (!event_hdl_sub_family_equal(cur_sub->sub, type))
|
||||
return 0; /* family types differ, do nothing */
|
||||
cur_sub->sub.subtype = type.subtype; /* new subtype assignment */
|
||||
return 1;
|
||||
}
|
||||
|
||||
static inline int _event_hdl_resub_async(struct event_hdl_sub *cur_sub, struct event_hdl_sub_type type)
|
||||
{
|
||||
int status = 0;
|
||||
struct mt_list lock;
|
||||
|
||||
lock = MT_LIST_LOCK_ELT(&cur_sub->mt_list);
|
||||
if (lock.next != &cur_sub->mt_list)
|
||||
status = _event_hdl_resub(cur_sub, type);
|
||||
// else already removed
|
||||
MT_LIST_UNLOCK_ELT(&cur_sub->mt_list, lock);
|
||||
return status;
|
||||
}
|
||||
|
||||
static inline void _event_hdl_unsubscribe(struct event_hdl_sub *del_sub)
|
||||
{
|
||||
struct mt_list lock;
|
||||
|
||||
if (del_sub->hdl.async) {
|
||||
/* ASYNC SUB MODE */
|
||||
/* push EVENT_HDL_SUB_END (to notify the task that the subscription is dead) */
|
||||
|
||||
/* push END EVENT in busy state so we can safely wakeup
|
||||
* the task before releasing it.
|
||||
* Not doing that would expose us to a race where the task could've already
|
||||
* consumed the END event before the wakeup, and some tasks
|
||||
* kill themselves (ie: normal async mode) when they receive such event
|
||||
*/
|
||||
lock = MT_LIST_APPEND_LOCKED(del_sub->hdl.async_equeue, &del_sub->async_end->mt_list);
|
||||
|
||||
/* wake up the task */
|
||||
tasklet_wakeup(del_sub->hdl.async_task);
|
||||
|
||||
/* unlock END EVENT (we're done, the task is now free to consume it) */
|
||||
MT_LIST_UNLOCK_ELT(&del_sub->async_end->mt_list, lock);
|
||||
|
||||
/* we don't free sub here
|
||||
* freeing will be performed by async task so it can safely rely
|
||||
* on the pointer until it notices it
|
||||
*/
|
||||
} else {
|
||||
/* SYNC SUB MODE */
|
||||
|
||||
/* we can directly free the subscription:
|
||||
* no other thread can access it since we successfully
|
||||
* removed it from the list
|
||||
*/
|
||||
event_hdl_drop(del_sub);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void _event_hdl_unsubscribe_async(struct event_hdl_sub *del_sub)
|
||||
{
|
||||
if (!MT_LIST_DELETE(&del_sub->mt_list))
|
||||
return; /* already removed (but may be pending in e_queues) */
|
||||
_event_hdl_unsubscribe(del_sub);
|
||||
}
|
||||
|
||||
/* sub_mgmt function pointers (for handlers) */
|
||||
static struct event_hdl_sub_type event_hdl_getsub_sync(const struct event_hdl_sub_mgmt *mgmt)
|
||||
{
|
||||
if (!mgmt)
|
||||
return EVENT_HDL_SUB_NONE;
|
||||
|
||||
if (!mgmt->this)
|
||||
return EVENT_HDL_SUB_NONE; /* already removed from sync ctx */
|
||||
return _event_hdl_getsub(mgmt->this);
|
||||
}
|
||||
|
||||
static struct event_hdl_sub_type event_hdl_getsub_async(const struct event_hdl_sub_mgmt *mgmt)
|
||||
{
|
||||
if (!mgmt)
|
||||
return EVENT_HDL_SUB_NONE;
|
||||
|
||||
return _event_hdl_getsub_async(mgmt->this);
|
||||
}
|
||||
|
||||
static int event_hdl_resub_sync(const struct event_hdl_sub_mgmt *mgmt, struct event_hdl_sub_type type)
|
||||
{
|
||||
if (!mgmt)
|
||||
return 0;
|
||||
|
||||
if (!mgmt->this)
|
||||
return 0; /* already removed from sync ctx */
|
||||
return _event_hdl_resub(mgmt->this, type);
|
||||
}
|
||||
|
||||
static int event_hdl_resub_async(const struct event_hdl_sub_mgmt *mgmt, struct event_hdl_sub_type type)
|
||||
{
|
||||
if (!mgmt)
|
||||
return 0;
|
||||
|
||||
return _event_hdl_resub_async(mgmt->this, type);
|
||||
}
|
||||
|
||||
static void event_hdl_unsubscribe_sync(const struct event_hdl_sub_mgmt *mgmt)
|
||||
{
|
||||
if (!mgmt)
|
||||
return;
|
||||
|
||||
if (!mgmt->this)
|
||||
return; /* already removed from sync ctx */
|
||||
|
||||
/* assuming that publish sync code will notice that mgmt->this is NULL
|
||||
* and will perform the list removal using MT_LIST_DELETE_SAFE and
|
||||
* _event_hdl_unsubscribe()
|
||||
* while still owning the lock
|
||||
*/
|
||||
((struct event_hdl_sub_mgmt *)mgmt)->this = NULL;
|
||||
}
|
||||
|
||||
static void event_hdl_unsubscribe_async(const struct event_hdl_sub_mgmt *mgmt)
|
||||
{
|
||||
if (!mgmt)
|
||||
return;
|
||||
|
||||
_event_hdl_unsubscribe_async(mgmt->this);
|
||||
}
|
||||
|
||||
#define EVENT_HDL_SUB_MGMT_ASYNC(_sub) (struct event_hdl_sub_mgmt){ .this = _sub, \
|
||||
.getsub = event_hdl_getsub_async, \
|
||||
.resub = event_hdl_resub_async, \
|
||||
.unsub = event_hdl_unsubscribe_async}
|
||||
#define EVENT_HDL_SUB_MGMT_SYNC(_sub) (struct event_hdl_sub_mgmt){ .this = _sub, \
|
||||
.getsub = event_hdl_getsub_sync, \
|
||||
.resub = event_hdl_resub_sync, \
|
||||
.unsub = event_hdl_unsubscribe_sync}
|
||||
|
||||
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)
|
||||
{
|
||||
struct event_hdl_sub *new_sub;
|
||||
struct mt_list *elt1, elt2;
|
||||
uint8_t found = 0;
|
||||
struct event_hdl_async_task_default_ctx *task_ctx;
|
||||
|
||||
if (!sub_list)
|
||||
sub_list = &global_event_hdl_sub_list; /* fall back to global list */
|
||||
|
||||
/* hdl API consistency check */
|
||||
/*FIXME: do we need to ensure that if private is set, private_free should be set as well? */
|
||||
BUG_ON((!hdl.async && !hdl.sync_ptr) ||
|
||||
(hdl.async == EVENT_HDL_ASYNC_MODE_NORMAL && !hdl.async_ptr) ||
|
||||
(hdl.async == EVENT_HDL_ASYNC_MODE_ADVANCED &&
|
||||
(!hdl.async_equeue || !hdl.async_task)));
|
||||
|
||||
/* first check if such identified hdl is not already registered */
|
||||
if (hdl.id) {
|
||||
mt_list_for_each_entry_safe(new_sub, sub_list, mt_list, elt1, elt2) {
|
||||
if (hdl.id == new_sub->hdl.id) {
|
||||
/* we found matching registered hdl */
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (found) {
|
||||
/* error already registered */
|
||||
event_hdl_report_hdl_state(ha_warning, &hdl, "SUB", "could not subscribe: subscription with this id already exists");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
new_sub = pool_alloc(pool_head_sub);
|
||||
if (new_sub == NULL) {
|
||||
goto new_sub_memory_error;
|
||||
}
|
||||
|
||||
/* assignments */
|
||||
new_sub->sub.family = e_type.family;
|
||||
new_sub->sub.subtype = e_type.subtype;
|
||||
new_sub->hdl = hdl;
|
||||
|
||||
if (hdl.async) {
|
||||
/* async END event pre-allocation */
|
||||
new_sub->async_end = pool_alloc(pool_head_sub_event);
|
||||
if (!new_sub->async_end) {
|
||||
/* memory error */
|
||||
goto new_sub_memory_error_event_end;
|
||||
}
|
||||
if (hdl.async == EVENT_HDL_ASYNC_MODE_NORMAL) {
|
||||
/* normal mode: no task provided, we must initialize it */
|
||||
|
||||
/* initialize task context */
|
||||
task_ctx = pool_alloc(pool_head_sub_taskctx);
|
||||
|
||||
if (!task_ctx) {
|
||||
/* memory error */
|
||||
goto new_sub_memory_error_task_ctx;
|
||||
}
|
||||
MT_LIST_INIT(&task_ctx->e_queue);
|
||||
task_ctx->func = new_sub->hdl.async_ptr;
|
||||
|
||||
new_sub->hdl.async_equeue = &task_ctx->e_queue;
|
||||
new_sub->hdl.async_task = tasklet_new();
|
||||
|
||||
if (!new_sub->hdl.async_task) {
|
||||
/* memory error */
|
||||
goto new_sub_memory_error_task;
|
||||
}
|
||||
new_sub->hdl.async_task->context = task_ctx;
|
||||
new_sub->hdl.async_task->process = event_hdl_async_task_default;
|
||||
}
|
||||
/* registration cannot fail anymore */
|
||||
|
||||
/* initialize END event (used to notify about subscription ending)
|
||||
* used by both normal and advanced mode:
|
||||
* - to safely terminate the task in normal mode
|
||||
* - to safely free subscription and
|
||||
* keep track of active subscriptions in advanced mode
|
||||
*/
|
||||
new_sub->async_end->type = EVENT_HDL_SUB_END;
|
||||
new_sub->async_end->sub_mgmt = EVENT_HDL_SUB_MGMT_ASYNC(new_sub);
|
||||
new_sub->async_end->private = new_sub->hdl.private;
|
||||
new_sub->async_end->_data = NULL;
|
||||
MT_LIST_INIT(&new_sub->async_end->mt_list);
|
||||
}
|
||||
/* set refcount to 2:
|
||||
* 1 for handler (because handler can manage the subscription itself)
|
||||
* 1 for caller (will be dropped automatically if caller use the non-ptr version)
|
||||
*/
|
||||
new_sub->refcount = 2;
|
||||
|
||||
/* Append in list (global or user specified list).
|
||||
* For now, append when sync mode, and insert when async mode
|
||||
* so that async handlers are executed first
|
||||
*/
|
||||
MT_LIST_INIT(&new_sub->mt_list);
|
||||
if (hdl.async) {
|
||||
/* async mode, insert at the beginning of the list */
|
||||
MT_LIST_INSERT(sub_list, &new_sub->mt_list);
|
||||
} else {
|
||||
/* sync mode, append at the end of the list */
|
||||
MT_LIST_APPEND(sub_list, &new_sub->mt_list);
|
||||
}
|
||||
|
||||
return new_sub;
|
||||
|
||||
new_sub_memory_error_task:
|
||||
pool_free(pool_head_sub_taskctx, task_ctx);
|
||||
new_sub_memory_error_task_ctx:
|
||||
pool_free(pool_head_sub_event, new_sub->async_end);
|
||||
new_sub_memory_error_event_end:
|
||||
pool_free(pool_head_sub, new_sub);
|
||||
new_sub_memory_error:
|
||||
|
||||
event_hdl_report_hdl_state(ha_warning, &hdl, "SUB", "could not register subscription due to memory error");
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void event_hdl_take(struct event_hdl_sub *sub)
|
||||
{
|
||||
HA_ATOMIC_INC(&sub->refcount);
|
||||
}
|
||||
|
||||
void event_hdl_drop(struct event_hdl_sub *sub)
|
||||
{
|
||||
if (HA_ATOMIC_SUB_FETCH(&sub->refcount, 1) != 0)
|
||||
return;
|
||||
|
||||
/* we are the last event holding reference to event data - free required */
|
||||
if (sub->hdl.private_free) {
|
||||
/* free private data if specified upon registration */
|
||||
sub->hdl.private_free(sub->hdl.private);
|
||||
}
|
||||
pool_free(pool_head_sub, sub);
|
||||
}
|
||||
|
||||
int event_hdl_resubscribe(struct event_hdl_sub *cur_sub, struct event_hdl_sub_type type)
|
||||
{
|
||||
return _event_hdl_resub_async(cur_sub, type);
|
||||
}
|
||||
|
||||
void event_hdl_unsubscribe(struct event_hdl_sub *del_sub)
|
||||
{
|
||||
_event_hdl_unsubscribe_async(del_sub);
|
||||
/* drop refcount, assuming caller no longer use ptr */
|
||||
event_hdl_drop(del_sub);
|
||||
}
|
||||
|
||||
int event_hdl_subscribe(event_hdl_sub_list *sub_list, struct event_hdl_sub_type e_type, struct event_hdl hdl)
|
||||
{
|
||||
struct event_hdl_sub *sub;
|
||||
|
||||
sub = event_hdl_subscribe_ptr(sub_list, e_type, hdl);
|
||||
if (sub) {
|
||||
/* drop refcount because the user is not willing to hold a reference */
|
||||
event_hdl_drop(sub);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Subscription external lookup functions
|
||||
*/
|
||||
int event_hdl_lookup_unsubscribe(event_hdl_sub_list *sub_list,
|
||||
uint64_t lookup_id)
|
||||
{
|
||||
struct event_hdl_sub *del_sub = NULL;
|
||||
struct mt_list *elt1, elt2;
|
||||
int found = 0;
|
||||
|
||||
if (!sub_list)
|
||||
sub_list = &global_event_hdl_sub_list; /* fall back to global list */
|
||||
|
||||
mt_list_for_each_entry_safe(del_sub, sub_list, mt_list, elt1, elt2) {
|
||||
if (lookup_id == del_sub->hdl.id) {
|
||||
/* we found matching registered hdl */
|
||||
MT_LIST_DELETE_SAFE(elt1);
|
||||
_event_hdl_unsubscribe(del_sub);
|
||||
found = 1;
|
||||
break; /* id is unique, stop searching */
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
int event_hdl_lookup_resubscribe(event_hdl_sub_list *sub_list,
|
||||
uint64_t lookup_id, struct event_hdl_sub_type type)
|
||||
{
|
||||
struct event_hdl_sub *cur_sub = NULL;
|
||||
struct mt_list *elt1, elt2;
|
||||
int status = 0;
|
||||
|
||||
if (!sub_list)
|
||||
sub_list = &global_event_hdl_sub_list; /* fall back to global list */
|
||||
|
||||
mt_list_for_each_entry_safe(cur_sub, sub_list, mt_list, elt1, elt2) {
|
||||
if (lookup_id == cur_sub->hdl.id) {
|
||||
/* we found matching registered hdl */
|
||||
status = _event_hdl_resub(cur_sub, type);
|
||||
break; /* id is unique, stop searching */
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
struct event_hdl_sub *event_hdl_lookup_take(event_hdl_sub_list *sub_list,
|
||||
uint64_t lookup_id)
|
||||
{
|
||||
struct event_hdl_sub *cur_sub = NULL;
|
||||
struct mt_list *elt1, elt2;
|
||||
uint8_t found = 0;
|
||||
|
||||
if (!sub_list)
|
||||
sub_list = &global_event_hdl_sub_list; /* fall back to global list */
|
||||
|
||||
mt_list_for_each_entry_safe(cur_sub, sub_list, mt_list, elt1, elt2) {
|
||||
if (lookup_id == cur_sub->hdl.id) {
|
||||
/* we found matching registered hdl */
|
||||
event_hdl_take(cur_sub);
|
||||
found = 1;
|
||||
break; /* id is unique, stop searching */
|
||||
}
|
||||
}
|
||||
if (found)
|
||||
return cur_sub;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* event publishing functions
|
||||
*/
|
||||
static int _event_hdl_publish(event_hdl_sub_list *sub_list, struct event_hdl_sub_type e_type,
|
||||
const struct event_hdl_cb_data *data)
|
||||
{
|
||||
struct event_hdl_sub *cur_sub;
|
||||
struct mt_list *elt1, elt2;
|
||||
struct event_hdl_async_event_data *async_data = NULL; /* reuse async data for multiple async hdls */
|
||||
int error = 0;
|
||||
|
||||
mt_list_for_each_entry_safe(cur_sub, sub_list, mt_list, elt1, elt2) {
|
||||
/* notify each function that has subscribed to sub_family.type */
|
||||
if ((cur_sub->sub.family == e_type.family) &&
|
||||
((cur_sub->sub.subtype & e_type.subtype) == e_type.subtype)) {
|
||||
/* hdl should be notified */
|
||||
if (!cur_sub->hdl.async) {
|
||||
/* sync mode: simply call cb pointer
|
||||
* it is up to the callee to schedule a task if needed or
|
||||
* take specific precautions in order to return as fast as possible
|
||||
* and not use locks that are already held by the caller
|
||||
*/
|
||||
struct event_hdl_cb cb;
|
||||
struct event_hdl_sub_mgmt sub_mgmt;
|
||||
|
||||
sub_mgmt = EVENT_HDL_SUB_MGMT_SYNC(cur_sub);
|
||||
cb.e_type = e_type;
|
||||
if (data)
|
||||
cb.e_data = data->_ptr;
|
||||
else
|
||||
cb.e_data = NULL;
|
||||
cb.sub_mgmt = &sub_mgmt;
|
||||
cb._sync = 1;
|
||||
|
||||
/* call user function */
|
||||
cur_sub->hdl.sync_ptr(&cb, cur_sub->hdl.private);
|
||||
|
||||
if (!sub_mgmt.this) {
|
||||
/* user has performed hdl unsub
|
||||
* we must remove it from the list
|
||||
*/
|
||||
MT_LIST_DELETE_SAFE(elt1);
|
||||
/* then free it */
|
||||
_event_hdl_unsubscribe(cur_sub);
|
||||
}
|
||||
} else {
|
||||
/* async mode: here we need to prepare event data
|
||||
* and push it to the event_queue of the task(s)
|
||||
* responsible for consuming the events of current
|
||||
* subscription.
|
||||
* Once the event is pushed, we wake up the associated task.
|
||||
* This feature depends on <haproxy/task> that also
|
||||
* depends on <haproxy/pool>:
|
||||
* If STG_PREPARE+STG_POOL is not performed prior to publishing to
|
||||
* async handler, program may crash.
|
||||
* Hopefully, STG_PREPARE+STG_POOL should be done early in
|
||||
* HAProxy startup sequence.
|
||||
*/
|
||||
struct event_hdl_async_event *new_event;
|
||||
|
||||
new_event = pool_alloc(pool_head_sub_event);
|
||||
if (!new_event) {
|
||||
error = 1;
|
||||
break; /* stop on error */
|
||||
}
|
||||
new_event->type = e_type;
|
||||
new_event->private = cur_sub->hdl.private;
|
||||
new_event->sub_mgmt = EVENT_HDL_SUB_MGMT_ASYNC(cur_sub);
|
||||
if (data) {
|
||||
/* if this fails, please adjust EVENT_HDL_ASYNC_EVENT_DATA in
|
||||
* event_hdl-t.h file
|
||||
*/
|
||||
BUG_ON(data->_size > sizeof(async_data->data));
|
||||
if (!async_data) {
|
||||
/* first async hdl reached - preparing async_data cache */
|
||||
async_data = pool_alloc(pool_head_sub_event_data);
|
||||
if (!async_data) {
|
||||
error = 1;
|
||||
pool_free(pool_head_sub_event, new_event);
|
||||
break; /* stop on error */
|
||||
}
|
||||
|
||||
/* async data assignment */
|
||||
memcpy(async_data->data, data->_ptr, data->_size);
|
||||
async_data->refcount = 0; /* initialize async->refcount (first use, atomic operation not required) */
|
||||
}
|
||||
new_event->_data = async_data;
|
||||
new_event->data = async_data->data;
|
||||
/* increment refcount because multiple hdls could
|
||||
* use the same async_data
|
||||
*/
|
||||
HA_ATOMIC_INC(&async_data->refcount);
|
||||
} else
|
||||
new_event->data = NULL;
|
||||
|
||||
/* appending new event to event hdl queue */
|
||||
MT_LIST_INIT(&new_event->mt_list);
|
||||
MT_LIST_APPEND(cur_sub->hdl.async_equeue, &new_event->mt_list);
|
||||
|
||||
/* wake up the task */
|
||||
tasklet_wakeup(cur_sub->hdl.async_task);
|
||||
} /* end async mode */
|
||||
} /* end hdl should be notified */
|
||||
} /* end mt_list */
|
||||
if (error) {
|
||||
event_hdl_report_hdl_state(ha_warning, &cur_sub->hdl, "PUBLISH", "memory error");
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Publish function should not be used from high calling rate or time sensitive
|
||||
* places for now, because list lookup based on e_type is not optimized at
|
||||
* all!
|
||||
* Returns 1 in case of SUCCESS:
|
||||
* Subscribed handlers were notified successfully
|
||||
* Returns 0 in case of FAILURE:
|
||||
* FAILURE means memory error while handling the very first async handler from
|
||||
* the subscription list.
|
||||
* As async handlers are executed first within the list, when such failure occurs
|
||||
* you can safely assume that no events were published for the current call
|
||||
*/
|
||||
int event_hdl_publish(event_hdl_sub_list *sub_list,
|
||||
struct event_hdl_sub_type e_type, const struct event_hdl_cb_data *data)
|
||||
{
|
||||
if (!e_type.family) {
|
||||
/* do nothing, these types are reserved for internal use only
|
||||
* (ie: unregistering) */
|
||||
return 0;
|
||||
}
|
||||
if (sub_list) {
|
||||
/* if sublist is provided, first publish event to list subscribers */
|
||||
return _event_hdl_publish(sub_list, e_type, data);
|
||||
} else {
|
||||
/* publish to global list */
|
||||
return _event_hdl_publish(&global_event_hdl_sub_list, e_type, data);
|
||||
}
|
||||
}
|
||||
|
||||
/* when a subscription list is no longer used, call this
|
||||
* to do the cleanup and make sure all related subscriptions are
|
||||
* safely ended according to their types
|
||||
*/
|
||||
void event_hdl_sub_list_destroy(event_hdl_sub_list *sub_list)
|
||||
{
|
||||
struct event_hdl_sub *cur_sub;
|
||||
struct mt_list *elt1, elt2;
|
||||
|
||||
if (!sub_list)
|
||||
sub_list = &global_event_hdl_sub_list; /* fall back to global list */
|
||||
mt_list_for_each_entry_safe(cur_sub, sub_list, mt_list, elt1, elt2) {
|
||||
/* remove cur elem from list */
|
||||
MT_LIST_DELETE_SAFE(elt1);
|
||||
/* then free it */
|
||||
_event_hdl_unsubscribe(cur_sub);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user