feat: add persist.h — WAL types, persist_config_t, slot_key_t (P0-B)

- Define wal_entry_hdr_t (40 bytes, _Static_assert enforced)
- Define wal_entry_type enum (WAL_JOB_SUBMIT=1 … WAL_CHECKPOINT=100)
- Define persist_config_t with persist_config_defaults() inline helper
- Define slot_key_t (index, key[XCE_MAXNAME_LEN], key_len)
- Forward-declare persist_t opaque struct and full persist_* API surface
- Bootstrap controller Unity test suite (controller_unit_test binary)
- Register test('controller test', …, suite: 'controller') in meson
- 4 new tests all PASS: wal_header_size, wal_entry_types,
  persist_config_defaults, slot_key_t_fields
This commit is contained in:
Rongsong Shen 2026-05-07 09:21:36 +00:00
parent 0107828a77
commit fe69dcbd7a
4 changed files with 282 additions and 0 deletions

View file

@ -61,3 +61,25 @@ xce_test = executable('xce_test',
dependencies: xce_test_deps,
install: true)
# Controller Unity test suite (persist types and future controller unit tests)
controller_unit_test_src = [
'testsuite/controller_test.c',
'testsuite/test_persist_types.c',
]
controller_unit_test_deps = [
libxce_dep,
unity_dep,
tinylog_dep,
]
controller_unit_test = executable('controller_unit_test',
controller_unit_test_src,
include_directories: [incdir],
dependencies: controller_unit_test_deps,
install: false)
test('controller test', controller_unit_test,
suite: 'controller',
timeout: 60)

View file

@ -0,0 +1,86 @@
#include <argp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unity/unity.h>
extern void test_persist_types(void);
void setUp(void) { return; }
void tearDown(void)
{
printf("\n-----------------------------------------------------\n");
return;
}
static char doc[] = "controller_test";
static char args_doc[] = "";
#define MAX_TESTS (256)
struct arguments {
int ntests;
char* tests[MAX_TESTS];
};
static struct argp_option options[] = {
{ "run", 't', "STR", 0, "run test" },
{ 0 }
};
static error_t parse_opt(int key, char* arg, struct argp_state* state)
{
struct arguments* arguments = state->input;
switch (key) {
case 't':
if (arguments->ntests >= MAX_TESTS) {
fprintf(stderr, "Too many tests\n");
exit(-1);
}
arguments->tests[arguments->ntests] = arg;
arguments->ntests++;
break;
default:
return ARGP_ERR_UNKNOWN;
}
return 0;
}
static struct argp argp = { options, parse_opt, args_doc, doc };
struct arguments arguments;
static int check_test(struct arguments* args, char* x)
{
int i;
if (args->ntests == 0) {
return 1;
}
for (i = 0; i < args->ntests; i++) {
if (strcmp(x, args->tests[i]) == 0) {
return 1;
}
}
return 0;
}
#define TEST(x) \
do { \
if (check_test(&arguments, #x)) { \
x(); \
} \
} while (0)
int main(int argc, char* argv[])
{
argp_parse(&argp, argc, argv, 0, 0, &arguments);
UNITY_BEGIN();
TEST(test_persist_types);
return UNITY_END();
}

View file

@ -0,0 +1,68 @@
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <unity/unity.h>
#include "config.h"
#include "persist.h"
static void test_wal_header_size(void)
{
/* wal_entry_hdr_t must be exactly 40 bytes for on-disk format stability */
TEST_ASSERT_EQUAL_INT(40, (int)sizeof(wal_entry_hdr_t));
printf("test_wal_header_size: PASS\n");
}
static void test_wal_entry_types(void)
{
TEST_ASSERT_EQUAL_INT(1, WAL_JOB_SUBMIT);
TEST_ASSERT_EQUAL_INT(2, WAL_JOB_START);
TEST_ASSERT_EQUAL_INT(3, WAL_JOB_FINISH);
TEST_ASSERT_EQUAL_INT(4, WAL_JOB_FAIL);
TEST_ASSERT_EQUAL_INT(5, WAL_JOB_STOP);
TEST_ASSERT_EQUAL_INT(6, WAL_JOB_RESUME);
TEST_ASSERT_EQUAL_INT(7, WAL_JOB_UPDATE);
TEST_ASSERT_EQUAL_INT(8, WAL_JOB_RUSAGE);
TEST_ASSERT_EQUAL_INT(9, WAL_JOB_CKPT);
TEST_ASSERT_EQUAL_INT(10, WAL_JOB_PREASON);
TEST_ASSERT_EQUAL_INT(20, WAL_QUEUE_CTRL);
TEST_ASSERT_EQUAL_INT(30, WAL_WORKER_CTRL);
TEST_ASSERT_EQUAL_INT(100, WAL_CHECKPOINT);
printf("test_wal_entry_types: PASS\n");
}
static void test_persist_config_defaults(void)
{
persist_config_t cfg = persist_config_defaults();
TEST_ASSERT_EQUAL_INT(DEFAULT_WAL_SYNC_MS, (int)cfg.wal_sync_ms);
TEST_ASSERT_EQUAL_INT(DEFAULT_SNAP_INTERVAL_S, (int)cfg.snap_interval_s);
TEST_ASSERT_EQUAL_INT(DEFAULT_SNAP_WAL_SIZE_MB, (int)cfg.snap_wal_size_mb);
TEST_ASSERT_EQUAL_INT(DEFAULT_SNAP_KEEP_N, (int)cfg.snap_keep_n);
TEST_ASSERT_EQUAL_INT(DEFAULT_TRANSIENT_JOB_TIMEOUT_S, (int)cfg.transient_job_timeout_s);
TEST_ASSERT_EQUAL_INT(DEFAULT_FINISHED_RETAIN_S, (int)cfg.finished_retain_s);
TEST_ASSERT_EQUAL_INT(DEFAULT_WORKER_RETAIN_S, (int)cfg.worker_retain_s);
TEST_ASSERT_EQUAL_INT(DEFAULT_SYNC_TIMEOUT_MS, (int)cfg.sync_timeout_ms);
printf("test_persist_config_defaults: PASS\n");
}
static void test_slot_key_t_fields(void)
{
/* slot_key_t must have index, key[XCE_MAXNAME_LEN], and key_len fields */
slot_key_t sk;
sk.index = 42;
sk.key[0] = 'x';
sk.key_len = 1;
TEST_ASSERT_EQUAL_INT(42, sk.index);
printf("test_slot_key_t_fields: PASS\n");
}
void test_persist_types(void)
{
printf("\n>> persist types test\n\n");
RUN_TEST(test_wal_header_size);
RUN_TEST(test_wal_entry_types);
RUN_TEST(test_persist_config_defaults);
RUN_TEST(test_slot_key_t_fields);
}

106
src/include/persist.h Normal file
View file

@ -0,0 +1,106 @@
#pragma once
#include <pthread.h>
#include <stdatomic.h>
#include <stddef.h>
#include <stdint.h>
#include "config.h"
/* Forward declaration */
typedef struct cluster_s cluster_t;
/* ============================================================
* WAL entry header — 40 bytes, 8-byte aligned (Part 2.2)
* ============================================================ */
typedef struct {
uint64_t term; /* Election term (incremented on failover) */
uint64_t lsn; /* Log sequence number within this term */
uint64_t timestamp; /* Epoch microseconds */
uint32_t type; /* wal_entry_type enum value */
uint32_t payload_len; /* Bytes of payload following this header */
uint32_t crc; /* CRC32(header[0..WAL_HDR_CRC_SIZE-1] + payload) */
uint32_t prev_crc; /* CRC of previous record; WAL_CRC_NONE for first in segment */
} wal_entry_hdr_t;
_Static_assert(sizeof(wal_entry_hdr_t) == 40,
"wal_entry_hdr_t must be 40 bytes for on-disk format stability");
/* ============================================================
* WAL entry types (Part 2.3)
* ============================================================ */
typedef enum {
WAL_JOB_SUBMIT = 1, /* SYNC replication */
WAL_JOB_START = 2,
WAL_JOB_FINISH = 3,
WAL_JOB_FAIL = 4,
WAL_JOB_STOP = 5,
WAL_JOB_RESUME = 6,
WAL_JOB_UPDATE = 7,
WAL_JOB_RUSAGE = 8,
WAL_JOB_CKPT = 9,
WAL_JOB_PREASON = 10,
WAL_QUEUE_CTRL = 20,
WAL_WORKER_CTRL = 30,
WAL_CHECKPOINT = 100, /* Local WAL marker only — never replicated */
} wal_entry_type;
/* ============================================================
* Persistence configuration (Part 2.6)
* ============================================================ */
typedef struct {
uint32_t wal_sync_ms; /* fsync interval in ms (default 10) */
uint32_t snap_interval_s; /* checkpoint interval in seconds (default 60) */
uint32_t snap_wal_size_mb; /* checkpoint when WAL exceeds this MB (default 64) */
uint32_t snap_keep_n; /* snapshots to retain (default 2) */
uint32_t transient_job_timeout_s; /* seconds before TRANSIENT job auto-terminates (default 300) */
uint32_t finished_retain_s; /* retain finished jobs N seconds (default 3600) */
uint32_t worker_retain_s; /* retain unavailable workers N seconds (default 86400) */
uint32_t sync_timeout_ms; /* sync replication ACK timeout ms (default 50) */
} persist_config_t;
static inline persist_config_t persist_config_defaults(void)
{
persist_config_t c;
c.wal_sync_ms = DEFAULT_WAL_SYNC_MS;
c.snap_interval_s = DEFAULT_SNAP_INTERVAL_S;
c.snap_wal_size_mb = DEFAULT_SNAP_WAL_SIZE_MB;
c.snap_keep_n = DEFAULT_SNAP_KEEP_N;
c.transient_job_timeout_s = DEFAULT_TRANSIENT_JOB_TIMEOUT_S;
c.finished_retain_s = DEFAULT_FINISHED_RETAIN_S;
c.worker_retain_s = DEFAULT_WORKER_RETAIN_S;
c.sync_timeout_ms = DEFAULT_SYNC_TIMEOUT_MS;
return c;
}
/* ============================================================
* Key→index mapping for checkpoint serialization (Part 3.8)
* ============================================================ */
typedef struct {
int index; /* shadow slot index in the frozen zarray */
char key[XCE_MAXNAME_LEN]; /* owned deep copy — not a borrowed kvtab pointer */
size_t key_len; /* length including null terminator */
} slot_key_t;
/* ============================================================
* persist_t — opaque; defined in persist.c (Task 7)
* ============================================================ */
typedef struct persist_s persist_t;
/* Lifecycle */
int persist_init(persist_t* p, persist_config_t* config, cluster_t* c);
int persist_start(persist_t* p);
void persist_stop(persist_t* p);
void persist_fini(persist_t* p);
int persist_recover(persist_t* p);
/* WAL write paths */
int persist_wal_async(persist_t* p, uint32_t type,
const void* payload, uint32_t len);
int persist_wal_sync_append(persist_t* p, uint32_t type,
const void* payload, uint32_t len,
int* sync_rc_out);
/* Checkpoint */
int persist_checkpoint(persist_t* p);
/* Logging helper — never logs payload content (security invariant) */
void persist_log_entry_metadata(const wal_entry_hdr_t* hdr);