blob: 4414bc495f4d375b114be42fc5b6f53c41a82de2 [file] [log] [blame]
Aurelien DARRAGONd06b9c82022-11-16 18:06:34 +01001 -----------------------------------------
2 event_hdl Guide - version 1.0
3 ( Last update: 2022-11-14 )
4 ------------------------------------------
5
6ABSTRACT
7--------
8
9The event_hdl support is a new feature of HAProxy 2.7. It is a way to easily
10handle general events in a simple to maintain fashion, while keeping core code
11impact to the bare minimum.
12
13This document first describes how to use already supported events,
14then how to add support for your very own events.
15
16This feature is quite new for now. The API is not frozen and will be
17updated/modified/improved/extended as needed.
18
19SUMMARY
20-------
21
22 1. event_hdl introduction
23 2. How to handle existing events
24 2.1 SYNC mode
25 2.2 ASYNC mode
26 2.2.1 normal version
27 2.2.2 task version
28 2.3 Advanced features
29 2.3.1 sub_mgmt
30 2.3.2 subscription external lookups
31 2.3.3 subscription ptr
32 2.3.4 private_free
33 3. How to add support for new events
34 3.1 Declaring a new event data structure
35 3.2 Publishing an event
36 4. Subscription lists
37 5. misc/helper functions
38
39
401. EVENT_HDL INTRODUCTION
41-----------------------
42
43EVENT_HDL provides two complementary APIs, both are implemented
44in src/event_hdl.c and include/haproxy/event_hdl(-t).h:
45
46One API targeting developers that want to register event
47handlers that will be notified when specific events occur in the process.
48(See section 2.)
49
50One API targeting developers that want to notify registered handlers about
51an event that is happening in the process.
52(See section 3.)
53
542. HOW TO HANDLE EXISTING EVENTS
55---------------------
56
57To handle existing events, you must first decide which events you're
58interested in.
59
60event types are defined as follow:
61
62```
63 /* type for storing event subscription type */
64 typedef struct event_hdl_sub_type
65 {
66 /* up to 256 families, non cumulative, adjust if needed */
67 uint8_t family;
68 /* up to 16 sub types using bitmasks, adjust if needed */
69 uint16_t subtype;
70 } event_hdl_sub_type;
71```
72
73For an up to date list of already supported events,
74please refer to include/haproxy/event_hdl-t.h
75At the end of the file you will find existing event types.
76
77Each event family provides an unique data structure that will
78be provided to the event handler (registered to one or more
79event subtypes) when such events occur.
80
81An event handler can subscribe to a single event family type at a time, but
82within the family type it can subscribe to multiple event subtypes.
83
84 For example, let's consider the SERVER family type.
85
86 Let's assume it provides the event_hdl_cb_data_server data structure.
87
88 We can register a handler that will be notified for
89 every SERVER event types using:
90 EVENT_HDL_SUB_SERVER
91
92 This will include EVENT_HDL_SUB_SERVER_ADD,
93 EVENT_HDL_SUB_SERVER_DEL [...]
94
95 But we can also subscribe to a specific subtype only,
96 for example server deletion:
97 EVENT_HDL_SUB_SERVER_DEL
98
99 You can even combine multiple SERVER subtypes using
100 event_hdl_sub_type_add function helper:
101 event_hdl_sub_type_add(EVENT_HDL_SUB_SERVER_DEL,
102 EVENT_HDL_SUB_SERVER_ADD)
103
104 (will refer to server deletion as well as server addition)
105
106Registering a handler comes into multiple flavors:
107
108 SYNC mode:
109 handler is called in a blocking manner directly from the
110 thread that publishes the event.
111 This mode should be used with precaution because it could
112 slow the caller or cause deadlocks if used improperly.
113
114 Sync mode is useful when you directly depend on data or
115 state consistency from the caller.
116
117 Sync mode gives you access to unsafe elements in the data structure
118 provided by the caller (again, see event_hdl-t.h for more details).
119 The data structure may provide lock hints in the unsafe section
120 so that you know which locks are already held within the
121 calling context, hopefully preventing you from relocking
122 an already locked element and preventing deadlocks.
123
124 ASYNC mode:
125 handler is called in a non-blocking manner
126 (in a dedicated tasklet),
127 thus, the caller (that published the event) is not affected
128 by the handler. (time wise and data wise)
129
130 This is the safest way to handle events,
131 but it also comes with a limitation:
132
133 unsafe elements in the data structure provided by
134 the caller SHOULD be used under NO circumstances.
135 Indeed, only safe elements are meant to be used
136 when handling the event in async mode.
137
138 ASYNC mode is declined in 2 different versions:
139 normal:
140 handler is simply a function pointer
141 (same prototype as sync mode),
142 that is called asynchronously with relevant data
143 when the event is published. Only difference with
144 sync mode here is that 'unsafe' data provided
145 by the data structure may not be used.
146 task:
147 handler is a user defined task that uses an event queue
148 to consume pending events.
149 This mode is interesting when you need to perform
150 advanced operations or you need to handle the event
151 in an already existing task context.
152 It is a bit more complicated to setup, but really
153 nothing to worry about, some examples will be
154 provided later in this document.
155
156event subscription is performed using the function:
157
158 event_hdl_subscribe(list, event, hdl);
159
160 The function returns 1 in case of success,
161 and 0 in case of failure (bad arguments, or memory error)
162
163 The function may BUG_ON if used improperly (invalid arguments)
164
165 <list> is either user specified list used to store the
166 new subscription, or NULL if you want to store the subscription
167 in the process global list.
168
169 <list> is also asked when publishing an event,
170 so specifying list could be useful, if, for example,
171 you only want to subscribe to a specific subscription list
172 (see this as a scope for example, NULL being full scope,
173 and specific list being limited scope)
174
175 We will use server events as an example:
176
177 You could register to events for ALL servers by using the
178 global list (NULL), or only to a specific server events
179 by using the subscription list dedicated to a single server.
180
181 <event> are the events (family.subtypes) you're subscribing to
182
183 <hdl> contains required handler options, it must be provided using
184 EVENT_HDL_(TASK_)(A)SYNC() and EVENT_HDL_ID_(TASK_)(A)SYNC()
185 helper macros.
186
187 See include/haproxy/event_hdl.h or below to know which macro
188 best suits your needs.
189
190 When registering a handler, you have the ability to provide an
191 unique ID (using EVENT_HDL_ID_ macro family) that could be used
192 later to perform lookups on the subscription.
193 ID is stored as an uint64_t hash that is expected to be computed using
194 general purpose event_hdl_id inline function provided by event_hdl.h.
195 Not providing an ID (using EVENT_HDL_ macro family)
196 results in the subscription being considered as anonymous.
197 As the name implies, anonymous subscriptions don't support lookups.
198
1992.1 SYNC MODE
200---------------------
201
202Example, you want to register a sync handler that will be called when
203a new server is added.
204
205Here is what the handler function will look like:
206```
207void my_sync_handler(const struct event_hdl_cb *cb, void *private)
208{
209 const struct event_hdl_cb_data_server *server = cb->e_data;
210
211 /* using EVENT_HDL_ASSERT_SYNC is a good practice to ensure
212 * that the function breaks if used in async mode
213 * (because we will access unsafe data in this function that
214 * is sync mode only)
215 */
216 EVENT_HDL_ASSERT_SYNC(cb);
217 printf("I've been called for '%s', private = %p\n",
218 event_hdl_sub_type_to_string(cb->e_type), private);
219 printf("server name is '%s'\n", server->safe.name);
220
221 /* here it is safe to use unsafe data */
222 printf("server ptr is '%p'\n", server->unsafe.ptr);
223
224 /* from here you have the possibility to manage the subscription
225 * cb->sub_mgmt->unsub(cb->sub_mgmt);
226 * // hdl will be removed from the subscription list
227 */
228}
229```
230
231Here is how you perform the subscription:
232
233anonymous subscription:
234```
235 int private = 10;
236
237 event_hdl_subscribe(NULL, EVENT_HDL_SUB_SERVER_ADD,
238 EVENT_HDL_SYNC(my_sync_handler, &private, NULL));
239```
240
241identified subscription:
242```
243 int private = 10;
244 uint64_t id = event_hdl_id("test", "sync");
245
246 event_hdl_subscribe(NULL, EVENT_HDL_SUB_SERVER_ADD,
247 EVENT_HDL_ID_SYNC(id,
248 my_sync_handler,
249 &private,
250 NULL));
251
252```
253
254identified subscription where freeing private is required when subscription ends:
255(also works for anonymous)
256(more on this feature in 2.3.4)
257```
258 int *private = malloc(sizeof(*private));
259 uint64_t id = event_hdl_id("test", "sync_free");
260
261 BUG_ON(!private);
262 *private = 10;
263
264 /* passing free as 'private_free' function so that
265 * private can be freed when unregistering is performed
266 */
267 event_hdl_subscribe(NULL, EVENT_HDL_SUB_SERVER_ADD,
268 EVENT_HDL_ID_SYNC(id,
269 my_sync_handler,
270 private,
271 free));
272
273
274 /* ... */
275
276 // unregistering the identified hdl
277 if (event_hdl_lookup_unsubscribe(NULL, id)) {
278 printf("private will automatically be freed!\n");
279 }
280```
281
2822.2 ASYNC MODE
283---------------------
284
285As mentioned before, async mode comes in 2 flavors, normal and task.
286
2872.2.1 NORMAL VERSION
288---------------------
289
290Normal is meant to be really easy to use, and highly compatible with sync mode.
291
292(Handler can easily be converted or copy pasted from async to sync mode
293and vice versa)
294
295Quick warning about sync to async handler conversion:
296
297please always use EVENT_HDL_ASSERT_SYNC whenever you develop a
298sync handler that performs unsafe data access.
299
300This way, if the handler were to be converted or copy pasted as is to
301async mode without removing unsafe data accesses,
302the handler will forcefully fail to indicate an error so that you
303know something has to be fixed in your handler code.
304
305Back to our async handler, let's say you want to declare an
306async handler that will be called when a new server is added.
307
308Here is what the handler function will look like:
309```
310void my_async_handler(const struct event_hdl_cb *cb, void *private)
311{
312 const struct event_hdl_cb_data_server *server = cb->e_data;
313
314 printf("I've been called for '%s', private = %p\n",
315 event_hdl_sub_type_to_string(cb->e_type), private);
316 printf("server name is '%s'\n", server->safe.name);
317
318 /* here it is not safe to use unsafe data */
319
320 /* from here you have the possibility to manage the subscription
321 * cb->sub_mgmt->unsub(cb->sub_mgmt);
322 * // hdl will be removed from the subscription list
323 */
324}
325```
326
327Note that it is pretty similar to sync handler, except
328for unsafe data access.
329
330Here is how you declare the subscription:
331
332anonymous subscription:
333```
334 int private = 10;
335
336 event_hdl_subscribe(NULL, EVENT_HDL_SUB_SERVER_ADD,
337 EVENT_HDL_ASYNC(my_async_handler, &private, NULL));
338```
339
340identified subscription:
341```
342 int private = 10;
343 uint64_t id = event_hdl_id("test", "async");
344
345 event_hdl_subscribe(NULL, EVENT_HDL_SUB_SERVER_ADD,
346 EVENT_HDL_ID_ASYNC(id,
347 my_async_handler,
348 &private,
349 NULL));
350
351```
352
353identified subscription where freeing private is required when subscription ends:
354(also works for anonymous)
355```
356 int *private = malloc(sizeof(*private));
357 uint64_t id = event_hdl_id("test", "async_free");
358
359 BUG_ON(!private);
360 *private = 10;
361
362 /* passing free as 'private_free' function so that
363 * private can be freed when unregistering is performed
364 */
365 event_hdl_subscribe(NULL, EVENT_HDL_SUB_SERVER_ADD,
366 EVENT_HDL_ID_ASYNC(id,
367 my_async_handler,
368 private,
369 free));
370
371 /* ... */
372
373 // unregistering the identified hdl
374 if (event_hdl_lookup_unsubscribe(NULL, id)) {
375 printf("private will automatically be freed when "
376 "all pending events referencing private "
377 "are consumed!\n");
378 }
379```
380
3812.2.2 TASK VERSION
382---------------------
383
384task version requires a bit more setup, but it's pretty
385straightforward actually.
386
387
388First, you need to initialize an event queue that will be used
389by event_hdl facility to push you events according to your subscription:
390
391```
392 event_hdl_async_equeue my_q;
393
394 event_hdl_async_equeue_init(&my_q);
395```
396
397
398Then, you need to declare a tasklet (or reuse existing tasklet)
399
400It is your responsibility to make sure that the tasklet still exists
401(is not freed) when calling the subscribe function
402(and that the task remains valid as long as the subscription is).
403
404When a subscription referencing your task is over
405(either ended because of list purge, external code or from the handler itself),
406you will receive the EVENT_HDL_SUB_END event.
407When you receive this event, you must free it as usual and you can safely
408assume that the related subscription won't be sending you any more events.
409
410Here is what your task will look like (involving a single event queue):
411
412```
413struct task *event_hdl_async_task_my(struct task *task,
414 void *ctx, unsigned int state)
415{
416 struct tasklet *tl = (struct tasklet *)task;
417 event_hdl_async_equeue *queue = ctx;
418 struct event_hdl_async_event *event;
419 struct event_hdl_cb_data_server *srv;
420 uint8_t done = 0;
421
422 while ((event = event_hdl_async_equeue_pop(queue)))
423 {
424 if (event_hdl_sub_type_equal(event->type, EVENT_HDL_SUB_END)) {
425 done = 1;
426 event_hdl_async_free_event(event);
427 printf("no more events to come, "
428 "subscription is over\n");
429 break;
430 }
431
432 srv = event->data;
433
434 printf("task event %s, %d (name = %s)\n",
435 event_hdl_sub_type_to_string(event->type),
436 *((int *)event->private), srv->safe.name);
437 event_hdl_async_free_event(event);
438 }
439
440 if (done) {
441 /* our job is done, subscription is over:
442 * no more events to come
443 */
444 tasklet_free(tl);
445 return NULL;
446 }
447 return task;
448}
449
450```
451
452Here is how we would initialize the task event_hdl_async_task_my:
453```
454 struct tasklet *my_task;
455
456 my_task = tasklet_new();
457 BUG_ON(!my_task);
458 my_task->context = &my_q; // we declared my_q previously in this example
459 /* we declared event_hdl_async_task_my previously
460 * in this example
461 */
462 my_task->process = event_hdl_async_task_my;
463
464```
465
466Given our task and our previously initialized event queue, here is how
467to perform the subscription:
468```
469 int test_val = 11;
470 uint64_t id = event_hdl_id("test", "my_task");
471
472 /* anonymous variant */
473 event_hdl_subscribe(NULL, EVENT_HDL_SUB_SERVER_ADD,
474 EVENT_HDL_ASYNC_TASK(&my_q,
475 my_task,
476 &test_val,
477 NULL));
478 /* identified variant */
479 event_hdl_subscribe(NULL, EVENT_HDL_SUB_SERVER_ADD,
480 EVENT_HDL_ID_ASYNC_TASK(id,
481 &my_q,
482 my_task,
483 &test_val,
484 NULL));
485```
486
487Note: it is not recommended to perform multiple subscriptions
488 that share the same event queue or same tasklet (or both)
489
490 That is, having more than one subscription waking a tasklet
491 and/or feeding the same event queue.
492
493 No check is performed on this when registering, so the API
494 won't prevent you from doing it.
495
496 If you are going to do this anyway despite this warning:
497
498 In the case you need to stop the task prematurely
499 (if this is not going to happen please skip this paragraph):
500 You are responsible for acknowledging the end of every
501 active subscriptions that refer to your task or
502 your event queue(s).
503 And you really don't want a subscription associated with
504 your task or event queue to keep going when the task
505 is not active anymore because:
506 1: there will be memory leak
507 (event queue might continue to receive new events)
508 2: there is a 100% chance of process crash in case of event
509 because we will try to wake a task (your task)
510 that might already be freed. Thus UAF will occur.
511
5122.3 ADVANCED FEATURES
513-----------------------
514
515We've already covered some of these features in the previous examples.
516Here is a documented recap.
517
518
5192.3.1 SUB MGMT
520-----------------------
521
522From an event handler context, either sync or async mode:
523 You have the ability to directly manage the subscription
524 that provided the event.
525
526As of today, these actions are supported:
527 - Consulting the subscription.
528 - Modifying the subscription (resubscribing within same family)
529 - Unregistering the subscription (unsubscribing).
530
531To do this, consider the following structure:
532```
533 struct event_hdl_sub_mgmt
534 {
535 /* manage subscriptions from event
536 * this must not be used directly because
537 * locking might be required
538 */
539 struct event_hdl_sub *this;
540 /* safe functions than can be used from
541 * event context (sync and async mode)
542 */
543 struct event_hdl_sub_type (*getsub)(const struct event_hdl_sub_mgmt *);
544 int (*resub)(const struct event_hdl_sub_mgmt *, struct event_hdl_sub_type);
545 void (*unsub)(const struct event_hdl_sub_mgmt *);
546 };
547
548```
549A reference to this structure is provided in every handler mode.
550
551Sync mode and normal async mode (directly from the callback data pointer):
552```
553 const struct event_hdl_cb *cb;
554 // cb->sub_mgmt
555 // cb->sub_mgmt->getsub(cb->sub_mgmt);
556 // cb->sub_mgmt->unsub(cb->sub_mgmt);
557```
558
559task and notify async modes (from the event):
560```
561 struct event_hdl_async_event *event;
562 // event->sub_mgmt
563 // event->sub_mgmt.getsub(&event->sub_mgmt);
564 // event->sub_mgmt.unsub(&event->sub_mgmt);
565```
566
5672.3.2 SUBSCRIPTION EXTERNAL LOOKUPS
568-----------------------
569
570As you've seen in 2.3.1, managing the subscription directly
571from the handler is a possibility.
572
573But for identified subscriptions, you also have the ability to
574perform lookups and management operations on specific subscriptions
575within a list based on their ID, anywhere in the code.
576
577/!\ This feature is not available for anonymous subscriptions /!\
578
579Here are the actions already supported:
580
581 - unregistering a subscription (unsubscribing)
582 - updating a subscription (resubscribing within same family)
583 - getting a ptr/reference to the subscription
584
585Those functions are documented in event_hdl.h
586(search for EVENT_HDL_LOOKUP section).
587
588To select a specific subscription, you must provide
589the unique identifier (uint64_t hash) that was provided when subscribing.
590(using event_hdl_id(scope, name) function)
591
592Notes:
593 "id" is only unique within a given subscription list.
594
595 When using event_hdl_id to provide the id:
596 It is your responsibility to make sure that you "own"
597 the scope if you rely on name to be "free".
598
599 As ID computation is backed by xxhash hash API,
600 you should be aware that hash collisions could occur,
601 but are extremely rare and are thus considered safe
602 enough for this usage.
603 (see event_hdl.h for implementation details)
604
605 Please consider ptr based subscription management if
606 these limitations don't fit your requirements.
607
608Here are some examples:
609
610unsubscribing:
611```
612 /* registering "scope":"name" subscription */
613 event_hdl_subscribe(NULL, EVENT_HDL_SUB_SERVER_ADD,
614 EVENT_HDL_ID_SYNC(event_hdl_id("scope", "name"),
615 my_sync_handler,
616 NULL,
617 NULL));
618 /* unregistering "scope":"name" subscription */
619 event_hdl_lookup_unsubscribe(NULL, event_hdl_id("scope", "name"));
620```
621
6222.3.3 SUBSCRIPTION PTR
623-----------------------
624
625To manage existing subscriptions from external code,
626we already talked about identified subscriptions that
627allow lookups within list.
628
629But there is another way to accomplish this.
630
631When subscribing, you can use the event_hdl_subscribe_ptr() function
632variant (same arguments as event_hdl_subscribe()).
633
634What this function does, is instead of returning 1 in case of
635success and 0 in case of failure: it returns a valid subscription ptr
636for success and NULL for failure.
637
638Returned ptr is guaranteed to remain valid even if subscription
639is ended meanwhile because the ptr is internally guarded with a refcount.
640
641Thus, as long as you don't explicitly unregister the subscription with
642event_hdl_unsubscribe() or drop the reference using event_hdl_drop(),
643subscription ptr won't be freed.
644
645This ptr will allow you to use the following subscription
646management functions from external code:
647
648 - event_hdl_take() to increment subscription ptr refcount
649 (automatically incremented when using event_hdl_subscribe_ptr)
650 - event_hdl_drop() to decrement subscription ptr refcount
651 - event_hdl_resubscribe() to modify subscription subtype
652 - event_hdl_unsubscribe() to end the subscription
653 (refcount will be automatically decremented)
654
655Here is an example:
656```
657 struct event_hdl_sub *sub_ptr;
658
659 /* registering a subscription with subscribe_ptr */
660 sub_ptr = event_hdl_subscribe_ptr(NULL, EVENT_HDL_SUB_SERVER_ADD,
661 EVENT_HDL_SYNC(my_sync_handler,
662 NULL,
663 NULL));
664
665 /* ... */
666
667 /* unregistering the subscription */
668 event_hdl_unsubscribe(sub_ptr);
669```
670
671Regarding identified subscriptions that were registered using the non ptr
672subscribe function:
673
674You still have the ability to get a reference to the related subscription
675(if it still exists), by using event_hdl_lookup_take(list, id) function.
676event_hdl_lookup_take will return a subscription ptr in case of success
677and NULL in case of failure.
678Returned ptr reference is automatically incremented, so it is safe to use.
679
680Please don't forget to drop the reference
681when holding the ptr is no longer needed.
682
683Example:
684```
685 struct event_hdl_sub *sub_ptr = NULL;
686
687 /* registering subscription id "test":"ptr" with normal subscribe */
688 if (event_hdl_subscribe(NULL, EVENT_HDL_SUB_SERVER_ADD,
689 EVENT_HDL_ID_SYNC(event_hdl_id("test", "ptr"),
690 my_sync_handler,
691 NULL,
692 NULL))) {
693 /* fetch ref to subscription "test":"ptr" */
694 sub_ptr = event_hdl_lookup_take(NULL,
695 event_hdl_id("test", "ptr"));
696
697 /* unregister the subscription using lookup */
698 event_hdl_lookup_unsubscribe(NULL,
699 event_hdl_id("test", "ptr"));
700 }
701
702 /* ... */
703
704 /* unregistering the subscription with ptr
705 * will do nothing because subscription was
706 * already ended by lookup_unsubscribe, but
707 * here the catch is that sub_ptr is still
708 * valid so this won't crash the program
709 */
710 if (sub_ptr) {
711 event_hdl_unsubscribe(sub_ptr);
712 /* unsubscribe will also result in subscription
713 * reference drop, thus subscription will be freed here
714 * because sub_ptr was the last active reference.
715 * You must not use sub_ptr anymore past this point
716 * or UAF could occur
717 */
718 }
719
720```
721
7222.3.4 PRIVATE FREE
723-----------------------
724
725Upon handler subscription, you have the ability to provide
726a private data pointer that will be passed to the handler
727when subscribed events occur.
728
729Sometimes this private data pointer will rely on dynamically allocated memory.
730And in such cases, you have no way of knowing when
731freeing this pointer can be done safely.
732
733You could be tempted to think that freeing right after performing
734the unsubscription could be safe.
735But this is not the case, remember we could be dealing with async handlers
736that might still consume pending events even though unsubscription
737has been performed from external code.
738
739To deal with this, you may want to provide the private_free
740function pointer upon subscription.
741This way, private_free function will automatically be called
742(with private as argument) when private is no longer be used.
743
744Example:
745First we declare our private free function:
746```
747void my_private_free(void *my_private_data) {
748 /* here we only call free,
749 * but you could do more sophisticated stuff
750 */
751 free(my_private_data);
752}
753```
754Then:
755```
756 char *my_private_data = strdup("this string needs to be freed");
757
758 BUG_ON(!my_private_data);
759
760 event_hdl_subscribe(NULL, EVENT_HDL_SUB_SERVER_DEL,
761 EVENT_HDL_ID_ASYNC(event_hdl_id("test", "private"),
762 my_async_handler,
763 my_private_data,
764 my_private_free));
765
766 /* freeing my_private_data is not required anymore,
767 * it will be automatically freed by our private free
768 * function when subscription ends
769 */
770
771 /* unregistering "test":"private" subscription */
772 event_hdl_lookup_unsubscribe(NULL, event_hdl_id("test", "private"));
773
774 /* my_private_free will be automatically summoned when my_private_data
775 * is not referenced anymore
776 */
777```
778
7793 HOW TO ADD SUPPORT FOR NEW EVENTS
780-----------------------
781
782Adding support for a new event is pretty straightforward.
783
784First, you need to declare a new event subtype in event_hdl-t.h file
785(bottom of the file).
786
787You might want to declare a whole new event family, in which case
788you declare both the new family and the associated subtypes (if any).
789
790```
791 #define EVENT_HDL_SUB_NEW_FAMILY EVENT_HDL_SUB_FAMILY(4)
792 #define EVENT_HDL_SUB_NEW_FAMILY_SUBTYPE_1 EVENT_HDL_SUB_TYPE(4,0)
793```
794
795Then, you need to update the event_hdl_sub_type_map map,
796defined in src/event_hdl.c file (top of the file)
797to add string to event type and event type to string conversion support.
798You just need to add the missing entries corresponding to
799the event family / subtypes you've defined.
800
801Please follow this procedure:
802 You only added a new subtype to existing family: go to section 3.2
803 You added a new family: go to section 3.1
804
8053.1 DECLARING A NEW EVENT DATA STRUCTURE
806-----------------------
807
808You have the ability to provide additional data for a given
809event family when such events occur.
810
811Note that it is not mandatory: you could simply declare a new event family
812that does not provide any data.
813If this is your case, you can skip this section and go to 3.2 section.
814
815Now, take a look at this event data structure template
816(also defined at the top of event_hdl-t.h file):
817```
818 /* event data struct are defined as followed */
819 struct event_hdl_cb_data_template {
820 struct {
821 /* safe data can be safely used from both
822 * sync and async functions
823 * data consistency is guaranteed
824 */
825 } safe;
826 struct {
827 /* unsafe data may only be used from sync functions:
828 * in async mode, data consistency cannot be guaranteed
829 * and unsafe data may already be stale, thus using
830 * it is highly discouraged because it
831 * could lead to undefined behavior
832 * (UAF, null dereference...)
833 */
834 } unsafe;
835 };
836```
837
838This structure template allows you to easily create a new event
839data structure that can be provided with your new event family.
840
841You should name it after 'struct event_hdl_cb_data_new_family' so that it is
842easy to guess the event family it relates to.
843
844Indeed, each event data structure is to be associated with an
845unique event family type.
846For each subtypes within a family type, the associated data structure
847should be provided when publishing the event.
848
849The event data struct declaration should not be performed
850directly under event_hdl-t.h file:
851
852 It should be done in the header files of the corresponding
853 facility that will publish/provide this event.
854
855 Example: struct event_hdl_cb_data_server, provided for the
856 EVENT_HDL_SUB_SERVER event family, is going to be declared in
857 include/haproxy/server-t.h file.
858
859 However, in event_hdl-t.h, where you declare event family/subtypes,
860 you should add comments or links to the file containing the relevant
861 data struct declaration. This way we make sure all events related
862 information is centralized in event_hdl-t.h while keeping it clean
863 and not depending on any additional includes (you are free to
864 depend on specific data types within your custom event data structure).
865
866Please make sure that EVENT_HDL_ASYNC_EVENT_DATA (defined in event_hdl-t.h)
867is greater than sizeof(event_hdl_cb_data_new_family).
868
869It is required for async handlers to properly consume event data.
870
871You are free to adjust EVENT_HDL_ASYNC_EVENT_DATA size if needed.
872
873If EVENT_HDL_ASYNC_EVENT_DATA is not big enough to store your new
874event family struct, a compilation assert triggered by EVENT_HDL_CB_DATA
875will occur. In addition to this, an extra runtime BUG_ON will make
876sure the condition is met when publishing the event.
Ilya Shipitsin5fa29b82022-12-07 09:46:19 +0500877The goal here is to force haproxy to fail explicitly so you know that
Aurelien DARRAGONd06b9c82022-11-16 18:06:34 +0100878something must be done on your side.
879
8803.1 PUBLISHING AN EVENT
881-----------------------
882
883Publishing an event is really simple.
884It relies on the event_hdl_publish function.
885
886The function is defined as follow:
887```
888 int event_hdl_publish(event_hdl_sub_list *sub_list,
889 event_hdl_sub_type e_type,
890 const struct event_hdl_cb_data *data);
891```
892
893We will ignore sub_list argument for now.
894In the examples below, we will use sub_list = NULL.
895Go to section 4 for a full picture about this feature.
896
897<e_type>: the event type that should be published.
898 All subscriptions referring to this event within
899 a subscription list context will be notified about the event.
900<data>: data provided for the event family of <e_type>
901 If <e_type>.family does not provide additional data,
902 data should be set to NULL.
903 If <e_type>.family does provide additional data, data should be set
904 using EVENT_HDL_CB_DATA macro.
905 (see the example below)
906
907The function returns 1 in case of SUCCESS (handlers successfully notified)
908and 0 in case of FAILURE (no handlers notified, because of memory error).
909
910Event publishing can be performed from anywhere in the code.
911(this example does not compile)
912```
913 struct event_hdl_cb_data_new_family event_data;
914
915 /* first we need to prepare event data
916 * that will be provided to event handlers
917 */
918
919 /* safe data, available from both sync and async contexts */
920 event_data.safe.my_custom_data = x;
921
922 /* unsafe data, only available from sync contexts */
923 event_data.unsafe.my_unsafe_data = y;
924
925 /* once data is prepared, we can publish the event */
926 event_hdl_publish(NULL,
927 EVENT_HDL_SUB_NEW_FAMILY_SUBTYPE_1,
928 EVENT_HDL_CB_DATA(&event_data));
929
930 /* EVENT_HDL_SUB_NEW_FAMILY_SUBTYPE_1 event was
931 * successfully published in global subscription list
932 */
933```
934
935--------------------------------------------------------------------------------
936|You should know that there is currently a limitation about publish function: |
937|The function should not be used from critical places |
938|(where the calling frequency is high |
939|or where timing sensitivity is high). |
940| |
941|Because in current implementation, subscription list lookups are not |
942|optimized for such uses cases. |
943--------------------------------------------------------------------------------
944
9454 SUBSCRIPTION LISTS
946-----------------------
947
948As you may already know, EVENT_HDL API main functions rely on
949subscription lists.
950Providing NULL where subscription list argument is required
951allows to use the implicit global subscription list.
952
953But you can also provide a specific subscription list, example:
954 subscription list associated with a single entity so that you only
955 subscribe to events of this single entity
956
957A subscription list is of type event_hdl_sub_list.
958It is defined in event_hdl-t.h
959
960To make use of this feature, you should know about these 2 functions:
961
962event_hdl_sub_list_init(list): use this fcn to initialize
963 a new subscription list.
964
965Example:
966```
967 event_hdl_sub_list my_custom_list;
968
969 event_hdl_sub_list_init(&my_custom_list);
970```
971
972event_hdl_sub_list_destroy(list): use this fcn to destroy
973 an existing subscription list.
974
975Example:
976```
977 event_hdl_sub_list_init(&my_custom_list);
978```
979
980 Using this function will cause all the existing subscriptions
981 within the provided sub_list to be properly unregistered
982 and deleted according to their types.
983
984Now we'll take another quick look at event_hdl_publish() function:
985
986Remember that the function is defined as follow:
987```
988 int event_hdl_publish(event_hdl_sub_list *sub_list,
989 event_hdl_sub_type e_type,
990 const struct event_hdl_cb_data *data);
991```
992
993In the previous examples, we used sub_list = NULL.
994
995if sub_list is NULL:
996 event will be published in in global list
997else
998 event will be published in user specified sub_list
999
10005 MISC/HELPER FUNCTIONS
1001-----------------------
1002
1003Don't forget to take a look at MISC/HELPER FUNCTIONS in
1004include/haproxy/event_hdl.h (end of the file) for a
1005complete list of helper functions / macros.
1006
1007We've already used some, if not the vast majority
1008in the examples shown in this document.
1009
1010This includes, to name a few:
1011 - event types manipulation
1012 - event types comparison
1013 - lookup id computing
1014 - subscriber list management (covered in section 4)
1015 - sync/async handler helpers