myxce/src/lib/hook.c
Rongsong Shen f155b1ef97 feat: xce_disable_hook() + _Atomic hook_fn_t + NULL-slot reuse (P1-2a)
- hook_item_t.fn changed to _Atomic hook_fn_t (C11 stdatomic)
- xce_disable_hook(): atomically stores NULL into the slot fn field;
  returns 0 on success, -ENOENT if fn not found, -EINVAL on bad args
- xce_add_hook(): scans for NULL (disabled) slots before allocating;
  uses atomic_store_explicit(release) when writing fn pointer
- xce_run_hooks(): atomic_load_explicit(acquire) + NULL guard so
  disabled slots are silently skipped without disrupting the loop
- Add src/lib/testsuite/test_hooks.c with 3 TDD tests:
  test_disable_hook_stops_invocation
  test_disable_hook_returns_enoent_if_not_found
  test_add_hook_reuses_null_slot
- Register test_hooks in meson.build and test.c runner

Required by ha_demote_to_standby() to atomically disable persistence
hooks when the controller steps down from primary, preventing unbounded
hook list growth across promote/demote cycles (resolution A6-015,
A7-004, A7-016).
2026-05-07 13:55:42 +00:00

173 lines
3.6 KiB
C

#include <errno.h>
#include <stdatomic.h>
#include <stdlib.h>
#include <string.h>
#include "hook.h"
#include "utils.h"
int xce_hook_init(hooksys_t* s, int maxtypes)
{
int i;
if ((s == NULL) || (maxtypes <= 0)) {
return -EINVAL;
}
s->maxtypes = 0;
s->hooks = (xlist_t*)calloc(maxtypes, sizeof(xlist_t));
if (s->hooks == NULL) {
return -ENOMEM;
}
s->maxtypes = maxtypes;
for (i = 0; i < s->maxtypes; i++) {
xlist_init(&(s->hooks[i]), 8);
}
return 0;
}
int xce_add_hook(hooksys_t* s, int typ, hook_fn_t fn, void* data)
{
hook_item_t* it;
if (s == NULL) {
return -EINVAL;
}
if (fn == NULL) {
return -EINVAL;
}
if ((typ < 0) || (typ >= s->maxtypes)) {
return -EINVAL;
}
/* Scan for a disabled (NULL-fn) slot of the same type to reuse */
{
xlist_t* l = &s->hooks[typ];
int i;
for (i = 0; i < l->count; i++) {
hook_item_t* slot = (hook_item_t*)l->data[i];
if (!slot) continue;
hook_fn_t cur =
atomic_load_explicit(&slot->fn, memory_order_acquire);
if (cur == NULL) {
slot->data = data;
atomic_store_explicit(&slot->fn, fn, memory_order_release);
return 0;
}
}
}
/* No reusable slot — allocate new */
it = (hook_item_t*)calloc(1, sizeof(*it));
if (it == NULL) {
return -ENOMEM;
}
it->data = data;
atomic_store_explicit(&it->fn, fn, memory_order_release);
xlist_store(&(s->hooks[typ]), it);
return 0;
}
int xce_disable_hook(hooksys_t* s, int typ, hook_fn_t fn)
{
xlist_t* l;
int i;
if (!s || !fn || typ < 0 || typ >= s->maxtypes) {
return -EINVAL;
}
l = &s->hooks[typ];
for (i = 0; i < l->count; i++) {
hook_item_t* it = (hook_item_t*)l->data[i];
if (!it) continue;
hook_fn_t cur = atomic_load_explicit(&it->fn, memory_order_acquire);
if (cur == fn) {
atomic_store_explicit(&it->fn, (hook_fn_t)NULL,
memory_order_release);
return 0;
}
}
return -ENOENT;
}
/* HOOK_BEFORE_EVENT returns
* negative value will cause
* fail
*/
int xce_run_hooks(hooksys_t* s, int type, int situation, int detail, ...)
{
xlist_t* l;
int i;
int rc = 0;
if (s == NULL) {
return -EINVAL;
}
if ((type < 0) || (type >= s->maxtypes)) {
return -EINVAL;
}
l = &(s->hooks[type]);
for (i = 0; i < l->count; i++) {
hook_item_t* it = l->data[i];
hook_fn_t local_fn;
va_list ap;
if (it == NULL) {
continue;
}
local_fn = atomic_load_explicit(&it->fn, memory_order_acquire);
if (!local_fn) {
continue; /* disabled slot */
}
va_start(ap, detail);
rc = local_fn(type, situation, detail, it->data, ap);
va_end(ap);
if ((situation == HOOK_BEFORE_EVENT) && (rc < 0)) {
break;
}
}
return rc;
}
static int free_hook_item(void* data1, void* data2)
{
if (data1 == NULL) {
return -EINVAL;
}
free(data1);
return 0;
}
void xce_hook_fini(hooksys_t* s)
{
int i;
if (s == NULL) {
return;
}
if (s->hooks == NULL) {
return;
}
for (i = 0; i < s->maxtypes; i++) {
xlist_iter(&(s->hooks[i]), (xlist_iter_fn_t)free_hook_item, NULL);
xlist_fini(&(s->hooks[i]));
}
FREEUP(s->hooks);
}