feat: P2-1 repl.h/repl.c — replicator lifecycle, slot management, trim_lsn
Add replicator_t structure with per-standby connection slots (standby_conn_t), lifecycle (repl_init/repl_fini), slot management (repl_add_standby, repl_remove_standby, repl_expire_disconnect_slots), ACK tracking (repl_on_ack), WAL trim guard (repl_trim_lsn), and stubs for repl_broadcast/repl_wait_ack (wired in P2-3/P2-4). Uses mylock_t (pthread_mutex_t alias) with mylock_lock/mylock_unlock. snap_interval_s==0 in repl_expire_disconnect_slots forces immediate expiry to support deterministic unit testing. 5 new Unity tests all pass; 115 pre-existing controller tests unchanged.
This commit is contained in:
parent
05c00704d3
commit
8ef64a7ee7
5 changed files with 397 additions and 0 deletions
249
src/controller/repl.c
Normal file
249
src/controller/repl.c
Normal file
|
|
@ -0,0 +1,249 @@
|
|||
/*
|
||||
* repl.c — P2-1: Replicator lifecycle, slot management, and WAL trim guard.
|
||||
*
|
||||
* mylock_t is a plain pthread_mutex_t alias (see mylock.h).
|
||||
* API: mylock_init / mylock_fini / mylock_lock / mylock_unlock
|
||||
*/
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "repl.h"
|
||||
#include "mylock.h"
|
||||
|
||||
/* ============================================================
|
||||
* Lifecycle
|
||||
* ============================================================ */
|
||||
|
||||
int repl_init(replicator_t *r)
|
||||
{
|
||||
if (r == NULL) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
memset(r, 0, sizeof(*r));
|
||||
|
||||
int rc = mylock_init(&r->lock);
|
||||
if (rc != 0) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = pthread_mutex_init(&r->ack_lock, NULL);
|
||||
if (rc != 0) {
|
||||
mylock_fini(&r->lock);
|
||||
return -rc;
|
||||
}
|
||||
|
||||
rc = pthread_cond_init(&r->ack_cond, NULL);
|
||||
if (rc != 0) {
|
||||
pthread_mutex_destroy(&r->ack_lock);
|
||||
mylock_fini(&r->lock);
|
||||
return -rc;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void repl_fini(replicator_t *r)
|
||||
{
|
||||
if (r == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
pthread_cond_destroy(&r->ack_cond);
|
||||
pthread_mutex_destroy(&r->ack_lock);
|
||||
mylock_fini(&r->lock);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* Standby slot management
|
||||
* ============================================================ */
|
||||
|
||||
int repl_add_standby(replicator_t *r, repl_ep_t ep, const char *host, uint32_t port)
|
||||
{
|
||||
if (r == NULL || ep == NULL || host == NULL) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mylock_lock(&r->lock);
|
||||
|
||||
if (r->nstandbys >= MAX_STANDBYS) {
|
||||
mylock_unlock(&r->lock);
|
||||
return -ENOSPC;
|
||||
}
|
||||
|
||||
/* Find first empty slot */
|
||||
int idx = -1;
|
||||
for (int i = 0; i < MAX_STANDBYS; i++) {
|
||||
if (r->standbys[i].ep == NULL) {
|
||||
idx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (idx < 0) {
|
||||
/* Should not happen given nstandbys guard above, but be safe */
|
||||
mylock_unlock(&r->lock);
|
||||
return -ENOSPC;
|
||||
}
|
||||
|
||||
standby_conn_t *s = &r->standbys[idx];
|
||||
memset(s, 0, sizeof(*s));
|
||||
s->ep = ep;
|
||||
s->port = port;
|
||||
strncpy(s->host, host, XCE_MAXNAME_LEN - 1);
|
||||
s->host[XCE_MAXNAME_LEN - 1] = '\0';
|
||||
atomic_store(&s->acked_lsn, 0);
|
||||
atomic_store(&s->in_catch_up, 0);
|
||||
|
||||
r->nstandbys++;
|
||||
|
||||
mylock_unlock(&r->lock);
|
||||
return idx;
|
||||
}
|
||||
|
||||
void repl_remove_standby(replicator_t *r, repl_ep_t ep)
|
||||
{
|
||||
if (r == NULL || ep == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
mylock_lock(&r->lock);
|
||||
|
||||
for (int i = 0; i < MAX_STANDBYS; i++) {
|
||||
standby_conn_t *s = &r->standbys[i];
|
||||
if (s->ep == ep) {
|
||||
s->last_disconnected_lsn = atomic_load(&s->acked_lsn);
|
||||
s->last_disconnect_time = time(NULL);
|
||||
s->ep = NULL;
|
||||
r->nstandbys--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mylock_unlock(&r->lock);
|
||||
}
|
||||
|
||||
void repl_expire_disconnect_slots(replicator_t *r, time_t now_s, uint32_t snap_interval_s)
|
||||
{
|
||||
if (r == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
mylock_lock(&r->lock);
|
||||
|
||||
for (int i = 0; i < MAX_STANDBYS; i++) {
|
||||
standby_conn_t *s = &r->standbys[i];
|
||||
/* Only process disconnected (ep==NULL) slots that still hold a guard LSN */
|
||||
if (s->ep == NULL && s->last_disconnected_lsn != 0) {
|
||||
/* snap_interval_s==0 means expire immediately regardless of wall clock */
|
||||
int expired = (snap_interval_s == 0) ||
|
||||
(now_s >= s->last_disconnect_time + (time_t)snap_interval_s);
|
||||
if (expired) {
|
||||
memset(s, 0, sizeof(*s));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mylock_unlock(&r->lock);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* ACK handling — call from io_worker read callback ONLY
|
||||
* ============================================================ */
|
||||
|
||||
void repl_on_ack(replicator_t *r, repl_ep_t ep, uint64_t acked_lsn)
|
||||
{
|
||||
if (r == NULL || ep == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
mylock_lock(&r->lock);
|
||||
|
||||
for (int i = 0; i < MAX_STANDBYS; i++) {
|
||||
standby_conn_t *s = &r->standbys[i];
|
||||
if (s->ep == ep) {
|
||||
atomic_store(&s->acked_lsn, acked_lsn);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mylock_unlock(&r->lock);
|
||||
|
||||
/* Signal any waiter (repl_wait_ack) */
|
||||
pthread_mutex_lock(&r->ack_lock);
|
||||
pthread_cond_signal(&r->ack_cond);
|
||||
pthread_mutex_unlock(&r->ack_lock);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* Replication send — stub in P2-1; wired in P2-4
|
||||
* ============================================================ */
|
||||
|
||||
int repl_broadcast(replicator_t *r, const wal_entry_hdr_t *hdr,
|
||||
const void *payload, uint32_t payload_len)
|
||||
{
|
||||
(void)r;
|
||||
(void)payload;
|
||||
(void)payload_len;
|
||||
|
||||
/* WAL_CHECKPOINT entries must never be replicated */
|
||||
assert(hdr != NULL);
|
||||
assert(hdr->type != WAL_CHECKPOINT);
|
||||
|
||||
/* Full implementation in P2-4 */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* Sync replication — stub in P2-1; wired in P2-3
|
||||
* ============================================================ */
|
||||
|
||||
int repl_wait_ack(replicator_t *r, uint64_t target_lsn, uint32_t timeout_ms)
|
||||
{
|
||||
(void)r;
|
||||
(void)target_lsn;
|
||||
(void)timeout_ms;
|
||||
|
||||
/* Full implementation in P2-3 */
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* WAL trim guard
|
||||
* ============================================================ */
|
||||
|
||||
uint64_t repl_trim_lsn(replicator_t *r, uint64_t checkpoint_lsn,
|
||||
uint32_t snap_interval_s)
|
||||
{
|
||||
(void)snap_interval_s;
|
||||
|
||||
if (r == NULL) {
|
||||
return checkpoint_lsn;
|
||||
}
|
||||
|
||||
uint64_t min_lsn = checkpoint_lsn;
|
||||
|
||||
mylock_lock(&r->lock);
|
||||
|
||||
for (int i = 0; i < MAX_STANDBYS; i++) {
|
||||
const standby_conn_t *s = &r->standbys[i];
|
||||
|
||||
if (s->ep != NULL) {
|
||||
/* Active standby: guard at its last acked LSN */
|
||||
uint64_t acked = atomic_load(&s->acked_lsn);
|
||||
if (acked < min_lsn) {
|
||||
min_lsn = acked;
|
||||
}
|
||||
} else if (s->last_disconnected_lsn != 0) {
|
||||
/* Recently disconnected slot still holding a guard */
|
||||
if (s->last_disconnected_lsn < min_lsn) {
|
||||
min_lsn = s->last_disconnected_lsn;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mylock_unlock(&r->lock);
|
||||
|
||||
return min_lsn;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue