myxce/src/include/vtaskmgr.h
Rongsong Shen e17a6a613c feat(raft-drive-async): peer-control vtask dispatch shim, dormant (task 3.1)
Lands the PeerCtrlDispatch machinery behind a DEFAULT-OFF flag
(g_drive_async_peerctrl_dispatch_enabled); production behavior is
UNCHANGED. peer_ctrl_msg_handler becomes a thin shim that takes
ownership of ep->fsm_in.pkt and either runs the routing body inline
(flag OFF, byte-for-byte the prior path) or dispatches it to the vtask
pool via vtask_enq_classified (flag ON). The 3 routers now take pkt as
an explicit param (no cross-thread ep->fsm_in.pkt re-read). Per-endpoint
arrival order is enforced by bind_vtask's endpoint_block_tasks.

Packet ownership: shim clears ep->fsm_in.pkt at dispatch (sole-owner
transfer); peer_ctrl_vtask_entry NULLs iod->pkt before the body frees
it once. New vtask_t.owns_io_pkt (default 0) lets taskq_item_killer free
the carried packet for an un-executed queued task at vtaskmgr_fini WITHOUT
double-freeing the libxce endpoint-owned path (bug-1199).

Adversarial review found two FLAG-ON hazards, documented with loud
comments (flag is OFF so production is unaffected), to be resolved before
task 3.4 flips the flag ON:
  - bug-1197 (CRITICAL): raft_cb_become_follower endpoint_fini()s peer
    vote-eps from a pool worker, racing the io_worker's read of ep->data;
    needs iomgr_unmonitor before endpoint_fini in every pool-reachable
    teardown.
  - bug-1198 (HIGH): peer_ctrl_classify tags all work VTASK_LIVENESS, but
    the CTRL_PRIMARY repl-ack path can run a blocking snapshot checkpoint
    inline; resolved by task 3.2 (offload snapshot to SlowLane).

Per spec, 3.1 is not 'done' until the task 3.4 deadlock gate is green.
Tests: 3 new peerctrl tests; controller 196/196, lib 282/0; 0 net leaks.
2026-06-26 11:12:04 +00:00

138 lines
5.1 KiB
C

#if !defined(_VTASKMGR_H_)
#define _VTASKMGR_H_ 1
#include <pthread.h>
#include <stdatomic.h>
#include <stddef.h>
#include "dataq.h"
#include "mylock.h"
#include "netio.h"
#include "request.h"
typedef int (*vtask_func_t)(void*);
#define VTASK_IO 0x01
#define VTASK_GENERIC 0x02
/* raft-drive-async task 1.6: LivenessAdmission task classes.
*
* VTASK_LIVENESS never parks — WAL-ack/heartbeat/vote receipt+send.
* NEVER admission-gated, so >= VTASK_LIVENESS_RESERVED
* workers are always free for it (deadlock-freedom).
* VTASK_LIVE_BLOCK bounded mandatory park — vote-grant term fsync (R4.6).
* Runs in the reserved headroom; not gated by the
* REQ_BLOCK cap (it is short and bounded).
* VTASK_REQ_BLOCK may park seconds — commit-wait / config-ack wait.
* Admission-gated: rejected with -E_XCE_NOT_READY when
* admitting it would breach the reservation. */
typedef enum {
VTASK_LIVENESS = 0,
VTASK_LIVE_BLOCK,
VTASK_REQ_BLOCK
} vtask_class_t;
typedef struct {
int vtype;
union {
void* generic;
io_task_data_t iodata;
} u;
} vtask_data_t;
typedef struct {
data_item_t link;
void* owner;
vtask_data_t* data;
int vcpu;
vtask_func_t entry;
vtask_class_t cls; /* LivenessAdmission class (task 1.6); default LIVENESS */
/* raft-drive-async task 3.1: when non-zero AND data->vtype == VTASK_IO, the
* carried io_packet_t (data->u.iodata.pkt) is OWNED by the task and must be
* freed by taskq_item_killer if the task is destroyed while still queued
* (never executed) at vtaskmgr_fini. Default 0 preserves the legacy
* endpoint-owned-packet model used by the libxce client path (xcectx.c's
* vtask_handle_msg does NOT clear ep->fsm_in.pkt, so the packet lifetime is
* tied to the endpoint, not the task — freeing it here would double-free).
* The controller's peer-control dispatch sets this to 1 because it clears
* ep->fsm_in.pkt and the task becomes the sole owner. */
int owns_io_pkt;
} vtask_t;
#define ITPTR_TO_VTASK(p) ((vtask_t*)(((char*)p) - offsetof(vtask_t, link)))
typedef enum {
VCPU_IDLE = 0,
VCPU_BUSY,
VCPU_ERR
} vcpu_state_t;
typedef struct {
int cpuid;
vcpu_state_t state;
void* owner;
cbuf_t wbuf;
vtask_t* task;
pthread_t vcpu;
} vcpu_t;
typedef struct {
int n_vcpus;
pthread_t dispatcher;
vcpu_t* vcpus;
cbuf_t wbuf;
cbuf_t wtbuf; /* used to wake up vtaskmgr to check tasks */
dataq_t taskq;
/* LivenessAdmission (raft-drive-async task 1.6): pool-level reservation.
* liveness_reserved == 0 disables the gate (legacy: nothing gated).
* blocking_inflight counts in-flight *blocking-admission* holders
* (VTASK_REQ_BLOCK tasks + commit_submit waits folded onto this seam);
* capped at max(1, n_vcpus - liveness_reserved). */
int liveness_reserved;
int blocking_inflight;
pthread_mutex_t blk_lock; /* guards blocking_inflight check-and-increment */
} vtaskmgr_t;
extern vtask_t* vtask_alloc();
extern int vtask_init(vtask_t*, vtask_func_t, void*);
extern void vtask_fini(vtask_t*);
extern int vtask_enq(vtaskmgr_t*, vtask_t*);
extern int vtask_handle_msg(vtaskmgr_t*, vtask_func_t, void* arg);
/* LivenessAdmission (raft-drive-async task 1.6) — pool-level reservation.
*
* vtaskmgr_set_liveness_reserved: set R, the count of vcpus reserved for
* never-gated VTASK_LIVENESS work. The controller resolves R via
* drive_async_liveness_reserved() (the single source of truth) and wires it
* here at init. R == 0 disables the gate (legacy behavior). */
extern void vtaskmgr_set_liveness_reserved(vtaskmgr_t*, int reserved);
/* Effective blocking-admission cap = max(1, n_vcpus - liveness_reserved)
* (the cap is floored at 1 so a commit can always run); returns n_vcpus when
* the gate is disabled (reserved == 0). */
extern int vtaskmgr_blocking_cap(const vtaskmgr_t*);
/* Acquire/release a blocking-admission slot. acquire returns 0 when admitted
* or -E_XCE_NOT_READY when admitting would breach the reservation (fast
* reject — never blocks). When the gate is disabled (reserved == 0) acquire
* always succeeds and is uncounted. This is the seam commit.c folds onto:
* commit_submit acquires before its blocking wait and releases on wake. */
extern int vtaskmgr_blocking_acquire(vtaskmgr_t*);
extern void vtaskmgr_blocking_release(vtaskmgr_t*);
/* Classified submit. VTASK_REQ_BLOCK is admission-gated: returns
* -E_XCE_NOT_READY (task NOT enqueued — caller still owns it) when admitting
* would breach the reservation. VTASK_LIVENESS / VTASK_LIVE_BLOCK are never
* gated (always enqueued). On success the task is enqueued and owned by the
* pool. */
extern int vtask_enq_classified(vtaskmgr_t*, vtask_t*, vtask_class_t);
extern int vtaskmgr_init(vtaskmgr_t*, int);
extern int vtaskmgr_start(vtaskmgr_t*);
extern void vtaskmgr_fini(vtaskmgr_t*);
extern void vtaskmgr_wait(vtaskmgr_t*);
extern void vtaskmgr_wakeup(vtaskmgr_t*);
#endif