mirror of
https://git.haproxy.org/git/haproxy.git/
synced 2025-08-13 18:46:57 +02:00
We're using srv_update_status() as the only event source or UP/DOWN server events in an attempt to simplify the support for these 2 events. It seems srv_update_status() is the common path for server state changes anyway Tested with server state updated from various sources: - the cli - server-state file (maybe we could disable this or at least don't publish in global event queue in the future if it ends in slower startup for setups relying on huge server state files) - dns records (ie: srv template) (again, could be fined tuned to only publish in server specific subscriber list and no longer in global subscription list if mass dns update tend to slow down srv_update_status()) - normal checks and observe checks (HCHK_STATUS_HANA) (same as above, if checks related state update storms are expected) - lua scripts - html stats page (admin mode)
761 lines
24 KiB
C
761 lines
24 KiB
C
/*
|
|
* 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},
|
|
{"SERVER_UP", EVENT_HDL_SUB_SERVER_UP},
|
|
{"SERVER_DOWN", EVENT_HDL_SUB_SERVER_DOWN},
|
|
};
|
|
|
|
/* 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);
|
|
}
|
|
}
|