==> src/frames.c <==1/* jobs.rs's Node: frames (on_frame, answer, follow, gap). */
2#include "internal.h"
3#include "mesh/abi/peer_frames.h"
4
5#include <stdlib.h>
6#include <string.h>
7
8mj_lead *mj_lead_new(uint32_t n);
9void mj_lead_activate(mj_node *n, uint64_t now, uint64_t unix_ms, mj_outs *out);
10
11mj_op_parts mj_op_parts_of(const mj_ops *ops) {
12 mj_parts p = mj_parts_of(ops->v, ops->n);
13 mj_op_parts r = {mxalloc(p.n * sizeof *r.v), p.n};
14 for (size_t i = 0; i < p.n; i++)
15 if (!mj_decode_ops(p.v[i].p, p.v[i].n, &r.v[i], NULL)) r.v[i] = (mj_ops){0};
16 mj_parts_free(&p);
17 return r;
18}
19
20void mj_op_parts_free(mj_op_parts *p) {
21 for (size_t i = 0; i < p->n; i++) mj_ops_free(&p->v[i]);
22 free(p->v);
23 *p = (mj_op_parts){0};
24}
25
26/* Whether frames of leadership `lid` from `from` are to be followed (a newer one is adopted, and persisted). */
27static bool follow(mj_node *n, uint32_t from, uint64_t lid, mj_outs *out) {
28 if (lid < n->rep.lid) return false;
29 if (lid > n->rep.lid) {
30 if (from != n->cfg.me && n->lead) {
31 mj_outs o = mj_node_step_down(n);
32 mj_outs_append(out, &o);
33 }
34 mj_node_log(n, mj_log_lid(lid), out);
35 n->has_lid_from = true;
36 n->lid_from = from;
37 n->next = 0;
38 n->has_staging = false;
39 return true;
40 }
41 /* The leadership this member followed before a restart: its leader is whoever speaks for it. */
42 if (!n->has_lid_from) {
43 n->has_lid_from = true;
44 n->lid_from = from;
45 n->next = 0;
46 n->has_staging = false;
47 }
48 return n->lid_from == from;
49}
50
51static void gap(mj_node *n, uint32_t from, uint64_t lid, uint64_t now, mj_outs *out) {
52 if ((now > n->gap_at ? now - n->gap_at : 0) < 500) return;
53 n->gap_at = now;
54 mj_node_send(n, from, mj_frame_ack(lid, n->next ? n->next - 1 : 0, true), out);
55}
56
57static mj_held *held_now(const mj_node *n, size_t *count) {
58 size_t nh = momap_len(n->held), i = 0;
59 mj_held *v = nh ? mxalloc(nh * sizeof *v) : NULL;
60 uint64_t k;
61 momap_iter cur1_;
62 momap_iter_init(&cur1_, n->held);
63 for (const mj_held *h = momap_iter_next1(&cur1_, &k); h; h = momap_iter_next1(&cur1_, &k)) v[i++] = mj_held_clone(h);
64 *count = nh;
65 return v;
66}
67
68typedef struct {
69 mj_frame *v;
70 size_t n;
71} frames;
72
73static void put_parts(frames *f, uint64_t lid, uint32_t kind, uint64_t s0, uint64_t s1, const mj_ops *ops, uint32_t *seq) {
74 mj_op_parts p = mj_op_parts_of(ops);
75 for (size_t i = 0; i < p.n; i++) {
76 f->v = mxrealloc(f->v, (f->n + 1) * sizeof *f->v);
77 f->v[f->n++] = mj_frame_have(lid, kind, (*seq)++, s0, s1, p.v[i], NULL, 0);
78 p.v[i] = (mj_ops){0};
79 }
80 mj_op_parts_free(&p);
81}
82
83void mj_node_answer(const mj_node *n, uint64_t lid, mj_frame **out, size_t *count) {
84 frames f = {0};
85 uint32_t seq = 0;
86 mj_ops committed = {0};
87 mj_table_ops(&n->rep.committed, &committed);
88 put_parts(&f, lid, MESH_JOBS_HAVE_COMMITTED, n->rep.commit_lid, n->rep.commit_n, &committed, &seq);
89 mj_ops_free(&committed);
90 /* The entries of one leadership as one stream (the merge takes them in any order within it). */
91 size_t i = 0;
92 while (i < n->rep.npending) {
93 uint64_t l = n->rep.pending[i].lid, last = 0;
94 mj_ops ops = {0};
95 while (i < n->rep.npending && n->rep.pending[i].lid == l) {
96 const mj_entry *e = &n->rep.pending[i];
97 last = e->n;
98 if (e->replace) mj_table_ops(&e->table, &ops);
99 else
100 for (size_t k = 0; k < e->ops.n; k++) mj_ops_push(&ops, mj_op_clone(&e->ops.v[k]));
101 i++;
102 }
103 put_parts(&f, lid, MESH_JOBS_HAVE_DELTA, l, last, &ops, &seq);
104 mj_ops_free(&ops);
105 }
106 size_t nh;
107 mj_held *held = held_now(n, &nh);
108 uint32_t total = (uint32_t)f.n;
109 f.v = mxrealloc(f.v, (f.n + 1) * sizeof *f.v);
110 f.v[f.n++] = mj_frame_have(lid, MESH_JOBS_HAVE_END, total, 0, 0, (mj_ops){0}, held, nh);
111 *out = f.v;
112 *count = f.n;
113}
114
115static void on_have(mj_node *n, uint32_t from, mj_frame *f, uint64_t now, uint64_t unix_ms, mj_outs *out) {
116 uint32_t quorum = n->cfg.quorum;
117 mj_lead *l = n->lead;
118 if (!l || l->lid != f->lid) return;
119 if (l->active) {
120 /* A late answer: its results are welcome (the table is a union, and the working table holds every
121 * committed entry already), and so are its leases for the items still queued (not leased again). */
122 for (size_t i = 0; i < f->ops.n; i++) {
123 mj_table_apply(&l->working, &f->ops.v[i]);
124 mj_ops_push(&l->ops, f->ops.v[i]);
125 f->ops.v[i] = (mj_op){0};
126 }
127 uint64_t dead = n->cfg.dead_ms;
128 for (size_t i = 0; i < f->nheld; i++) {
129 mj_held *h = &f->held[i];
130 if (momap_get1(l->disp.leases, h->lease)) continue;
131 mj_queue *q = momap_get1(l->disp.queues, h->job);
132 if (!q) continue;
133 mj_ranges items = mj_ranges_intersect(&h->items, &q->fresh), r2 = mj_ranges_intersect(&h->items, &q->retry);
134 mj_ranges_union(&items, &r2);
135 mj_ranges_free(&r2);
136 if (mj_ranges_is_empty(&items)) {
137 mj_ranges_free(&items);
138 continue;
139 }
140 mj_ranges_subtract(&q->fresh, &items);
141 mj_ranges_subtract(&q->retry, &items);
142 uint64_t cnt = mj_ranges_len(&items);
143 mj_outs_push(out, mj_out_log(mfmt("jobs: system %u's late answer: lease %llx of %llu item(s) adopted", from,
144 (unsigned long long)h->lease, (unsigned long long)cnt)));
145 if (h->lease > l->disp.next_lease) l->disp.next_lease = h->lease;
146 bool fresh;
147 mj_lease *x = momap_put1(l->disp.leases, h->lease, &fresh);
148 *x = (mj_lease){h->lease, h->job, from, mstrdup(h->program), items, cnt, now, now + dead + 10000};
149 }
150 return;
151 }
152 if (l->responded >> from & 1) return;
153 l->heard_mask |= (uint32_t)1 << from;
154 l->heard[from] = now;
155 /* A part numbered 0 starts an answer (a repeated pull's answer replaces an earlier one). */
156 if (f->seq == 0 && f->kind != MESH_JOBS_HAVE_END && l->answers[from]) {
157 mj_answer_free(l->answers[from]);
158 free(l->answers[from]);
159 l->answers[from] = NULL;
160 }
161 if (!l->answers[from]) {
162 l->answers[from] = mxalloc(sizeof(mj_answer));
163 mj_answer_init(l->answers[from]);
164 }
165 mj_answer *a = l->answers[from];
166 if (f->kind == MESH_JOBS_HAVE_END) {
167 if (a->spoilt || a->seq != f->seq) {
168 mj_outs_push(out, mj_out_log(mfmt("jobs: system %u's table arrived incomplete (%u of %u parts); asking again", from, a->seq, f->seq)));
169 mj_answer_free(a);
170 free(a);
171 l->answers[from] = NULL;
172 mj_outs_push(out, mj_out_send(from, mj_frame_pull(f->lid)));
173 return;
174 }
175 l->responded |= (uint32_t)1 << from;
176 for (size_t i = 0; i < f->nheld; i++) {
177 if (l->nadopt == l->capadopt) {
178 l->capadopt = l->capadopt ? 2 * l->capadopt : 8;
179 l->adopt = mxrealloc(l->adopt, l->capadopt * sizeof *l->adopt);
180 }
181 l->adopt[l->nadopt++] = (mj_adopt){from, f->held[i]};
182 f->held[i] = (mj_held){0};
183 }
184 uint32_t cnt = 0;
185 for (uint32_t k = 0; k < 32; k++) cnt += l->responded >> k & 1;
186 if (cnt >= quorum) mj_lead_activate(n, now, unix_ms, out);
187 return;
188 }
189 if (a->spoilt || a->seq != f->seq) {
190 a->spoilt = true;
191 return;
192 }
193 a->seq++;
194 if (f->kind == MESH_JOBS_HAVE_COMMITTED) {
195 a->commit_lid = f->src0;
196 a->commit_n = f->src1;
197 for (size_t i = 0; i < f->ops.n; i++) mj_table_apply(&a->committed, &f->ops.v[i]);
198 } else {
199 mj_ops *e = mj_answer_entry(a, f->src0, f->src1);
200 mj_ops_append(e, &f->ops);
201 }
202}
203
204static void on_state(mj_node *n, uint32_t from, mj_frame *f, uint64_t now, mj_outs *out) {
205 uint64_t lid = f->lid;
206 switch (f->kind) {
207 case MESH_JOBS_ST_PART:
208 /* Numbered: part 0 starts the table; any other must follow the last (review finding 9). */
209 if (f->seq == 0) {
210 mj_table_free(&n->staging);
211 mj_table_init(&n->staging);
212 for (size_t i = 0; i < f->ops.n; i++) mj_table_apply(&n->staging, &f->ops.v[i]);
213 n->has_staging = true;
214 n->staging_n = f->n;
215 n->staging_seq = 1;
216 } else if (n->has_staging && n->staging_n == f->n && n->staging_seq == f->seq) {
217 for (size_t i = 0; i < f->ops.n; i++) mj_table_apply(&n->staging, &f->ops.v[i]);
218 n->staging_seq++;
219 } else n->has_staging = false;
220 break;
221 case MESH_JOBS_ST_SNAP_END:
222 case MESH_JOBS_ST_REPLACE_END: {
223 bool whole = n->has_staging && n->staging_n == f->n && n->staging_seq == f->seq;
224 n->has_staging = false;
225 if (whole && f->kind == MESH_JOBS_ST_SNAP_END && f->n == 0) {
226 /* A snapshot at n = 0 would be a leader's table from before it led: never installed. */
227 } else if (whole && f->kind == MESH_JOBS_ST_SNAP_END) {
228 /* The committed table at the leader's commit point n; the entries of this leadership past it that
229 * this member has stay (Replica::apply). */
230 mj_ops ops = {0};
231 mj_table_ops(&n->staging, &ops);
232 mj_node_log(n, mj_log_snapshot(lid, f->n, ops), out);
233 uint64_t last = mj_replica_last(&n->rep, lid);
234 n->next = (last > f->n ? last : f->n) + 1;
235 mj_node_send(n, from, mj_frame_ack(lid, n->next - 1, false), out);
236 } else if (whole && f->kind == MESH_JOBS_ST_REPLACE_END) {
237 if (f->n == n->next || (n->next == 0 && f->n == 1)) {
238 mj_ops ops = {0};
239 mj_table_ops(&n->staging, &ops);
240 mj_node_log(n, mj_log_entry(lid, f->n, true, ops), out);
241 n->next = f->n + 1;
242 mj_node_send(n, from, mj_frame_ack(lid, f->n, false), out);
243 } else if (f->n > n->next) gap(n, from, lid, now, out);
244 } else {
245 /* A part was lost: the table is not installed (a gap: the leader sends it again). */
246 mj_outs_push(out, mj_out_log(mfmt("jobs: a table from system %u arrived incomplete; asking for it again", from)));
247 gap(n, from, lid, now, out);
248 }
249 break;
250 }
251 case MESH_JOBS_ST_DELTA:
252 if (f->n == n->next && f->n > 0) {
253 mj_node_log(n, mj_log_entry(lid, f->n, false, f->ops), out);
254 f->ops = (mj_ops){0};
255 n->next = f->n + 1;
256 mj_node_send(n, from, mj_frame_ack(lid, f->n, false), out);
257 } else if (f->n > n->next) gap(n, from, lid, now, out);
258 break;
259 case MESH_JOBS_ST_COMMIT:
260 /* A commit point past the entries here: some were lost on the way: a gap. */
261 if (f->commit >= (n->next > 1 ? n->next : 1)) gap(n, from, lid, now, out);
262 break;
263 default: break;
264 }
265 /* The leader's commit point: what it covers of this leadership's entries here is committed. */
266 uint64_t top = n->next ? n->next - 1 : 0;
267 uint64_t c = f->commit < top ? f->commit : top;
268 if (n->rep.npending && n->rep.pending[0].lid == lid && n->rep.pending[0].n <= c) mj_node_log(n, mj_log_commit(lid, c), out);
269}
270
271void mj_node_on_frame(mj_node *n, uint32_t from, mj_frame f, uint64_t now, uint64_t unix_ms, mj_outs *out) {
272 uint32_t me = n->cfg.me;
273 switch (f.ty) {
274 case MESH_F_JOB_AVAIL:
275 if (n->lead) {
276 mj_lead *l = n->lead;
277 if (!(from < MJ_MEMBERS && l->disp.progs.has[from] && mj_progs_eq(l->disp.progs.v[from], l->disp.progs.n[from], f.progs, f.nprogs))) {
278 msb b = {0};
279 if (!f.nprogs) msb_puts(&b, "no job program");
280 for (size_t i = 0; i < f.nprogs; i++) msb_printf(&b, "%s%s", i ? ", " : "", f.progs[i].name);
281 char *names = msb_take(&b);
282 mj_outs_push(out, mj_out_log(mfmt("jobs: system %u runs %s", from, names)));
283 free(names);
284 if (from < MJ_MEMBERS) {
285 mj_member_progs_set(&l->disp.progs, from, f.progs, f.nprogs);
286 f.progs = NULL;
287 f.nprogs = 0;
288 }
289 }
290 }
291 break;
292 case MESH_F_RESULT:
293 if (mj_node_active(n)) {
294 mj_node_on_results(n, from, f.items, f.nitems, now, unix_ms, out);
295 f.items = NULL;
296 f.nitems = 0;
297 }
298 break;
299 case MESH_F_JOB_ACK:
300 if (n->lead && n->lead->lid == f.lid && n->lead->active && from < n->cfg.n) {
301 mj_lead *l = n->lead;
302 /* A gap: it gets a snapshot; what it says it has is all it has (review finding 6). */
303 if (f.gap) {
304 l->snap_due |= (uint32_t)1 << from;
305 if (f.n < l->acked[from]) l->acked[from] = f.n;
306 } else if (f.n > l->acked[from]) l->acked[from] = f.n;
307 mj_node_commit_check(n, out);
308 }
309 break;
310 case MESH_F_JOB_HAVE: on_have(n, from, &f, now, unix_ms, out); break;
311 case MESH_F_JOB_PULL:
312 if (follow(n, from, f.lid, out)) {
313 mj_frame *v;
314 size_t cnt;
315 mj_node_answer(n, f.lid, &v, &cnt);
316 for (size_t i = 0; i < cnt; i++) mj_node_send(n, from, v[i], out);
317 free(v);
318 }
319 break;
320 case MESH_F_JOB_STATE:
321 if (from != me && follow(n, from, f.lid, out)) on_state(n, from, &f, now, out);
322 break;
323 case MESH_F_LEASE:
324 if (f.lid < n->rep.lid && from != me) break;
325 {
326 bool fresh;
327 mj_held *h = momap_put1(n->held, f.l.lease, &fresh);
328 if (!fresh) mj_held_free(h);
329 *h = (mj_held){f.l.lease, f.l.job, mstrdup(f.l.program), mj_ranges_clone(&f.l.items)};
330 n->avail_dirty = true;
331 mj_outs_push(out, mj_out_run(f.l));
332 f.l = (mj_lease_msg){0};
333 f.ty = 0;
334 }
335 break;
336 case MESH_F_LEASE_CUT:
337 if (f.lid < n->rep.lid && from != me) break;
338 {
339 mj_held *h = momap_get1(n->held, f.lease);
340 if (h) {
341 mj_ranges_remove(&h->items, f.cut, UINT64_MAX);
342 if (mj_ranges_is_empty(&h->items)) momap_del1(n->held, f.lease);
343 n->avail_dirty = true;
344 }
345 mj_outs_push(out, mj_out_cut(f.lease, f.cut));
346 }
347 break;
348 default: break;
349 }
350 mj_frame_free(&f);
351}