skip to content
==> src/placer.c <==
1/* place.rs Placer: one slot's placement state machine (see the header). */
2#include "mesh/placer.h"
3#include "mesh/abi/place_ops.h"
4#include "mesh/abi/placement.h"
5#include "mesh/rust_fmt.h"
6
7#include <stdio.h>
8#include <stdlib.h>
9#include <string.h>
10
11typedef enum { PAUSING, SETTING } phase_t;
12
13/* Who asked for a change: a local request, or nobody (a re-plan after the up members changed). */
14typedef struct {
15    bool local;
16    uint64_t handle;
17} asker_t;
18
19typedef struct {
20    uint32_t k;
21    mpw_bytes state;
22    uint32_t part_ns;
23} member_state;
24
25/* A change the leader runs among the up members. */
26typedef struct {
27    uint32_t id;
28    mpm_mode from;
29    mpm_target to;
30    mpm_mode mode; /* resolved at Setting */
31    phase_t phase;
32    member_state states[MESH_PLACE_PARTS_MAX];
33    size_t nstates;
34    uint32_t waiting, members;
35    uint64_t since;
36    asker_t asker;
37} change_t;
38
39struct mpl_placer {
40    char name[MESH_NAME_BYTES + 1];
41    mps_spec spec;
42    uint32_t me, n;
43    mpm_mode mode;
44    bool paused;
45    uint32_t part_ns;
46    bool has_coord;
47    uint32_t coord;
48    uint32_t up, members;
49    bool has_request;
50    mt_strs request;
51    mpc_composer composer;
52    bool has_follow_inflight;
53    uint32_t follow_inflight;
54    bool has_follow_next;
55    mpw_frame follow_next;
56    bool has_change;
57    change_t change;
58    bool has_pause_for;
59    uint32_t pause_id, pause_leader;
60    bool has_join_at;
61    uint64_t join_at;
62    uint32_t next_id;
63    mpl_stats stats;
64    void (*log)(void *ctx, const char *line);
65    void *log_ctx;
66};
67
68static uint64_t since_us(uint64_t now, uint64_t t) { return now > t ? now - t : 0; }
69
70static void push(mpl_acts *a, mpl_act x) {
71    if (a->n == a->cap) {
72        a->cap = a->cap ? a->cap * 2 : 8;
73        a->v = mt_realloc(a->v, a->cap * sizeof *a->v);
74    }
75    a->v[a->n++] = x;
76}
77
78void mpl_acts_free(mpl_acts *a) {
79    for (size_t i = 0; i < a->n; i++) {
80        mpw_bytes_free(&a->v[i].bytes);
81        free(a->v[i].text);
82    }
83    free(a->v);
84    *a = (mpl_acts){0};
85}
86
87static void act_log(mpl_acts *a, char *text) { push(a, (mpl_act){.kind = MPL_LOG, .text = text}); }
88
89static void change_free(change_t *c) {
90    for (size_t i = 0; i < c->nstates; i++) mpw_bytes_free(&c->states[i].state);
91    c->nstates = 0;
92}
93
94mpl_placer *mpl_new(const char *name, mps_spec spec, uint32_t me, uint32_t n) {
95    mpl_placer *p = mt_alloc(sizeof *p);
96    size_t k = strlen(name);
97    if (k > MESH_NAME_BYTES) k = MESH_NAME_BYTES;
98    memcpy(p->name, name, k);
99    p->spec = spec;
100    p->me = me;
101    p->n = n;
102    p->mode = mpm_local();
103    p->up = p->members = 1u << me;
104    p->next_id = 1;
105    return p;
106}
107
108void mpl_free(mpl_placer *p) {
109    if (!p) return;
110    mps_free(&p->spec);
111    mt_strs_free(&p->request);
112    mpc_clear(&p->composer);
113    if (p->has_follow_next) mpw_frame_free(&p->follow_next);
114    if (p->has_change) change_free(&p->change);
115    free(p);
116}
117
118void mpl_set_log(mpl_placer *p, void (*log)(void *ctx, const char *line), void *ctx) {
119    p->log = log;
120    p->log_ctx = ctx;
121}
122
123const mpm_mode *mpl_mode(const mpl_placer *p) { return &p->mode; }
124mpm_role mpl_role(const mpl_placer *p) { return mpm_role_of(&p->mode, p->me); }
125bool mpl_paused(const mpl_placer *p) { return p->paused; }
126bool mpl_busy(const mpl_placer *p) { return p->has_change || p->has_pause_for; }
127uint32_t mpl_members(const mpl_placer *p) { return p->members; }
128uint32_t mpl_my_parts(const mpl_placer *p) { return mpm_parts_of(&p->mode, p->me, p->spec.fanout); }
129const mpl_stats *mpl_stats_of(const mpl_placer *p) { return &p->stats; }
130void mpl_set_part_ns(mpl_placer *p, uint32_t ns) { p->part_ns = ns; }
131const mps_spec *mpl_spec(const mpl_placer *p) { return &p->spec; }
132
133/* The other up members. */
134static uint32_t others(const mpl_placer *p) { return p->up & mpm_bits(p->n) & ~(1u << p->me); }
135static bool leading(const mpl_placer *p) { return p->has_coord && p->coord == p->me; }
136/* The members a mode needs (pin k: k; split: those with parts). */
137static uint32_t involved(const mpm_mode *m) { return m->kind == MPM_LOCAL ? 0 : m->kind == MPM_PIN ? 1u << m->pin : mpm_members(&m->a); }
138
139bool mpl_parse(const mpl_placer *p, char *const *words, size_t n, mpm_target *out, char **err) { return mpm_parse(words, n, p->n, p->spec.fanout, out, err); }
140
141bool mpl_plan(const mpl_placer *p, const mt_strs *words, mpm_target *out, char **err) {
142    uint32_t up[MESH_PLACE_PARTS_MAX];
143    size_t nup = 0;
144    for (uint32_t k = 0; k < p->n; k++)
145        if (p->up >> k & 1) up[nup++] = k;
146    mpm_target t;
147    if (!mpl_parse(p, words->v, words->n, &t, err)) return false;
148    if (t.is_auto) t.auto_mask &= p->up;
149    else if (t.mode.kind == MPM_PIN && !(p->up >> t.mode.pin & 1)) t.mode = mpm_pin(p->me);
150    else if (t.mode.kind == MPM_SPLIT && !((mpm_members(&t.mode.a) & ~p->up) == 0 && words->n != 1)) {
151        /* `split` alone: every up member; a spec naming a member that is down: its up members. */
152        uint32_t named[MESH_PLACE_PARTS_MAX];
153        size_t nn = 0;
154        if (words->n == 1) memcpy(named, up, nup * sizeof *up), nn = nup;
155        else {
156            for (uint32_t b = 0; b < t.mode.a.fanout; b++) {
157                uint32_t k = t.mode.a.of[b];
158                bool have = false;
159                for (size_t i = 0; i < nn; i++) have |= named[i] == k;
160                if ((p->up >> k & 1) && !have) named[nn++] = k;
161            }
162            if (!nn) memcpy(named, up, nup * sizeof *up), nn = nup;
163        }
164        t.mode = mpm_split(mpm_even(named, nn, p->spec.fanout));
165    }
166    *out = t;
167    return true;
168}
169
170static void msg(const mpl_placer *p, mpl_acts *a, mpl_dest to, uint32_t op, uint32_t id, mpm_mode mode, uint32_t aux, const uint8_t *bytes, size_t n) {
171    mpw_place m = {.op = op, .id = id, .mode = mode, .aux = aux, .bytes = (uint8_t *)bytes, .nbytes = n};
172    memcpy(m.name, p->name, sizeof m.name);
173    push(a, (mpl_act){.kind = MPL_SEND, .dest = to, .bytes = mpw_place_encode(&m)});
174}
175
176static mpl_dest to_k(uint32_t k) { return (mpl_dest){false, k}; }
177static mpl_dest to_mask(uint32_t m) { return (mpl_dest){true, m}; }
178
179/* The mode changes here (resets the per-mode frame state). */
180static void set_mode(mpl_placer *p, mpm_mode m, mpl_acts *a) {
181    p->mode = m;
182    mpc_clear(&p->composer);
183    p->has_follow_inflight = false;
184    if (p->has_follow_next) mpw_frame_free(&p->follow_next);
185    p->has_follow_next = false;
186    push(a, (mpl_act){.kind = MPL_MODE, .mode = m});
187}
188
189/* Answers a request: its asker, or a log line for a re-plan. Takes text. */
190static void answer(const mpl_placer *p, asker_t who, bool ok, char *text, mpl_acts *a) {
191    if (who.local) push(a, (mpl_act){.kind = MPL_ANSWER, .handle = who.handle, .ok = ok, .text = text});
192    else {
193        act_log(a, mt_fmt("place %s: %s", p->name, text));
194        free(text);
195    }
196}
197
198/* The leader starts a change: pause every other up member, collect their state. */
199static void begin(mpl_placer *p, mpm_target to, asker_t asker, uint64_t now, bool force, mpl_acts *a) {
200    if (p->has_change) {
201        answer(p, asker, false, mt_fmt("%s: a placement change is in progress", p->name), a);
202        return;
203    }
204    mpm_target cur = {.mode = p->mode};
205    if (mpm_target_eq(&to, &cur) && p->members == p->up && !force) {
206        char *l = mpm_label(&p->mode);
207        answer(p, asker, true, mt_fmt("%s already", l), a);
208        free(l);
209        return;
210    }
211    uint32_t id = p->next_id++;
212    p->paused = true;
213    char *label = to.is_auto ? mt_strdup("split auto") : mpm_label(&to.mode);
214    uint32_t o = others(p);
215    p->has_change = true;
216    p->change = (change_t){.id = id, .from = p->mode, .to = to, .mode = p->mode, .phase = PAUSING, .waiting = o, .members = p->up, .since = now, .asker = asker};
217    char *from = mpm_label(&p->mode), hx[24];
218    act_log(a, mt_fmt("place %s: %s -> %s (change %u): pausing the up members %s", p->name, from, label, id, rf_hex_alt(hx, p->up)));
219    free(from), free(label);
220    if (o) msg(p, a, to_mask(o), MESH_PL_PAUSE, id, p->mode, 0, NULL, 0);
221}
222
223static member_state *state_of(change_t *c, uint32_t k) {
224    for (size_t i = 0; i < c->nstates; i++)
225        if (c->states[i].k == k) return &c->states[i];
226    return NULL;
227}
228
229/* Removes k's state from the change into *out (true), or false. */
230static bool take_state(change_t *c, uint32_t k, mpw_bytes *out) {
231    for (size_t i = 0; i < c->nstates; i++)
232        if (c->states[i].k == k) {
233            *out = c->states[i].state;
234            c->states[i] = c->states[--c->nstates];
235            return true;
236        }
237    return false;
238}
239
240/* The mode a change lands on (split auto: every member's time per part, ours included). */
241static mpm_mode resolve(const mpl_placer *p, change_t *c) {
242    if (!c->to.is_auto) return c->to.mode;
243    uint32_t ks[MESH_PLACE_PARTS_MAX], ns[MESH_PLACE_PARTS_MAX];
244    size_t k = 0;
245    for (uint32_t s = 0; s < p->n; s++)
246        if (c->to.auto_mask >> s & 1) {
247            member_state *st = state_of(c, s);
248            ks[k] = s;
249            ns[k++] = s == p->me ? p->part_ns : st ? st->part_ns : 0;
250        }
251    mpm_mode m = mpm_split(mpm_auto(ks, ns, k, p->spec.fanout));
252    if (p->log) {
253        mt_buf b = {0};
254        mt_printf(&b, "place %s: split auto: time per part ", p->name);
255        for (size_t i = 0; i < k; i++) mt_printf(&b, i ? ", system %u %.1f us" : "system %u %.1f us", ks[i], (double)ns[i] / 1e3);
256        char *l = mpm_label(&m);
257        mt_printf(&b, " -> %s", l);
258        free(l);
259        p->log(p->log_ctx, mt_str(&b));
260        mt_buf_free(&b);
261    }
262    return m;
263}
264
265/* The leader re-runs the committed request over the up members. */
266static void replan(mpl_placer *p, uint64_t now, mpl_acts *a) {
267    p->has_join_at = false;
268    mt_strs words = p->has_request ? mt_strs_clone(&p->request) : (mt_strs){0};
269    if (!p->has_request) mt_push(&words, "local");
270    mpm_target t;
271    char *err = NULL;
272    if (mpl_plan(p, &words, &t, &err)) begin(p, t, (asker_t){false, 0}, now, true, a);
273    else {
274        char *j = mt_join(&words, " ");
275        char *q = rf_debug_str(j);
276        act_log(a, mt_fmt("place %s: re-plan of %s: %s", p->name, q, err));
277        free(j), free(q), free(err);
278    }
279    mt_strs_free(&words);
280}
281
282mpl_acts mpl_request(mpl_placer *p, const mt_strs *words, uint64_t handle, uint64_t now) {
283    mpl_acts a = {0};
284    if (!leading(p)) {
285        char *who = p->has_coord ? mt_fmt("the leader, system %u", p->coord) : mt_strdup("a leader (there is none: no majority)");
286        push(&a, (mpl_act){.kind = MPL_ANSWER, .handle = handle, .text = mt_fmt("peer: placement changes are coordinated by %s", who)});
287        free(who);
288        return a;
289    }
290    mpm_target to;
291    char *err = NULL;
292    bool ok = mpl_plan(p, words, &to, &err);
293    if (ok) {
294        mpm_mode m = to.is_auto ? mpm_split(mpm_even((uint32_t[]){0}, 1, p->spec.fanout)) : to.mode;
295        ok = mpm_allowed(&m, p->spec.cap, &err) && (to.is_auto || mpm_fits(&m, p->n, p->spec.fanout, &err));
296    }
297    if (!ok) {
298        push(&a, (mpl_act){.kind = MPL_ANSWER, .handle = handle, .text = mt_fmt("%s: %s", p->name, err)});
299        free(err);
300        return a;
301    }
302    mt_strs_free(&p->request);
303    p->has_request = !(words->n == 1 && strcmp(words->v[0], "local") == 0);
304    if (p->has_request) p->request = mt_strs_clone(words);
305    begin(p, to, (asker_t){true, handle}, now, false, &a);
306    return a;
307}
308
309mpl_acts mpl_view(mpl_placer *p, uint32_t up, bool has_coord, uint32_t coord, const mt_strs *request, uint64_t now) {
310    mpl_acts a = {0};
311    p->up = (up | 1u << p->me) & mpm_bits(p->n);
312    bool was_has = p->has_coord;
313    uint32_t was = p->coord;
314    p->has_coord = has_coord;
315    p->coord = has_coord ? coord : 0;
316    mt_strs_free(&p->request);
317    p->has_request = request != NULL;
318    if (request) p->request = mt_strs_clone(request);
319    /* Members gone during a change: nothing more to wait for from them. */
320    if (p->has_change) p->change.waiting &= p->up, p->change.members &= p->up;
321    if (p->has_change && !leading(p)) {
322        p->has_change = false;
323        change_free(&p->change);
324        p->paused = false;
325        answer(p, p->change.asker, false, mt_strdup("peer: the leader changed during the change"), &a);
326    }
327    if (p->has_pause_for && (!(p->up >> p->pause_leader & 1) || !has_coord || coord != p->pause_leader)) p->has_pause_for = false, p->paused = false;
328    uint32_t missing = involved(&p->mode) & ~p->up;
329    bool coord_changed = was_has != has_coord || (has_coord && was != coord);
330    char h1[24], h2[24];
331    if (leading(p)) {
332        if (missing && !p->has_change) {
333            char *l = mpm_label(&p->mode);
334            act_log(&a, mt_fmt("place %s: system(s) %s of %s are gone: re-planning over the up members %s", p->name, rf_hex_alt(h1, missing), l, rf_hex_alt(h2, p->up)));
335            free(l);
336            replan(p, now, &a);
337        } else if ((p->up & ~p->members) || (coord_changed && p->members != p->up)) {
338            if (!p->has_join_at) p->has_join_at = true, p->join_at = now + MPL_JOIN_DWELL_US;
339        } else p->has_join_at = false;
340    } else {
341        p->has_join_at = false;
342        if (!has_coord && missing && p->mode.kind != MPM_LOCAL) {
343            char *l = mpm_label(&p->mode);
344            act_log(&a, mt_fmt("place %s: system(s) %s are gone and there is no leader to re-plan: %s -> local on this machine", p->name, rf_hex_alt(h1, missing), l));
345            free(l);
346            p->paused = false;
347            set_mode(p, mpm_local(), &a);
348            p->members = 1u << p->me;
349        }
350    }
351    return a;
352}
353
354/* Setting with nothing more to wait for: the change is done. */
355static void finish(mpl_placer *p, uint64_t now, mpl_acts *a) {
356    if (!p->has_change || p->change.phase != SETTING || p->change.waiting) return;
357    change_t c = p->change;
358    p->has_change = false;
359    p->paused = false;
360    double ms = (double)since_us(now, c.since) / 1e3;
361    char *l = mpm_label(&c.mode), hx[24];
362    act_log(a, mt_fmt("place %s: %s on the up members %s in %.1f ms", p->name, l, rf_hex_alt(hx, c.members), ms));
363    answer(p, c.asker, true, mt_fmt("%s (state from system %u) in %.1f ms", l, mpm_authority(&c.from, &c.mode), ms), a);
364    free(l);
365    change_free(&c);
366}
367
368static void show(mpl_placer *p, uint32_t frame, uint32_t sys, uint8_t *buf, size_t n, uint32_t lat_us, mpl_acts *a) {
369    p->stats.shown++;
370    if (p->mode.kind == MPM_SPLIT) {
371        p->stats.lat_n++;
372        p->stats.lat_sum_us += lat_us;
373        if (lat_us > p->stats.lat_max_us) p->stats.lat_max_us = lat_us;
374    }
375    push(a, (mpl_act){.kind = MPL_SHOW, .frame = frame, .sys = sys, .bytes = {buf, n, n}, .lat_us = lat_us});
376}
377
378/* Parts into the composer; a whole frame is shown. */
379static bool compose(mpl_placer *p, uint32_t frame, uint32_t parts, uint32_t sys, const uint8_t *data, size_t n, uint64_t now, mpl_acts *a) {
380    mpc_whole w;
381    char *err = NULL;
382    int r = mpc_add(&p->composer, &p->spec, frame, parts, sys, data, n, now, &w, &err);
383    if (r == 1) {
384        show(p, w.frame, w.sys, w.buf, w.nbuf, (uint32_t)since_us(now, w.t0), a);
385        return true;
386    }
387    if (r < 0) {
388        act_log(a, mt_fmt("place %s: %s", p->name, err));
389        free(err);
390    }
391    return false;
392}
393
394static void try_follow(mpl_placer *p, uint64_t now, mpl_acts *a) {
395    if (p->has_follow_inflight || !p->has_follow_next) return;
396    mpw_frame m = p->follow_next;
397    p->has_follow_next = false;
398    mpw_bytes st = {0};
399    mpw_put(&st, m.state, m.nstate);
400    push(a, (mpl_act){.kind = MPL_WRITE_STATE, .bytes = st});
401    mpc_whole w;
402    char *err = NULL;
403    int r = mpc_add(&p->composer, &p->spec, m.frame, m.parts, m.sys, m.data, m.ndata, now, &w, &err);
404    if (r < 0) {
405        act_log(a, mt_fmt("place %s: %s", p->name, err));
406        free(err);
407        mpw_frame_free(&m);
408        return;
409    }
410    if (r == 1) free(w.buf); /* a follower's own parts complete it later */
411    p->has_follow_inflight = true;
412    p->follow_inflight = m.frame;
413    push(a, (mpl_act){.kind = MPL_FOLLOW, .frame = m.frame});
414    mpw_frame_free(&m);
415}
416
417mpl_acts mpl_tick(mpl_placer *p, uint64_t now, bool drained, mpl_state_fn state, void *ctx) {
418    mpl_acts a = {0};
419    if (p->has_pause_for && drained) {
420        p->has_pause_for = false;
421        mpw_bytes st = state(ctx);
422        msg(p, &a, to_k(p->pause_leader), MESH_PL_PAUSED, p->pause_id, p->mode, p->part_ns, st.b, st.n);
423        mpw_bytes_free(&st);
424    }
425    if (leading(p) && !p->has_change && p->has_join_at && now >= p->join_at) {
426        char hx[24];
427        act_log(&a, mt_fmt("place %s: members %s up again: re-planning", p->name, rf_hex_alt(hx, p->up & ~p->members)));
428        replan(p, now, &a);
429    }
430    if (p->has_change) {
431        change_t *c = &p->change;
432        if (since_us(now, c->since) > MPL_CHANGE_TIMEOUT_US) {
433            mt_buf miss = {0};
434            for (uint32_t k = 0; k < p->n; k++)
435                if (c->waiting >> k & 1) mt_printf(&miss, miss.len ? ",%u" : "%u", k);
436            char *from = mpm_label(&c->from);
437            char *why = mt_fmt("%s: the change did not finish in 5s (system(s) %s did not %s); back to %s", p->name, mt_str(&miss), c->phase == PAUSING ? "drain" : "apply it", from);
438            free(from);
439            mt_buf_free(&miss);
440            change_t cc = *c;
441            p->has_change = false;
442            act_log(&a, mt_strdup(why));
443            if (!mpm_mode_eq(&p->mode, &cc.from)) set_mode(p, cc.from, &a);
444            uint32_t o = others(p);
445            if (o) msg(p, &a, to_mask(o), MESH_PL_FAIL, cc.id, cc.from, 0, (const uint8_t *)why, strlen(why));
446            p->paused = false;
447            answer(p, cc.asker, false, why, &a);
448            change_free(&cc);
449        } else if (c->phase == PAUSING && drained && c->waiting == 0) {
450            mpm_mode to = resolve(p, c);
451            /* The state that continues: the old mode's authority's; if it is gone, the new authority's
452             * own copy (a mirror or follower holds the last frame's state). */
453            uint32_t auth = mpm_authority(&c->from, &to), src;
454            mpw_bytes st;
455            if (auth == p->me) src = p->me, st = state(ctx);
456            else if (take_state(c, auth, &st)) src = auth;
457            else {
458                mpm_mode local = mpm_local();
459                uint32_t na = mpm_authority(&local, &to);
460                bool had = take_state(c, na, &st);
461                if (had && na != p->me) src = na;
462                else {
463                    if (had) mpw_bytes_free(&st);
464                    src = p->me, st = state(ctx);
465                }
466            }
467            if (src != auth) act_log(&a, mt_fmt("place %s: system %u's state is gone: the game continues from system %u's copy", p->name, auth, src));
468            c->phase = SETTING;
469            c->mode = to;
470            c->waiting = others(p) & c->members;
471            if (src != p->me) {
472                mpw_bytes copy = {0};
473                mpw_put(&copy, st.b, st.n);
474                push(&a, (mpl_act){.kind = MPL_WRITE_STATE, .bytes = copy});
475            }
476            set_mode(p, to, &a);
477            p->members = c->members;
478            /* PL_SET's aux: the members (bits 0..8) and the system whose state continues (bits 8..16). */
479            if (c->waiting) msg(p, &a, to_mask(c->waiting), MESH_PL_SET, c->id, to, c->members | src << 8, st.b, st.n);
480            mpw_bytes_free(&st);
481            finish(p, now, &a);
482        }
483    }
484    if (mpl_role(p) == MPM_ROLE_FOLLOWER && !p->paused) try_follow(p, now, &a);
485    return a;
486}
487
488mpl_acts mpl_peer_place(mpl_placer *p, mpw_place m, uint32_t from, uint64_t now) {
489    mpl_acts a = {0};
490    if (m.op == MESH_PL_PAUSED && leading(p)) {
491        if (p->has_change && p->change.id == m.id && p->change.phase == PAUSING) {
492            change_t *c = &p->change;
493            member_state *s = state_of(c, from);
494            if (!s) s = &c->states[c->nstates++], s->k = from;
495            else mpw_bytes_free(&s->state);
496            s->state = (mpw_bytes){m.bytes, m.nbytes, m.nbytes};
497            s->part_ns = m.aux;
498            m.bytes = NULL;
499            c->waiting &= ~(1u << from);
500        } else act_log(&a, mt_fmt("place %s: stale PL_PAUSED %u from system %u", p->name, m.id, from));
501    } else if (m.op == MESH_PL_DONE && leading(p)) {
502        if (p->has_change && p->change.id == m.id && p->change.phase == SETTING) {
503            p->change.waiting &= ~(1u << from);
504            finish(p, now, &a);
505        } else act_log(&a, mt_fmt("place %s: stale PL_DONE %u from system %u", p->name, m.id, from));
506    } else if (m.op == MESH_PL_PAUSE) {
507        p->paused = true;
508        p->has_pause_for = true;
509        p->pause_id = m.id;
510        p->pause_leader = from;
511    } else if (m.op == MESH_PL_SET) {
512        char *err = NULL;
513        if (!mpm_fits(&m.mode, p->n, p->spec.fanout, &err)) {
514            char *l = mpm_label(&m.mode);
515            act_log(&a, mt_fmt("place %s: system %u set %s which does not fit here (%s); local", p->name, from, l, err));
516            free(l), free(err);
517            set_mode(p, mpm_local(), &a);
518        } else {
519            /* The state continues from the system the leader took it from (aux bits 8..16). */
520            if ((m.aux >> 8 & 0xff) != p->me) {
521                push(&a, (mpl_act){.kind = MPL_WRITE_STATE, .bytes = {m.bytes, m.nbytes, m.nbytes}});
522                m.bytes = NULL;
523            }
524            set_mode(p, m.mode, &a);
525            p->members = m.aux & 0xff & mpm_bits(p->n);
526        }
527        p->paused = false;
528        p->has_pause_for = false;
529        msg(p, &a, to_k(from), MESH_PL_DONE, m.id, p->mode, 0, NULL, 0);
530    } else if (m.op == MESH_PL_FAIL) {
531        char *r = mt_strndup((const char *)m.bytes, m.nbytes);
532        act_log(&a, mt_fmt("place %s: system %u rolled the change back: %s", p->name, from, r));
533        free(r);
534        if (!mpm_mode_eq(&p->mode, &m.mode)) set_mode(p, m.mode, &a);
535        p->paused = false;
536        p->has_pause_for = false;
537    } else {
538        char lead[24];
539        if (p->has_coord) snprintf(lead, sizeof lead, "Some(%u)", p->coord);
540        else strcpy(lead, "None");
541        act_log(&a, mt_fmt("place %s: F_PLACE op %u from system %u is not for system %u (the leader is %s); ignored", p->name, m.op, from, p->me, lead));
542    }
543    mpw_place_free(&m);
544    return a;
545}
546
547static mpw_bytes frame_bytes(const mpl_placer *p, uint32_t frame, uint32_t parts, const uint8_t *st, size_t nst, const uint8_t *data, size_t nd) {
548    mpw_frame f = {.frame = frame, .parts = parts, .sys = p->me, .state = (uint8_t *)st, .nstate = nst, .data = (uint8_t *)data, .ndata = nd};
549    memcpy(f.name, p->name, sizeof f.name);
550    return mpw_frame_encode(&f);
551}
552
553mpl_acts mpl_own_frame(mpl_placer *p, uint32_t frame, mpw_bytes state, mpw_bytes data, uint64_t now) {
554    mpl_acts a = {0};
555    uint32_t parts = mpl_my_parts(p);
556    mpl_dest to = to_mask(p->members & mpm_bits(p->n) & ~(1u << p->me));
557    mpm_role r = mpl_role(p);
558    if (r == MPM_ROLE_RUNNER) {
559        size_t nb = mps_out_bytes(&p->spec);
560        uint8_t *buf = mt_alloc(nb ? nb : 1);
561        char *err = NULL;
562        if (!mpc_scatter(&p->spec, buf, nb, parts, data.b, data.n, &err)) {
563            act_log(&a, mt_fmt("place %s: %s", p->name, err));
564            free(err), free(buf);
565            goto done;
566        }
567        show(p, frame, mpc_sys_nibbles(parts, p->me), buf, nb, 0, &a);
568        push(&a, (mpl_act){.kind = MPL_SEND, .dest = to, .bytes = frame_bytes(p, frame, parts, state.b, state.n, data.b, data.n)});
569    } else if (r == MPM_ROLE_OWNER) {
570        mpc_whole w;
571        char *err = NULL;
572        int x = mpc_add(&p->composer, &p->spec, frame, parts, p->me, data.b, data.n, now, &w, &err);
573        if (x == 1) show(p, w.frame, w.sys, w.buf, w.nbuf, 0, &a);
574        else if (x < 0) {
575            act_log(&a, mt_fmt("place %s: %s", p->name, err));
576            free(err);
577        }
578        push(&a, (mpl_act){.kind = MPL_SEND, .dest = to, .bytes = frame_bytes(p, frame, parts, state.b, state.n, data.b, data.n)});
579    }
580done:
581    mpw_bytes_free(&state);
582    mpw_bytes_free(&data);
583    return a;
584}
585
586mpl_acts mpl_follow_done(mpl_placer *p, mpw_bytes data, uint64_t now) {
587    mpl_acts a = {0};
588    if (!p->has_follow_inflight) goto done;
589    p->has_follow_inflight = false;
590    uint32_t frame = p->follow_inflight;
591    if (mpl_role(p) != MPM_ROLE_FOLLOWER) goto done;
592    uint32_t parts = mpl_my_parts(p);
593    compose(p, frame, parts, p->me, data.b, data.n, now, &a);
594    push(&a, (mpl_act){.kind = MPL_SEND, .dest = to_mask(p->members & mpm_bits(p->n) & ~(1u << p->me)), .bytes = frame_bytes(p, frame, parts, NULL, 0, data.b, data.n)});
595    if (!p->paused) try_follow(p, now, &a);
596done:
597    mpw_bytes_free(&data);
598    return a;
599}
600
601void mpl_follow_lost(mpl_placer *p) { p->has_follow_inflight = false; }
602
603mpl_acts mpl_peer_frame(mpl_placer *p, mpw_frame m, uint64_t now) {
604    mpl_acts a = {0};
605    /* The sender must render exactly these parts in our mode; the state comes from the authority only. */
606    uint32_t theirs = mpm_parts_of(&p->mode, m.sys, p->spec.fanout);
607    uint32_t auth = mpm_input_to(&p->mode, p->me);
608    bool valid = m.sys != p->me && theirs && m.parts == theirs && (m.nstate == 0 || (m.sys == auth && m.nstate == mps_state_bytes(&p->spec)));
609    if (!valid) {
610        p->stats.ignored++;
611        mpw_frame_free(&m);
612        return a;
613    }
614    mpm_role r = mpl_role(p);
615    if (r == MPM_ROLE_MIRROR) {
616        if (m.nstate) {
617            mpw_bytes st = {0};
618            mpw_put(&st, m.state, m.nstate);
619            push(&a, (mpl_act){.kind = MPL_WRITE_STATE, .bytes = st});
620        }
621        compose(p, m.frame, m.parts, m.sys, m.data, m.ndata, now, &a);
622    } else if (r == MPM_ROLE_FOLLOWER && m.sys == auth) {
623        if (!m.nstate) {
624            p->stats.ignored++;
625            mpw_frame_free(&m);
626            return a;
627        }
628        /* The newest wins: a frame still waiting is replaced (the owner is ahead). */
629        if (p->has_follow_next) {
630            p->stats.ignored++;
631            mpw_frame_free(&p->follow_next);
632        }
633        p->follow_next = m;
634        p->has_follow_next = true;
635        m = (mpw_frame){0};
636        if (!p->paused) try_follow(p, now, &a);
637    } else if (r == MPM_ROLE_FOLLOWER || r == MPM_ROLE_OWNER) {
638        /* Another follower's parts (to the owner, or to a fellow follower). */
639        if (!compose(p, m.frame, m.parts, m.sys, m.data, m.ndata, now, &a) && r == MPM_ROLE_OWNER && p->composer.n == 0) p->stats.ignored++;
640    } else p->stats.ignored++;
641    mpw_frame_free(&m);
642    return a;
643}
644
645mpl_acts mpl_drop_all(mpl_placer *p, const char *why) {
646    mpl_acts a = {0};
647    if (p->has_change) {
648        p->has_change = false;
649        answer(p, p->change.asker, false, mt_strdup(why), &a);
650        change_free(&p->change);
651    }
652    return a;
653}