summaryrefslogtreecommitdiffstats
path: root/include/haproxy/event_hdl.h
blob: 5a7ee661350eb473771430ffe53c5df96e0805b8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
/*
 * 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 task(let) 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 = (struct tasklet *)task,		\
			    .async_equeue = equeue,				\
			    .private = _private,				\
			    .private_free = _private_free }

/* macro helper:
 * async version
 * same than EVENT_HDL_ASYNC - advanced mode:
 * you directly provide task and event_queue list.
 *
 * anonymous subscription (no lookup by id)
 *
 * <equeue>: pointer to event_hdl_async_event queue where the pending
 * events will be pushed. Cannot be NULL.
 * <task>: pointer to task(let) 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.
 *
 * For identified subscriptions (EVENT_HDL_ID_*), the function is safe against
 * concurrent subscriptions attempts with the same ID: the ID will only be
 * inserted once in the list and subsequent attempts will yield an error.
 * However, trying to register the same ID multiple times is considered as
 * an error (no specific error code is returned in this case) so the check should
 * be performed by the caller if it is expected. (The caller must ensure that the ID
 * is unique to prevent the error from being raised)
 *
 * Returns 1 in case of success, 0 in case of failure (invalid argument / memory error)
 */
int event_hdl_subscribe(event_hdl_sub_list *sub_list,
                        struct event_hdl_sub_type e_type, struct event_hdl hdl);

/* same as event_hdl_subscribe, but
 * returns the subscription ptr in case of success
 * or NULL in case of failure
 * subscription refcount is automatically incremented by 1
 * so that ptr remains valid while you use it.
 * You must call event_hdl_drop() when you no longer
 * use it or event_hdl_unsubscribe() to unregister the
 * subscription
 */
struct event_hdl_sub *event_hdl_subscribe_ptr(event_hdl_sub_list *sub_list,
                                              struct event_hdl_sub_type e_type, struct event_hdl hdl);

/* update subscription type:
 * if new type family does not match current family, does nothing
 * only subtype update is supported
 * Returns 1 for SUCCESS and 0 for FAILURE (update not supported)
 */
int event_hdl_resubscribe(struct event_hdl_sub *cur_sub, struct event_hdl_sub_type type);

/* unregister an existing subscription <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);

/* pause an existing subscription <sub>
 * the subscription will no longer receive events (reversible)
 * This can be reverted thanks to _resume() function
 */
void event_hdl_pause(struct event_hdl_sub *sub);

/* resume an existing subscription <sub>
 * that was previously paused using _pause() function
 */
void event_hdl_resume(struct event_hdl_sub *sub);

/* Same as event_hdl_pause() for identified subscriptions:
 * use this function to pause the subscription <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_pause(event_hdl_sub_list *sub_list,
                           uint64_t lookup_id);

/* Same as event_hdl_resume() for identified subscriptions:
 * use this function to resume the subscription <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_resume(event_hdl_sub_list *sub_list,
                            uint64_t lookup_id);

/* ------ PUBLISHING FUNCTIONS ------ */

/* this macro is provided as an internal helper to automatically populate
 * data for fixed length structs as required by event_hdl publish function
 */
#define _EVENT_HDL_CB_DATA_ASSERT(size)					\
	({								\
		/* if this fails to compile				\
		 * it means you need to fix				\
		 * EVENT_HDL_ASYNC_EVENT_DATA				\
		 * size in event_hdl-t.h				\
		 */							\
		__attribute__((unused))					\
		char __static_assert[(size <= EVENT_HDL_ASYNC_EVENT_DATA) ? 1 : -1];\
		(size);							\
	})
#define _EVENT_HDL_CB_DATA(data,size,mfree)				\
	(&(struct event_hdl_cb_data){ ._ptr = data,			\
				      ._size = size,			\
				      ._mfree = mfree })

/* Use this when 'safe' data is completely standalone */
#define EVENT_HDL_CB_DATA(data)						\
	_EVENT_HDL_CB_DATA(data,					\
			   _EVENT_HDL_CB_DATA_ASSERT(sizeof(*data)),	\
			   NULL)
/* Use this when 'safe' data points to dynamically allocated members
 * that require freeing when the event is completely consumed
 * (data in itself may be statically allocated as with
 *  EVENT_HDL_CB_DATA since the publish function will take
 *  care of copying it for async handlers)
 *
 * mfree function will be called with data as argument
 * (or copy of data in async context) when the event is completely
 * consumed (sync and async handlers included). This will give you
 * enough context to perform the required cleanup steps.
 *
 * mfree should be prototyped like this:
 *    void (*mfree)(const void *data)
 */
#define EVENT_HDL_CB_DATA_DM(data, mfree)				\
	_EVENT_HDL_CB_DATA(data,                                        \
			   _EVENT_HDL_CB_DATA_ASSERT(sizeof(*data)),    \
			   mfree)

/* event publishing function
 * this function should be called from anywhere in the code to notify
 * about an <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->head);
	queue->size = 0;
}

/* use this for advanced async mode to pop an event from event queue */
static inline struct event_hdl_async_event *event_hdl_async_equeue_pop(event_hdl_async_equeue *queue)
{
	struct event_hdl_async_event *event;

	event = MT_LIST_POP(&queue->head, struct event_hdl_async_event *, mt_list);
	if (event)
		HA_ATOMIC_DEC(&queue->size);
	return event;
}

/* use this for advanced async mode to check if the event queue is empty */
static inline int event_hdl_async_equeue_isempty(event_hdl_async_equeue *queue)
{
	return MT_LIST_ISEMPTY(&queue->head);
}

/* use this for advanced async mode to check if the event queue size */
static inline uint32_t event_hdl_async_equeue_size(event_hdl_async_equeue *queue)
{
	return HA_ATOMIC_LOAD(&queue->size);
}

/* use this to initialize <sub_list> event subscription list */
void event_hdl_sub_list_init(event_hdl_sub_list *sub_list);

/* use this function when you need to destroy <sub_list>
 * event subscription list
 * All subscriptions will be removed and properly freed according
 * to their types
 */
void event_hdl_sub_list_destroy(event_hdl_sub_list *sub_list);

/* event_hdl tunables */
extern struct event_hdl_tune event_hdl_tune;

#endif /* _HAPROXY_EVENT_HDL_H */