feat(raft-drive-async): SlowLane bounded executor for long blocking I/O (task 1.2)

Dedicated-pthread pool isolated from the vtask pool; non-blocking slowlane_submit
(0 enqueued / -EEXIST deduped / -EAGAIN cap-full-deferred), admission cap on
running tasks, per-key in-flight dedup, run-exactly-once, drain+join fini. Wired
into controller lifecycle (dormant; consumers land in 2.x/3.x). 7 unit tests,
mutation-verified cap+dedup; full suite 194/0 green, 15x ASAN.
This commit is contained in:
Rongsong Shen 2026-06-26 05:24:00 +00:00
parent 729055a37f
commit 5650acd375
8 changed files with 710 additions and 1 deletions

View file

@ -0,0 +1,95 @@
#pragma once
/*
* slowlane.h — SlowLane executor: a bounded pool of dedicated worker threads
* that runs long blocking I/O OFF the Raft drive threads (iomgr epoll loop,
* delaymgr timer), with admission control and per-key in-flight dedup.
*
* Service interface is verbatim from
* .kiro/specs/raft-drive-async/design.md (Components -> SlowLane).
*
* The lane owns scheduling/isolation, NOT the work content: the submitted
* `fn` (peer connect, snapshot transfer, config-push body) stays in its own
* module and is responsible for clearing any caller-side in-flight state on
* completion. `slowlane_submit` is non-blocking: a drive-thread caller
* enqueues and returns immediately, never waiting for `fn` to run.
*
* raft-drive-async task 1.2 — Requirements 3.1, 3.2, 3.3, 3.4, 1.5.
*/
#include <pthread.h>
#include <stdint.h>
typedef void (*slowlane_fn_t)(void *arg);
/*
* One queued unit of work. Allocated by the lane on submit, freed by the
* worker after `fn` returns. Held on the FIFO pending queue until a worker
* picks it up.
*/
typedef struct slowlane_task {
uint64_t key; /* dedup key; 0 disables dedup */
slowlane_fn_t fn;
void *arg;
struct slowlane_task *next; /* intrusive FIFO link */
} slowlane_task_t;
/*
* SlowLane executor state. All fields are lane-private and guarded by `lock`;
* callers touch the lane only through the API below. Bounded by design:
* - `n_workers` dedicated pthreads, isolated from the vtask request pool;
* - at most `max_inflight` tasks RUNNING concurrently (admission cap);
* - at most one in-flight task per non-zero key (dedup).
*/
typedef struct slowlane {
pthread_mutex_t lock;
pthread_cond_t cond; /* signalled when work or a slot frees up */
slowlane_task_t *q_head; /* FIFO pending queue (admission-deferred) */
slowlane_task_t *q_tail;
uint64_t *inflight_keys; /* running OR queued non-zero keys (dedup set) */
int n_inflight_keys;
int cap_inflight_keys;
int n_workers;
int max_inflight; /* admission cap on concurrently RUNNING tasks */
int n_running; /* tasks currently executing on a worker */
pthread_t *workers;
int started; /* worker threads created */
int stopping; /* fini requested: drain + join */
} slowlane_t;
/*
* Initialise the lane and start `n_workers` dedicated worker threads.
* `max_inflight` bounds the number of tasks RUNNING concurrently (excess is
* queued, never dropped, never run on a drive thread).
* Returns 0 on success, -EINVAL on a bad argument, -errno on a thread/alloc
* failure (lane left un-started).
*/
int slowlane_init(slowlane_t *sl, int n_workers, int max_inflight);
/*
* Submit `fn(arg)` to run exactly once on a lane worker. NON-BLOCKING: the
* caller (which may be a drive thread) returns immediately.
*
* Returns (per design SlowLane Service Interface):
* 0 enqueued AND a worker slot was free (running < max_inflight) —
* the task can start immediately
* -EEXIST `key` (non-zero) already in flight — deduped no-op, NOT enqueued
* -EAGAIN admission cap full at submit time (running >= max_inflight) — the
* task IS still queued/deferred (never dropped, never run on a drive
* thread); the caller is told it was deferred, not that it failed
* -errno allocation failure / lane stopping
*
* In all of {0, -EAGAIN} the work is enqueued and will run exactly once. Only
* -EEXIST and -errno mean the task was not enqueued.
*
* `key == 0` disables dedup (every key-0 submit is independent work).
*/
int slowlane_submit(slowlane_t *sl, uint64_t key, slowlane_fn_t fn, void *arg);
/*
* Drain the pending queue, stop and join all workers. Shutdown only.
* After return the lane is dormant; re-init to reuse.
*/
void slowlane_fini(slowlane_t *sl);