feat: P2-4 wire repl_broadcast into persist_wal_async/sync_append; last_replicated_crc
- Move repl.h from controller/ to include/ (shared header) to break persist.h ↔ repl.h circular include - Extract wal_entry_hdr_t + wal_entry_type into include/wal_types.h so repl.h can include only what it needs without pulling in all of persist.h - Add replicator_t repl field to persist_t (embedded by value, P2-4) - Wire repl_init/repl_fini into persist_init/persist_fini lifecycle - Wire repl_broadcast into persist_wal_async after each WAL write; last_replicated_crc updated here (not in persist_write_checkpoint_marker) - Wire repl_wait_ack into persist_wal_sync_append; returns 0 on local WAL success; sync_rc_out carries -ENODEV/-ETIMEDOUT for degraded mode - Add 4 TDD tests in test_persist_repl.c: structural repl field check, async no-standbys succeeds, sync degraded mode rc, checkpoint CRC invariant
This commit is contained in:
parent
d326ee1b2e
commit
3563d7387b
7 changed files with 228 additions and 41 deletions
|
|
@ -168,6 +168,7 @@ int persist_init(persist_t* p, persist_config_t* cfg, cluster_t* c)
|
|||
pthread_mutex_init(&p->lock, NULL);
|
||||
pthread_mutex_init(&p->checkpoint_mutex, NULL);
|
||||
pthread_cond_init(&p->checkpoint_done, NULL);
|
||||
repl_init(&p->repl); /* P2-4: fan-out replicator lifecycle */
|
||||
|
||||
fprintf(stderr,
|
||||
"INFO: WAL/snapshot stored unencrypted in %s — "
|
||||
|
|
@ -214,6 +215,7 @@ void persist_stop(persist_t* p)
|
|||
|
||||
void persist_fini(persist_t* p)
|
||||
{
|
||||
repl_fini(&p->repl); /* P2-4: fan-out replicator lifecycle */
|
||||
pthread_mutex_destroy(&p->lock);
|
||||
pthread_mutex_destroy(&p->checkpoint_mutex);
|
||||
pthread_cond_destroy(&p->checkpoint_done);
|
||||
|
|
@ -274,8 +276,15 @@ int persist_wal_async(persist_t* p, uint32_t type,
|
|||
p->seg_offset += entry_size;
|
||||
p->total_wal_bytes += entry_size;
|
||||
p->last_crc = hdr.crc;
|
||||
/* For single-controller P0 there is no replication; track same value */
|
||||
p->last_replicated_crc = hdr.crc;
|
||||
|
||||
/* Replicate to standbys — never replicate WAL_CHECKPOINT entries.
|
||||
* last_replicated_crc is distinct from last_crc:
|
||||
* last_crc includes checkpoint markers; last_replicated_crc skips them.
|
||||
* This maintains a continuous CRC chain for standbys (design doc Part 2.3).
|
||||
* Note: WAL_CHECKPOINT is blocked by the assert at function entry, so
|
||||
* every entry that reaches here is a replicable entry. */
|
||||
p->last_replicated_crc = hdr.crc;
|
||||
repl_broadcast(&p->repl, &hdr, payload, len);
|
||||
|
||||
/* Rotate segment when full */
|
||||
if (p->seg_offset >= WAL_SEGMENT_SIZE_BYTES) {
|
||||
|
|
@ -845,9 +854,26 @@ int persist_wal_sync_append(persist_t* p, uint32_t type,
|
|||
int* sync_rc_out)
|
||||
{
|
||||
int rc = persist_wal_async(p, type, payload, len);
|
||||
if (rc != 0) {
|
||||
if (sync_rc_out)
|
||||
*sync_rc_out = rc;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* For sync replication (WAL_JOB_SUBMIT): wait for at least one standby ACK.
|
||||
* We use current_lsn - 1 because persist_wal_async already incremented it. */
|
||||
uint64_t written_lsn;
|
||||
pthread_mutex_lock(&p->lock);
|
||||
written_lsn = p->current_lsn - 1;
|
||||
pthread_mutex_unlock(&p->lock);
|
||||
|
||||
int sync_err = repl_wait_ack(&p->repl, written_lsn,
|
||||
p->config.sync_timeout_ms);
|
||||
if (sync_rc_out)
|
||||
*sync_rc_out = rc;
|
||||
return rc;
|
||||
*sync_rc_out = sync_err;
|
||||
/* -ETIMEDOUT = degraded mode (local-only durability); -ENODEV = no standbys.
|
||||
* Both are acceptable — primary continues operating. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
void persist_log_entry_metadata(const wal_entry_hdr_t* hdr)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue