skip to content
==> tests/catchup.c <==
1/* fleet.rs's catch-up tests (review finding 1) over a network delivered by hand, and review finding
2 * 4: nothing leaves without its persist. */
3#include "mesh/check.h"
4#include "mesh/fleet_core.h"
5#include "mesh/mt.h"
6
7#include <stdlib.h>
8#include <string.h>
9
10typedef struct {
11    uint32_t from, to;
12    mfc_msg m;
13} qitem;
14
15typedef struct {
16    mfc_core *c[5];
17    uint32_t n;
18    qitem *q;
19    size_t qh, qn, qcap;
20    bool down[5];
21    uint64_t now;
22    mfc_id high[5];
23} net_t;
24
25static void enqueue(net_t *n, uint32_t from, uint32_t to, mfc_msg m) {
26    if (n->qn == n->qcap) {
27        n->qcap = n->qcap ? n->qcap * 2 : 64;
28        n->q = mt_realloc(n->q, n->qcap * sizeof *n->q);
29    }
30    n->q[n->qn++] = (qitem){from, to, m};
31}
32
33static void out(net_t *n, uint32_t k, mfc_outs o) {
34    for (size_t i = 0; i < o.n; i++)
35        if (o.v[i].kind == MFC_OUT_SEND) {
36            enqueue(n, k, o.v[i].to, o.v[i].msg);
37            o.v[i].kind = MFC_OUT_LOG; /* moved */
38        }
39    mfc_outs_free(&o);
40    mfc_id id = mfc_doc_id(mfc_committed(n->c[k]));
41    CHECK(mfc_id_cmp(id, n->high[k]) >= 0);
42    n->high[k] = id;
43}
44
45static void net_init(net_t *n, uint32_t count) {
46    memset(n, 0, sizeof *n);
47    n->n = count;
48    n->now = 1;
49    for (uint32_t k = 0; k < count; k++) n->c[k] = mfc_new((mfc_config){k, count, 3000, 7 + k}, (mfp_persist){0}, 1);
50    for (uint32_t a = 0; a < count; a++)
51        for (uint32_t b = 0; b < count; b++)
52            if (a != b) out(n, a, mfc_link(n->c[a], b, true, 1));
53}
54
55static void net_free(net_t *n) {
56    for (uint32_t k = 0; k < n->n; k++) mfc_free(n->c[k]);
57    for (size_t i = n->qh; i < n->qn; i++) mfc_msg_free(&n->q[i].m);
58    free(n->q);
59}
60
61typedef bool (*keep_fn)(uint32_t from, uint32_t to, const mfc_msg *m);
62
63static bool all_keep(uint32_t f, uint32_t t, const mfc_msg *m) { return (void)f, (void)t, (void)m, true; }
64static bool none_keep(uint32_t f, uint32_t t, const mfc_msg *m) { return (void)f, (void)t, (void)m, false; }
65
66/* Delivers everything queued; keep false drops it. */
67static void run(net_t *n, keep_fn keep) {
68    while (n->qh < n->qn) {
69        qitem it = n->q[n->qh++];
70        if (n->down[it.from] || n->down[it.to] || !keep(it.from, it.to, &it.m)) {
71            mfc_msg_free(&it.m);
72            continue;
73        }
74        out(n, it.to, mfc_recv(n->c[it.to], it.from, it.m, n->now));
75    }
76}
77
78static void crash(net_t *n, uint32_t k) {
79    n->down[k] = true;
80    for (uint32_t j = 0; j < n->n; j++)
81        if (j != k && !n->down[j]) out(n, j, mfc_link(n->c[j], k, false, n->now));
82}
83
84static void elect(net_t *n, uint32_t k) {
85    n->now += 5000;
86    out(n, k, mfc_tick(n->c[k], n->now));
87    run(n, all_keep);
88    CHECK(mfc_role_of(n->c[k]) == MFC_LEADER);
89}
90
91static void beat(net_t *n, uint32_t k, uint32_t to) { enqueue(n, k, to, mfc_beat(n->c[k])); }
92
93static uint64_t propose(net_t *n, uint32_t k, const char *tag) {
94    mfd_doc d = mfd_doc_clone(mfc_committed(n->c[k]));
95    mt_strs w = {0};
96    mt_push(&w, "local");
97    mfd_place_put(&d, tag, w);
98    uint64_t v = 0;
99    mfc_outs o;
100    char *err = NULL;
101    CHECK(mfc_propose(n->c[k], d, n->now, &v, &o, &err));
102    out(n, k, o);
103    return v;
104}
105
106/* peer.rs `lead`: the term's first document (the accepted one re-stamped). */
107static uint64_t first(net_t *n, uint32_t k) {
108    CHECK(!mfc_term_committed(n->c[k]));
109    mfd_doc d = {0};
110    mfc_first_doc(n->c[k], &d);
111    uint64_t v = 0;
112    mfc_outs o;
113    char *err = NULL;
114    CHECK(mfc_propose(n->c[k], d, n->now, &v, &o, &err));
115    out(n, k, o);
116    return v;
117}
118
119static bool not_commit_0_to_1(uint32_t f, uint32_t t, const mfc_msg *m) { return !(f == 0 && t == 1 && m->kind == MFC_COMMIT); }
120static bool not_to_2(uint32_t f, uint32_t t, const mfc_msg *m) { return (void)f, (void)m, t != 2; }
121static bool n5_filter(uint32_t f, uint32_t t, const mfc_msg *m) {
122    return !(f == 0 && (t == 3 || t == 4) && m->kind == MFC_STATE) && !(f == 0 && t != 2 && m->kind == MFC_COMMIT);
123}
124
125/* N = 3: a new leader whose committed document is behind must not catch a follower "up" to it. */
126/* fleet.rs catch_up_never_regresses_a_committed_document */
127static void never_regresses(void) {
128    net_t n;
129    net_init(&n, 3);
130    elect(&n, 0);
131    first(&n, 0);
132    run(&n, all_keep);
133    propose(&n, 0, "a");
134    run(&n, all_keep);
135    uint64_t v2 = propose(&n, 0, "x");
136    run(&n, not_commit_0_to_1);
137    CHECK(mfc_committed(n.c[0])->version == v2 && mfc_committed(n.c[2])->version == v2 && mfc_committed(n.c[1])->version == v2 - 1);
138    crash(&n, 0);
139    elect(&n, 1);
140    beat(&n, 2, 1);
141    run(&n, all_keep);
142    beat(&n, 1, 2);
143    run(&n, all_keep);
144    CHECK_U(mfc_committed(n.c[2])->version, v2);
145    CHECK(mfd_place_get(mfc_committed(n.c[2]), "x") != NULL);
146    uint64_t v = first(&n, 1);
147    run(&n, all_keep);
148    for (uint32_t k = 1; k < 3; k++) CHECK(mfc_committed(n.c[k])->version == v && mfd_place_get(mfc_committed(n.c[k]), "x"));
149    net_free(&n);
150}
151
152/* A follower that missed a commit while away catches up even past an abandoned proposal. */
153/* fleet.rs a_follower_catches_up_past_an_abandoned_proposal */
154static void past_abandoned(void) {
155    net_t n;
156    net_init(&n, 3);
157    elect(&n, 0);
158    first(&n, 0);
159    run(&n, all_keep);
160    uint64_t v2 = propose(&n, 0, "a");
161    run(&n, not_to_2);
162    CHECK(mfc_committed(n.c[0])->version == v2 && mfc_committed(n.c[2])->version == v2 - 1);
163    propose(&n, 0, "b");
164    run(&n, none_keep);
165    n.now += 4000;
166    for (uint32_t k = 1; k < 3; k++) out(&n, 0, mfc_link(n.c[0], k, true, n.now));
167    out(&n, 0, mfc_tick(n.c[0], n.now));
168    CHECK(!mfc_busy(n.c[0]) && mfc_accepted(n.c[0])->version == v2 + 1 && mfc_committed(n.c[0])->version == v2);
169    for (int i = 0; i < 3; i++) {
170        n.now += 1100;
171        beat(&n, 2, 0);
172        run(&n, all_keep);
173        beat(&n, 0, 2);
174        run(&n, all_keep);
175    }
176    CHECK_U(mfc_committed(n.c[2])->version, v2);
177    net_free(&n);
178}
179
180/* N = 5: version 2 (x) committed on {0, 1, 2}; two leader changes lose nothing. */
181/* fleet.rs a_committed_version_survives_two_leader_changes_n5 */
182static void survives_two_changes(void) {
183    net_t n;
184    net_init(&n, 5);
185    elect(&n, 0);
186    first(&n, 0);
187    run(&n, all_keep);
188    propose(&n, 0, "a");
189    run(&n, all_keep);
190    uint64_t v2 = propose(&n, 0, "x");
191    run(&n, n5_filter);
192    CHECK(mfd_place_get(mfc_committed(n.c[0]), "x") && mfc_committed(n.c[2])->version == v2);
193    crash(&n, 0);
194    elect(&n, 1);
195    beat(&n, 2, 1);
196    run(&n, all_keep);
197    beat(&n, 1, 2);
198    run(&n, all_keep);
199    crash(&n, 1);
200    elect(&n, 2);
201    uint64_t v = first(&n, 2);
202    run(&n, all_keep);
203    for (uint32_t k = 2; k < 5; k++) {
204        const mfd_doc *d = mfc_committed(n.c[k]);
205        CHECK(d->version == v && mfd_place_get(d, "x") && mfd_place_get(d, "a"));
206    }
207    net_free(&n);
208}
209
210static bool fail_persist(void *ctx, char **err) {
211    *err = mt_strdup((const char *)ctx);
212    return false;
213}
214
215static bool count_persist(void *ctx, char **err) {
216    (void)err;
217    (*(int *)ctx)++;
218    return true;
219}
220
221static bool has_send(const mfc_outs *o, mfc_msg_kind kind, int to, int granted) {
222    for (size_t i = 0; i < o->n; i++)
223        if (o->v[i].kind == MFC_OUT_SEND && o->v[i].msg.kind == kind && (to < 0 || o->v[i].to == (uint32_t)to) &&
224            (granted < 0 || o->v[i].msg.granted == (bool)granted))
225            return true;
226    return false;
227}
228
229/* Review finding 4: a vote (or an acknowledgement) whose persist failed is never sent. */
230/* fleet.rs nothing_leaves_without_its_persist */
231static void nothing_leaves_without_persist(void) {
232    mfc_core *c = mfc_new((mfc_config){1, 3, 3000, 3}, (mfp_persist){0}, 1);
233    mfc_outs o = mfc_link(c, 0, true, 1);
234    mfc_outs_free(&o);
235    o = mfc_link(c, 2, true, 1);
236    mfc_outs_free(&o);
237    o = mfc_recv(c, 0, (mfc_msg){.kind = MFC_VOTE_REQ, .pre = false, .term = 5}, 10000);
238    CHECK(has_send(&o, MFC_VOTE, 0, 1));
239    char *err = NULL;
240    CHECK(!mfc_durable(&o, fail_persist, "ENOSPC", &err));
241    free(err);
242    CHECK(!has_send(&o, MFC_VOTE, -1, -1));
243    mfc_outs_free(&o);
244    mfc_free(c);
245    /* The disk holds what it held before the vote: no vote in term 5. */
246    c = mfc_new((mfc_config){1, 3, 3000, 3}, (mfp_persist){0}, 1);
247    o = mfc_link(c, 0, true, 1);
248    mfc_outs_free(&o);
249    o = mfc_link(c, 2, true, 1);
250    mfc_outs_free(&o);
251    o = mfc_recv(c, 2, (mfc_msg){.kind = MFC_VOTE_REQ, .pre = false, .term = 5}, 10000);
252    CHECK(has_send(&o, MFC_VOTE, 2, 1));
253    mfc_outs_free(&o);
254    /* An accepted document's acknowledgement likewise. */
255    mfd_doc d = {.version = 1, .term = 5};
256    mfd_place_put(&d, "x", (mt_strs){0});
257    o = mfc_recv(c, 2, (mfc_msg){.kind = MFC_STATE, .term = 5, .doc = d}, 10001);
258    CHECK(!mfc_durable(&o, fail_persist, "EIO", &err));
259    free(err);
260    CHECK(!has_send(&o, MFC_STATE_ACK, -1, -1));
261    mfc_outs_free(&o);
262    /* A disk that works: the persist precedes the sends, which all come out. */
263    o = mfc_recv(c, 2, (mfc_msg){.kind = MFC_COMMIT, .term = 5, .ver = {5, 1}}, 10002);
264    size_t want = 0, persists = 0;
265    for (size_t i = 0; i < o.n; i++) {
266        if (o.v[i].kind == MFC_OUT_PERSIST) persists++;
267        else want++;
268    }
269    int wrote = 0;
270    CHECK(mfc_durable(&o, count_persist, &wrote, &err));
271    CHECK(o.n == want && (size_t)wrote == persists && persists > 0);
272    mfc_outs_free(&o);
273    mfc_free(c);
274}
275
276int main(void) {
277    never_regresses();
278    past_abandoned();
279    survives_two_changes();
280    nothing_leaves_without_persist();
281    return mesh_check_done();
282}