From a6df26a8499522033277ce4f3cd76eee155169ee Mon Sep 17 00:00:00 2001 From: eunhhu Date: Wed, 30 Sep 2026 03:04:03 +0100 Subject: Bound what an app holds (--memory): refuse what would grow past it pure-serve --memory BYTES gives the app a budget of state bytes: its lists' items as they are stored, its texts, the outbox and the numbers heard (docs/design/013-memory-budget.md). The count is deterministic, the same on any device. An event after which the states hold more than the budget, and more than before it, fails with the fault memory and changes nothing; HTTP answers 503 memory. One that frees or keeps them goes on, so an app over its budget can always shrink. Sends waiting for their receivers are state: a full outbox refuses the next send until some are stored, which is the backpressure a slow receiver puts on clients. A delivery refused for memory is not heard: the link keeps it (c9c0edf) and HTTP senders try again after 503. An answer (--reply) now takes its send out of the outbox in its own event (pure_app_answer), so it has the room its send held. Apart, an app whose outbox filled the budget could never apply an answer that keeps anything, and the send leaves only after its answer: both would stay for good. An answer that still does not fit waits, with the wire's after it, and is tried again every 100 ms: pure_wire_answer leaves it in place until pure_wire_answered. Checks: test-module memory_budget (refused and undone, freeing over the budget, a full outbox, a delivery not heard, answers that fit or wait, across a restart); check.py server_memory (a sender refused 503 memory with its receiver down and taking buys once it is up; a receiver restarted below what it keeps, refusing 7 times in 2 s, then taking all) and server_answers_memory (answers wait while receipts fill the budget, then apply once each; the API called once per order). The stats report state_bytes and memory_refused. --- serve/deliver.c | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) (limited to 'serve/deliver.c') diff --git a/serve/deliver.c b/serve/deliver.c index b4d9e57..68c5f14 100644 --- a/serve/deliver.c +++ b/serve/deliver.c @@ -47,7 +47,6 @@ struct PureWire { int answer_input; /* the input answers become events of, or -1 */ Answer *answers; /* made, for the owner to take in order */ size_t answer_head, answer_count, answer_capacity; - Answer taken; /* the one the owner took last, kept until it takes the next */ uint32_t placed[2 * PURE_MODULE_FIELDS]; /* Sealed sends: to peer's key, authenticated by the node's own key when * it has one (its public half is from), in one session made at the first @@ -240,23 +239,26 @@ bool pure_wire_reply(PureWire *w, uint32_t input, char *error, size_t size) { } bool pure_wire_answer(PureWire *w, PureWireAnswer *out) { - free(w->taken.bytes); - w->taken = (Answer){0}; pthread_mutex_lock(&w->lock); bool some = w->answer_count > 0; - if (some) { - w->taken = w->answers[w->answer_head++]; - --w->answer_count; - w->delivered = w->taken.seq; /* trimmed after the owner applies it */ - } + Answer next = some ? w->answers[w->answer_head] : (Answer){0}; /* its bytes stay put while the array moves */ pthread_mutex_unlock(&w->lock); if (!some) return false; const PureInputShape *in = &w->module->input[w->answer_input]; - for (size_t i = 0; i < 2 * (size_t)in->field_count; ++i) w->placed[i] = read_u32(w->taken.bytes + 4 * i); - *out = (PureWireAnswer){(uint32_t)w->answer_input, w->taken.seq, - {w->taken.bytes + w->taken.table, w->taken.size - w->taken.table}, w->placed, w->source}; + for (size_t i = 0; i < 2 * (size_t)in->field_count; ++i) w->placed[i] = read_u32(next.bytes + 4 * i); + *out = (PureWireAnswer){(uint32_t)w->answer_input, next.seq, {next.bytes + next.table, next.size - next.table}, + w->placed, w->source}; return true; } + +void pure_wire_answered(PureWire *w) { + pthread_mutex_lock(&w->lock); + Answer done = w->answers[w->answer_head++]; + --w->answer_count; + w->delivered = done.seq; /* trimmed after the owner applies it */ + pthread_mutex_unlock(&w->lock); + free(done.bytes); +} uint64_t pure_wire_queued(const PureWire *w) { return w->queued; } uint64_t pure_wire_delivered(PureWire *w) { @@ -771,7 +773,6 @@ void pure_wire_close(PureWire *w) { } for (size_t i = 0; i < w->answer_count; ++i) free(w->answers[w->answer_head + i].bytes); free(w->answers); - free(w->taken.bytes); free(w->queue); pthread_cond_destroy(&w->changed); pthread_mutex_destroy(&w->lock); -- cgit v1.2.3