/* test_dynep.c — runtime hot-plug of a VM endpoint (WS1): a discovery-style consumer * attaches an adapter trio, then detaches it and re-attaches it on the SAME endpoint * while the loop is running. Proves: * - vmsig_core_add_adapter works AFTER vmsig_core_run started (from a loop-thread cb); * - vmsig_core_detach_endpoint tears the trio down (deferred reap) and bumps the epoch, * broadcasting MEMCTX_INVALIDATED so a holder settles; * - re-attaching the same endpoint publishes MEMCTX at the strictly-higher epoch. * All driven from the holder callbacks, which run on the loop thread (single-threaded * with the pumps), so attach/detach are issued mid-loop exactly as discovery will. */ #define _GNU_SOURCE #include "vmsig.h" #include #include #include static int g_fail = 0; #define CHECK(cond, msg) do { \ if (!(cond)) { printf(" FAIL: %s\n", (msg)); g_fail = 1; } \ } while (0) typedef struct { vmsig_core* core; uint32_t ep; int memctx; /* MEMCTX received */ int invalidated; /* MEMCTX_INVALIDATED received */ uint32_t last_epoch; /* epoch of the last MEMCTX */ int phase; /* 0: pre-detach, 1: detached, 2: reattached */ int ticks; /* vmhost watchdog ticks (failsafe) */ } dyn; /* Re-attach the trio (vmhost watchdog + memctx) on the same endpoint, mid-loop, from the * INVALIDATED delivery — exactly the discovery "file reappeared" path. */ static void reattach_trio(dyn* d) { vmsig_core_add_adapter(d->core, vmsig_vmhost_ops(), NULL, d->ep); vmsig_core_add_adapter(d->core, vmsig_memctx_ops(), NULL, d->ep); } static int dyn_on_ev(void* u, const vmsig_event* ev) { dyn* d = u; if (ev->kind == VMSIG_EV_VM_LIFECYCLE) d->ticks++; else if (ev->kind == VMSIG_EV_MEMCTX_INVALIDATED) { d->invalidated++; if (d->phase == 1) { d->phase = 2; reattach_trio(d); } } if (d->ticks > 60) vmsig_core_stop(d->core); /* failsafe */ return 0; } static int dyn_on_memctx(void* u, const vmsig_event* ev, int fd) { dyn* d = u; const vmsig_memctx* m = (const vmsig_memctx*)ev->inln; (void)fd; /* core closes the borrowed RO-fd after this call */ d->memctx++; d->last_epoch = m->epoch; if (d->phase == 0 && m->epoch == 0) { d->phase = 1; vmsig_core_detach_endpoint(d->core, d->ep); /* deferred reap -> bump -> INVALIDATED */ } else if (d->phase == 2 && m->epoch >= 1) { vmsig_core_stop(d->core); /* re-attached context observed: done */ } return 0; } static void test_dynep(void) { printf("test_dynep\n"); vmsig_ctx* ctx = vmsig_ctx_new(); vmsig_core* core = vmsig_core_new(ctx); dyn d; memset(&d, 0, sizeof d); d.core = core; d.ep = 0; vmsig_inproc_cfg cfg; memset(&cfg, 0, sizeof cfg); cfg.on_event = dyn_on_ev; cfg.on_memctx = dyn_on_memctx; cfg.user = &d; void* ctl = vmsig_inproc_control_new(&cfg); vmsig_grant g; memset(&g, 0, sizeof g); g.principal = 1; g.endpoint_mask = 1ull << 0; g.source_mask = 0xFFFFFFFFu; g.cap_mask = VMSIG_CAP_MEMCTX | VMSIG_CAP_OBSERVE; vmsig_core_add_control(core, vmsig_inproc_control_ops(), ctl, &g); /* initial trio on ep0, pre-run (vmhost watchdog ticks the loop + memctx publishes). */ CHECK(vmsig_core_add_adapter(core, vmsig_vmhost_ops(), NULL, 0) >= 0, "add vmhost ep0"); CHECK(vmsig_core_add_adapter(core, vmsig_memctx_ops(), NULL, 0) >= 0, "add memctx ep0"); vmsig_core_run(core); CHECK(d.memctx >= 2, "MEMCTX received before AND after re-attach"); CHECK(d.invalidated >= 1, "MEMCTX_INVALIDATED delivered on detach"); CHECK(d.last_epoch >= 1, "epoch advanced across detach/re-attach"); CHECK(d.phase == 2, "reached the re-attached phase"); vmsig_core_free(core); vmsig_ctx_free(ctx); } int main(void) { test_dynep(); printf("dynep tests: %s\n", g_fail ? "FAIL" : "PASS"); return g_fail ? 1 : 0; }