From fe69dcbd7adcb796ddb940e00e86e1c4164f704f Mon Sep 17 00:00:00 2001 From: Rongsong Shen Date: Thu, 7 May 2026 09:21:36 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20add=20persist.h=20=E2=80=94=20WAL=20typ?= =?UTF-8?q?es,=20persist=5Fconfig=5Ft,=20slot=5Fkey=5Ft=20(P0-B)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- src/controller/meson.build | 22 ++++ src/controller/testsuite/controller_test.c | 86 ++++++++++++++ src/controller/testsuite/test_persist_types.c | 68 +++++++++++ src/include/persist.h | 106 ++++++++++++++++++ 4 files changed, 282 insertions(+) create mode 100644 src/controller/testsuite/controller_test.c create mode 100644 src/controller/testsuite/test_persist_types.c create mode 100644 src/include/persist.h diff --git a/src/controller/meson.build b/src/controller/meson.build index 09b8116..23ac5df 100644 --- a/src/controller/meson.build +++ b/src/controller/meson.build @@ -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) + diff --git a/src/controller/testsuite/controller_test.c b/src/controller/testsuite/controller_test.c new file mode 100644 index 0000000..fd57952 --- /dev/null +++ b/src/controller/testsuite/controller_test.c @@ -0,0 +1,86 @@ +#include +#include +#include +#include + +#include + +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(); +} diff --git a/src/controller/testsuite/test_persist_types.c b/src/controller/testsuite/test_persist_types.c new file mode 100644 index 0000000..8a32772 --- /dev/null +++ b/src/controller/testsuite/test_persist_types.c @@ -0,0 +1,68 @@ +#include +#include +#include + +#include + +#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); +} diff --git a/src/include/persist.h b/src/include/persist.h new file mode 100644 index 0000000..b436c5d --- /dev/null +++ b/src/include/persist.h @@ -0,0 +1,106 @@ +#pragma once +#include +#include +#include +#include + +#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);