skip to content
==> src/lead.c <==
1/* jobs.rs's Node, leading: step_down, start_lead, activate, flush, snapshots, commit_check. */
2#include "internal.h"
3#include "mesh/jobs/lid.h"
4
5#include <stdlib.h>
6#include <string.h>
7
8mj_lead *mj_lead_new(uint32_t n);
9void mj_lead_free(mj_lead *l);
10void mj_lead_activate(mj_node *n, uint64_t now, uint64_t unix_ms, mj_outs *out);
11
12static unsigned ones(uint32_t x) {
13    unsigned c = 0;
14    for (; x; x &= x - 1) c++;
15    return c;
16}
17
18mj_outs mj_node_step_down(mj_node *n) {
19    mj_outs out = {0};
20    mj_lead *l = n->lead;
21    if (!l) return out;
22    n->lead = NULL;
23    for (size_t i = 0; i < l->nwaits; i++)
24        mj_outs_push(&out, mj_out_answer(l->waits[i].token, false,
25                                         mstrdup("jobs: the leader changed before the change was committed; `jobs` shows whether it was")));
26    for (size_t i = 0; i < l->nheld; i++)
27        mj_outs_push(&out, mj_out_answer(l->held[i].token, false,
28                                         mstrdup("jobs: the leader changed before the submit found a member running its program; submit again")));
29    /* Its entries not committed yet stay pending in the replica (a majority may have them: the next leader's pull). */
30    mj_lead_free(l);
31    return out;
32}
33
34void mj_node_start_lead(mj_node *n, uint64_t term, uint64_t now, uint64_t unix_ms, mj_outs *out) {
35    uint32_t me = n->cfg.me;
36    uint64_t lid;
37    if (!mj_lid_next(term, n->rep.lid, &lid)) {
38        if (n->behind != term) {
39            n->behind = term;
40            mj_outs_push(out, mj_out_log(mfmt("jobs: system %u leads fleet term %llu but knows job leadership %llx of term %llu: waiting for the fleet's view", me,
41                                              (unsigned long long)term, (unsigned long long)n->rep.lid, (unsigned long long)mj_lid_term(n->rep.lid))));
42        }
43        return;
44    }
45    /* Persisted before any frame of it leaves (a restart leads above it). */
46    mj_node_log(n, mj_log_lid(lid), out);
47    n->has_lid_from = true;
48    n->lid_from = me;
49    n->next = 0;
50    n->has_staging = false;
51    mj_outs_push(out, mj_out_log(mfmt("jobs: system %u leads the job table (term %llu, leadership %llx); pulling the members' tables", me,
52                                      (unsigned long long)term, (unsigned long long)lid)));
53    mj_lead *l = mj_lead_new(n->cfg.n);
54    l->lid = lid;
55    l->term = term;
56    l->responded = (uint32_t)1 << me;
57    l->answers[me] = mxalloc(sizeof(mj_answer));
58    *l->answers[me] = mj_answer_of(&n->rep);
59    uint64_t k;
60    momap_iter cur2_;
61    momap_iter_init(&cur2_, n->held);
62    for (const mj_held *h = momap_iter_next1(&cur2_, &k); h; h = momap_iter_next1(&cur2_, &k)) {
63        if (l->nadopt == l->capadopt) {
64            l->capadopt = l->capadopt ? 2 * l->capadopt : 8;
65            l->adopt = mxrealloc(l->adopt, l->capadopt * sizeof *l->adopt);
66        }
67        l->adopt[l->nadopt++] = (mj_adopt){me, mj_held_clone(h)};
68    }
69    l->pull_at = now;
70    n->lead = l;
71    uint32_t linked = n->view.linked & ~((uint32_t)1 << me);
72    for (uint32_t m = 0; m < 32; m++)
73        if (linked >> m & 1) mj_outs_push(out, mj_out_send(m, mj_frame_pull(lid)));
74    if (n->cfg.quorum <= 1) mj_lead_activate(n, now, unix_ms, out);
75}
76
77/* A majority's tables are merged: commit the merge as this leadership's first entry, rebuild the
78 * queues, adopt the leases. */
79void mj_lead_activate(mj_node *n, uint64_t now, uint64_t unix_ms, mj_outs *out) {
80    (void)unix_ms;
81    uint32_t me = n->cfg.me;
82    uint64_t dead = n->cfg.dead_ms;
83    uint32_t linked = n->view.linked & ~((uint32_t)1 << me);
84    mj_lead *l = n->lead;
85    uint64_t lid = l->lid;
86    uint32_t responded = l->responded;
87    mj_answer *in[MJ_MEMBERS];
88    size_t nin = 0;
89    for (int k = 0; k < MJ_MEMBERS; k++)
90        if (l->answers[k]) in[nin++] = l->answers[k];
91    mj_table working = mj_merge(in, nin);
92    mj_ops ops = {0};
93    mj_table_ops(&working, &ops);
94    /* The first entry: the merge (a replace: the entries of the leaderships before it are in it). */
95    mj_node_log(n, mj_log_entry(lid, 1, true, mj_ops_clone(&ops)), out);
96    n->next = 2;
97    l->active = true;
98    for (int k = 0; k < MJ_MEMBERS; k++)
99        if (l->answers[k]) {
100            mj_answer_free(l->answers[k]);
101            free(l->answers[k]);
102            l->answers[k] = NULL;
103        }
104    mj_table_free(&l->working);
105    l->working = working;
106    l->n = 1;
107    l->acked[me] = 1;
108    mj_op_parts ps = mj_op_parts_of(&ops);
109    for (size_t i = 0; i < ps.n; i++)
110        for (uint32_t k = 0; k < 32; k++)
111            if (linked >> k & 1) mj_outs_push(out, mj_out_send(k, mj_frame_state(lid, MESH_JOBS_ST_PART, 1, 0, (uint32_t)i, mj_ops_clone(&ps.v[i]))));
112    for (uint32_t k = 0; k < 32; k++)
113        if (linked >> k & 1) mj_outs_push(out, mj_out_send(k, mj_frame_state(lid, MESH_JOBS_ST_REPLACE_END, 1, 0, (uint32_t)ps.n, (mj_ops){0})));
114    mj_op_parts_free(&ps);
115    mj_ops_free(&ops);
116    /* The queues: every active job's items left, but those in adopted leases. */
117    momap_clear(l->disp.queues);
118    uint64_t id;
119    for (const mj_job *j = mj_table_first(&l->working, &id); j; j = mj_table_next(&l->working, &id))
120        if (mj_job_active(j)) {
121            mj_queue *q = momap_put1(l->disp.queues, id, NULL);
122            *q = (mj_queue){mj_job_left(j), {0}};
123        }
124    uint64_t adopted = 0, top = 0;
125    for (size_t i = 0; i < l->nadopt; i++) {
126        uint32_t k = l->adopt[i].member;
127        mj_held *h = &l->adopt[i].h;
128        if (h->lease > top) top = h->lease;
129        mj_queue *qq = momap_get1(l->disp.queues, h->job);
130        if (!qq) continue;
131        mj_ranges items = mj_ranges_intersect(&h->items, &qq->fresh);
132        if (mj_ranges_is_empty(&items)) {
133            mj_ranges_free(&items);
134            continue;
135        }
136        mj_ranges_subtract(&qq->fresh, &items);
137        mj_ranges in2 = mj_ranges_intersect(&items, &mj_table_get(&l->working, h->job)->spec.items);
138        mj_ranges_free(&items);
139        uint64_t size = mj_ranges_len(&in2);
140        adopted += size;
141        bool fresh;
142        mj_lease *x = momap_put1(l->disp.leases, h->lease, &fresh);
143        if (!fresh) {
144            free(x->program);
145            mj_ranges_free(&x->out);
146        }
147        *x = (mj_lease){h->lease, h->job, k, mstrdup(h->program), in2, size, now, now + dead + 10000};
148    }
149    for (size_t i = 0; i < l->nadopt; i++) mj_held_free(&l->adopt[i].h);
150    l->nadopt = 0;
151    /* Lease ids of this leadership, past any a member holds (review finding 10: never the wall clock's). */
152    uint64_t base = mj_lease_base(lid);
153    l->disp.next_lease = base > top ? base : top;
154    size_t jobs = 0, active = 0;
155    for (const mj_job *j = mj_table_first(&l->working, &id); j; j = mj_table_next(&l->working, &id)) {
156        jobs++;
157        active += mj_job_active(j);
158    }
159    mj_outs_push(out, mj_out_log(mfmt("jobs: system %u leads the job table: %u answer(s) merged, %zu job(s) (%zu active), %zu lease(s) of %llu item(s) adopted",
160                                      me, ones(responded), jobs, active, momap_len(l->disp.leases), (unsigned long long)adopted)));
161    mj_node_commit_check(n, out);
162}
163
164/* This tick's ops as one delta (persisted, sent to every member that is in step). */
165void mj_node_flush(mj_node *n, mj_outs *out) {
166    uint32_t me = n->cfg.me;
167    uint32_t linked = n->view.linked & ~((uint32_t)1 << me);
168    mj_lead *l = n->lead;
169    if (!l || !l->active) return;
170    /* Dupes counted this tick: one op per job. */
171    uint64_t job;
172    momap_iter cur3_;
173    momap_iter_init(&cur3_, l->dupes);
174    for (const uint64_t *cnt = momap_iter_next1(&cur3_, &job); cnt; cnt = momap_iter_next1(&cur3_, &job)) {
175        mj_job *j = mj_table_get(&l->working, job);
176        if (!j) continue;
177        uint64_t total = j->dupes > *cnt ? j->dupes : *cnt;
178        if (total > j->dupes) {
179            mj_op op = mj_op_dupes(job, total);
180            mj_table_apply(&l->working, &op);
181            mj_ops_push(&l->ops, op);
182        }
183    }
184    if (!l->ops.n) return;
185    mj_ops ops = l->ops;
186    l->ops = (mj_ops){0};
187    uint64_t lid = l->lid;
188    mj_op_parts ps = mj_op_parts_of(&ops);
189    mj_ops_free(&ops);
190    for (size_t i = 0; i < ps.n; i++) {
191        l->n++;
192        uint64_t nn = l->n, commit = l->commit;
193        uint32_t due = l->snap_due;
194        l->acked[me] = nn;
195        mj_node_log(n, mj_log_entry(lid, nn, false, mj_ops_clone(&ps.v[i])), out);
196        n->next = nn + 1;
197        uint32_t to = linked & ~due;
198        for (uint32_t k = 0; k < 32; k++)
199            if (to >> k & 1) mj_outs_push(out, mj_out_send(k, mj_frame_state(lid, MESH_JOBS_ST_DELTA, nn, commit, 0, mj_ops_clone(&ps.v[i]))));
200    }
201    mj_op_parts_free(&ps);
202}
203
204typedef struct {
205    mj_frame *v;
206    size_t n;
207} frames;
208
209static void push_frame(frames *f, mj_frame x) {
210    f->v = mxrealloc(f->v, (f->n + 1) * sizeof *f->v);
211    f->v[f->n++] = x;
212}
213
214static void table_parts(frames *f, uint64_t lid, uint64_t nn, uint64_t commit, const mj_table *t, uint32_t end_kind) {
215    mj_ops ops = {0};
216    mj_table_ops(t, &ops);
217    mj_op_parts ps = mj_op_parts_of(&ops);
218    mj_ops_free(&ops);
219    for (size_t i = 0; i < ps.n; i++) {
220        push_frame(f, mj_frame_state(lid, MESH_JOBS_ST_PART, nn, commit, (uint32_t)i, ps.v[i]));
221        ps.v[i] = (mj_ops){0};
222    }
223    push_frame(f, mj_frame_state(lid, end_kind, nn, commit, (uint32_t)ps.n, (mj_ops){0}));
224    mj_op_parts_free(&ps);
225}
226
227/* Members that need the committed table, then the entries after it. */
228void mj_node_snapshots(mj_node *n, mj_outs *out) {
229    uint32_t me = n->cfg.me;
230    uint32_t linked = n->view.linked & ~((uint32_t)1 << me);
231    mj_lead *l = n->lead;
232    if (!l) return;
233    uint32_t due = l->snap_due & linked;
234    if (!due) return;
235    l->snap_due &= ~due;
236    uint64_t lid = l->lid, commit = l->commit;
237    frames f = {0};
238    /* The committed table only once this leadership committed its first entry (the merge): before, this
239     * leader's committed table is its own from before it led, maybe behind the member's, which a snapshot
240     * would replace. Until then the member gets the entries alone (the merge first). */
241    if (commit > 0) table_parts(&f, lid, commit, commit, &n->rep.committed, MESH_JOBS_ST_SNAP_END);
242    size_t entries = 0;
243    for (size_t i = 0; i < n->rep.npending; i++) {
244        const mj_entry *e = &n->rep.pending[i];
245        if (e->lid != lid) continue;
246        entries++;
247        if (e->replace) table_parts(&f, lid, e->n, commit, &e->table, MESH_JOBS_ST_REPLACE_END);
248        else push_frame(&f, mj_frame_state(lid, MESH_JOBS_ST_DELTA, e->n, commit, 0, mj_ops_clone(&e->ops)));
249    }
250    for (uint32_t k = 0; k < 32; k++) {
251        if (!(due >> k & 1)) continue;
252        mj_outs_push(out, mj_out_log(mfmt("jobs: system %u gets the job table (commit %llu, %zu entries after it)", k, (unsigned long long)commit, entries)));
253        for (size_t i = 0; i < f.n; i++) mj_outs_push(out, mj_out_send(k, mj_frame_clone(&f.v[i])));
254    }
255    for (size_t i = 0; i < f.n; i++) mj_frame_free(&f.v[i]);
256    free(f.v);
257}
258
259static int desc(const void *a, const void *b) {
260    uint64_t x = *(const uint64_t *)a, y = *(const uint64_t *)b;
261    return x < y ? 1 : x > y ? -1 : 0;
262}
263
264/* The commit point: the highest entry a majority persisted. */
265void mj_node_commit_check(mj_node *n, mj_outs *out) {
266    uint32_t me = n->cfg.me;
267    uint32_t linked = n->view.linked & ~((uint32_t)1 << me);
268    uint32_t q = n->cfg.quorum > 1 ? n->cfg.quorum : 1;
269    mj_lead *l = n->lead;
270    if (!l || !l->active) return;
271    uint64_t *a = mxalloc(n->cfg.n * sizeof *a);
272    memcpy(a, l->acked, n->cfg.n * sizeof *a);
273    qsort(a, n->cfg.n, sizeof *a, desc);
274    uint64_t c = a[q - 1];
275    free(a);
276    if (c <= l->commit) return;
277    l->commit = c;
278    uint64_t lid = l->lid;
279    mj_wait *done = NULL;
280    size_t ndone = 0, keep = 0;
281    for (size_t i = 0; i < l->nwaits; i++) {
282        if (l->waits[i].n <= c) {
283            done = mxrealloc(done, (ndone + 1) * sizeof *done);
284            done[ndone++] = l->waits[i];
285        } else l->waits[keep++] = l->waits[i];
286    }
287    l->nwaits = keep;
288    uint32_t due = l->snap_due;
289    mj_node_log(n, mj_log_commit(lid, c), out);
290    for (size_t i = 0; i < ndone; i++) mj_outs_push(out, mj_out_answer(done[i].token, done[i].ok, done[i].text));
291    free(done);
292    uint32_t to = linked & ~due;
293    for (uint32_t k = 0; k < 32; k++)
294        if (to >> k & 1) mj_outs_push(out, mj_out_send(k, mj_frame_state(lid, MESH_JOBS_ST_COMMIT, c, c, 0, (mj_ops){0})));
295}