aboutsummaryrefslogtreecommitdiffstats
path: root/serve/serve.c
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-10-18 19:30:16 +0100
committereunhhu <[email protected]>2026-10-18 19:30:16 +0100
commitb22b8c3f6c5378b47c36370225d7c41d4394bb7c (patch)
treed1a3cbf1b0e718f0acafa08ec6741761f08e5dd5 /serve/serve.c
parente8b3499289022c8729900a58ed218d961a85fec8 (diff)
downloadpure-b22b8c3f6c5378b47c36370225d7c41d4394bb7c.tar.gz
pure-b22b8c3f6c5378b47c36370225d7c41d4394bb7c.zip
Module format v7, 027's 4b-2: keyed documents gone, a keyed list of records holding bytes doing their work: no collections table, DOC, DOC_SIZE or REPLACE (106 opcodes now 103, those from LEN on moved up), keyed messages or a base revision (pure_app_message takes an input and a payload), no PureData document callbacks (a data host opens states and saves them together), no burst by document key; the header 38 bytes; pure-edit-module gone; pure-serve without /objects, ETag, If-Match and If-None-Match, 412 or an app a thread for a module of documents alone (one without states or outputs runs so); a hole closed: an instruction in no handler's, view's or function's block was never verified yet the load's later passes read its arguments (a damaged module read out of bounds once the header moved what the mutation test damaged), now blanked to CONST 0; checks moved: server_documents (DOCS_SOURCE, keyed byte documents with the app's revision, against a Python model, one and three connection threads, a restart) in place of the parity with editor.pure, pipelining and bursts through kill -9 on its inputs and views, the lent budget on a text view (lending a document's read is server_lent_views' already), clocks and tokens on it; message tests without a document host; the HTTP benchmark's document suite runs the earlier document server and the baselines alone; the count harness builds against either message API (runs.sh build: -DPURE_KEYED_MESSAGES before 4b); counts: b33 against b32 with batches within 0.002% but BFS -0.33%, without batches BFS +0.63%, knapsack +0.52%, join +0.38%; the bank's queries (s32 on a v6 module, s33 on v7) field +0.68%, found +0.44%, large +0.42%, the rest within 0.13%; 008 as v7, 027's 4b-2 as made and 4c designed
Diffstat (limited to 'serve/serve.c')
-rw-r--r--serve/serve.c246
1 files changed, 69 insertions, 177 deletions
diff --git a/serve/serve.c b/serve/serve.c
index bb339cf..500f470 100644
--- a/serve/serve.c
+++ b/serve/serve.c
@@ -9,10 +9,7 @@
* its bytes field from the body, and answers what its handler replies, a
* field of the message). A failed event answers with its fault.
*
- * A module with keyed messages named show and edit also serves the document
- * contract of server.c (GET and PUT /objects/KEY, If-Match or If-None-Match:
- * *, ETag = revision, X-Pure-Error): GET is its show message and PUT its edit
- * message. HTTP/1.1 connections persist and may pipeline; an epoll thread
+ * HTTP/1.1 connections persist and may pipeline; an epoll thread
* accepts, reads and writes without blocking. Requests go through the
* runtime core (a work slot per connection, the payload budget, ingress
* owned by the core, write replies borrowed from the request). After each
@@ -23,9 +20,8 @@
*
* With --threads N each thread has its own listener on the same port
* (SO_REUSEPORT), epoll set and runtime domain; the kernel spreads
- * connections over them. A module of keyed documents alone runs one app per
- * thread: they share nothing but the store, whose commits take turns and
- * whose revisions settle concurrent edits of a key. States have one owner:
+ * connections over them. A module without states or outputs runs one app
+ * per thread: there is nothing for them to share. States have one owner:
* for a module with them, one more thread owns the app, and the connection
* threads hand it their ready requests and answer what it hands back. Its
* bursts take the requests of every thread, one save each. */
@@ -74,7 +70,7 @@ typedef struct Host Host;
/* What a request names: a document, a view (a table's rows from, count) or
* an input. Kept per work slot, so an accepted event runs even if its
* connection is gone before the burst. */
-typedef enum { ROUTE_NONE, ROUTE_OBJECT, ROUTE_VIEW, ROUTE_INPUT, ROUTE_QUERY } RouteKind;
+typedef enum { ROUTE_NONE, ROUTE_VIEW, ROUTE_INPUT, ROUTE_QUERY } RouteKind;
/* A view's (or a query's) arguments come as the request's query: argued
* says so, and given is its text, kept on the request's work slot. */
typedef struct App App;
@@ -172,7 +168,6 @@ typedef struct Connection {
Seal seal;
char aad[PURE_SOURCE + 48];
size_t opened;
- int64_t expected;
size_t body_size, received, in_used;
unsigned char *body;
PureTicket ticket;
@@ -195,7 +190,6 @@ typedef struct {
PureTicket ticket;
PureInput input;
PureResult status;
- int64_t revision;
PureResource resource;
PureSpan reply;
struct iovec *extents;
@@ -221,8 +215,8 @@ typedef struct Published {
_Atomic unsigned subscribers; /* streams of its views on any thread (docs/design/019): publishing wakes their threads */
} Published;
-/* The app and its store. A connection thread runs it itself (an app of
- * documents only), or one owner thread runs it for all connection threads.
+/* The app and its store. A connection thread runs it itself (an app without
+ * states or outputs), or one owner thread runs it for all connection threads.
* The owner publishes a snapshot of the states after each save, and the
* connection threads show views from it themselves: reads never wait for
* the owner, which may be applying events or waiting for a sync. */
@@ -230,7 +224,6 @@ typedef struct {
const PureModule *module;
PureStore *store;
PureApp app;
- uint32_t show, edit;
uint64_t bursts, burst_events;
Applied *applied; /* a round's requests, grown to fit */
size_t applied_capacity;
@@ -425,9 +418,7 @@ struct App {
char *tokens_path;
uint64_t tokens_digest;
const char *store;
- bool documents; /* the module serves /objects: it has show and edit */
bool owned; /* an owner runs it (states or outputs, or any of --apps); else the connection threads */
- uint32_t show, edit;
_Atomic size_t memory; /* its budgets (0: none): what its states hold (read by its views' readers too), */
_Atomic uint64_t work; /* what an event or a view's evaluation may run */
size_t queue; /* the requests that may wait for its turn, and its views' with the view workers */
@@ -459,7 +450,7 @@ static void subscribe(Connection *c, const Route *route, const char *raw, size_t
static void wake_pushers(void);
struct Host {
- Executor local; /* the one app of the host, when this thread runs it (documents alone) */
+ Executor local; /* the one app of the host, when this thread runs it (no states nor outputs) */
PureApp *readers; /* by app: shows its owner's snapshots here */
PureSnapshot **snapshots; /* by app: the snapshot a round reads views from, once */
uint64_t round, *read_in, *handed_in; /* by app: the round it was read in, its requests handed in */
@@ -597,8 +588,8 @@ static void put_decimal(Head *h, uint64_t value) {
/* Answer with a body of `count` extents (`size` bytes in all); the ticket
* keeps them alive. One writev sends as much as the socket takes. */
-static void respond_typed(Connection *c, int status, const char *reason, const char *error, int64_t revision,
- const char *type, const struct iovec *extents, size_t count, size_t size) {
+static void respond_typed(Connection *c, int status, const char *reason, const char *error, const char *type,
+ const struct iovec *extents, size_t count, size_t size) {
Head h = {c->head, c->head + sizeof(c->head)};
put_text(&h, "HTTP/1.1 ");
put_decimal(&h, (uint64_t)status);
@@ -612,11 +603,6 @@ static void respond_typed(Connection *c, int status, const char *reason, const c
put_text(&h, "\r\nCache-Control: no-store\r\nX-Pure-Error: ");
put_text(&h, error);
put_text(&h, status == 401 ? "\r\nWWW-Authenticate: Bearer\r\n" : "\r\n");
- if (revision > 0) {
- put_text(&h, "ETag: \"");
- put_decimal(&h, (uint64_t)revision);
- put_text(&h, "\"\r\n");
- }
/* A delivery's answer tells how many numbered messages one request may carry. */
if (*c->source) {
put_text(&h, "X-Pure-Batch: ");
@@ -634,43 +620,37 @@ static void respond_typed(Connection *c, int status, const char *reason, const c
c->out_at = 0;
}
-static void respond_extents(Connection *c, int status, const char *reason, const char *error, int64_t revision,
- const struct iovec *extents, size_t count, size_t size) {
- respond_typed(c, status, reason, error, revision, "application/octet-stream", extents, count, size);
-}
-
-static void respond(Connection *c, int status, const char *reason, const char *error, int64_t revision,
- PureSpan body) {
+static void respond(Connection *c, int status, const char *reason, const char *error, PureSpan body) {
struct iovec extent = {(void *)body.data, body.size};
- respond_extents(c, status, reason, error, revision, &extent, body.size ? 1 : 0, body.size);
+ respond_typed(c, status, reason, error, "application/octet-stream", &extent, body.size ? 1 : 0, body.size);
}
/* A request refused before it has a ticket; a body it came with is not read. */
static void reject(Connection *c, int status, const char *reason, const char *error) {
if (c->body_size) c->keep_alive = false;
- respond(c, status, reason, error, 0, (PureSpan){0});
+ respond(c, status, reason, error, (PureSpan){0});
}
/* A request the host cannot read further: answer and close. */
static void refuse(Connection *c, int status, const char *reason, const char *error) {
release_ticket(c);
c->keep_alive = false;
- respond(c, status, reason, error, 0, (PureSpan){0});
+ respond(c, status, reason, error, (PureSpan){0});
}
static void answer(Connection *c, PureOutcome result) {
switch (result.status) {
- case PURE_OK: respond(c, 200, "OK", "none", result.revision, result.body); return;
- case PURE_CONFLICT: respond(c, 412, "Precondition Failed", "conflict", result.revision, (PureSpan){0}); return;
- case PURE_NOT_FOUND: respond(c, 404, "Not Found", "not_found", 0, (PureSpan){0}); break;
- case PURE_BUSY: respond(c, 503, "Service Unavailable", "store_busy", 0, (PureSpan){0}); break;
- case PURE_BUDGET: respond(c, 503, "Service Unavailable", "payload_budget", 0, (PureSpan){0}); break;
- case PURE_ALLOCATION: respond(c, 503, "Service Unavailable", "allocation", 0, (PureSpan){0}); break;
- case PURE_CLOSED: respond(c, 503, "Service Unavailable", "stopped", 0, (PureSpan){0}); break;
- case PURE_DENIED: respond(c, 403, "Forbidden", "denied", 0, (PureSpan){0}); break;
- case PURE_LIMIT: respond(c, 413, "Content Too Large", "limit", 0, (PureSpan){0}); break;
- case PURE_UNCERTAIN: respond(c, 500, "Internal Server Error", "durability_unknown", 0, (PureSpan){0}); break;
- default: respond(c, 500, "Internal Server Error", "storage", 0, (PureSpan){0}); break;
+ case PURE_OK: respond(c, 200, "OK", "none", result.body); return;
+ case PURE_CONFLICT: respond(c, 409, "Conflict", "conflict", (PureSpan){0}); break;
+ case PURE_NOT_FOUND: respond(c, 404, "Not Found", "not_found", (PureSpan){0}); break;
+ case PURE_BUSY: respond(c, 503, "Service Unavailable", "store_busy", (PureSpan){0}); break;
+ case PURE_BUDGET: respond(c, 503, "Service Unavailable", "payload_budget", (PureSpan){0}); break;
+ case PURE_ALLOCATION: respond(c, 503, "Service Unavailable", "allocation", (PureSpan){0}); break;
+ case PURE_CLOSED: respond(c, 503, "Service Unavailable", "stopped", (PureSpan){0}); break;
+ case PURE_DENIED: respond(c, 403, "Forbidden", "denied", (PureSpan){0}); break;
+ case PURE_LIMIT: respond(c, 413, "Content Too Large", "limit", (PureSpan){0}); break;
+ case PURE_UNCERTAIN: respond(c, 500, "Internal Server Error", "durability_unknown", (PureSpan){0}); break;
+ default: respond(c, 500, "Internal Server Error", "storage", (PureSpan){0}); break;
}
/* A failed request carries no body: the payload can go now. */
if (!result.body.size) release_ticket(c);
@@ -684,18 +664,18 @@ static void answer_app(Connection *c, PureOutcome result, bool view, PureFault f
const char *named = fault != PURE_FAULT_NONE ? pure_fault_name(fault) : NULL;
if (result.status == PURE_OK) {
struct iovec extent = {(void *)result.body.data, result.body.size};
- respond_typed(c, 200, "OK", "none", 0, type ? type : "application/octet-stream", &extent,
+ respond_typed(c, 200, "OK", "none", type ? type : "application/octet-stream", &extent,
result.body.size ? 1 : 0, result.body.size);
} else if (result.status == PURE_NOT_FOUND) {
- respond(c, 404, "Not Found", view ? "no_value" : named ? named : "not_found", 0, (PureSpan){0});
+ respond(c, 404, "Not Found", view ? "no_value" : named ? named : "not_found", (PureSpan){0});
} else if (result.status == PURE_CONFLICT) {
- respond(c, 409, "Conflict", named ? named : "conflict", 0, (PureSpan){0});
+ respond(c, 409, "Conflict", named ? named : "conflict", (PureSpan){0});
} else if (fault == PURE_FAULT_MEMORY) {
- respond(c, 503, "Service Unavailable", named, 0, (PureSpan){0});
+ respond(c, 503, "Service Unavailable", named, (PureSpan){0});
} else if (named && (result.status == PURE_LIMIT || result.status == PURE_INVALID)) {
- respond(c, 422, "Unprocessable Content", named, 0, (PureSpan){0});
+ respond(c, 422, "Unprocessable Content", named, (PureSpan){0});
} else if (result.status == PURE_INVALID) {
- respond(c, 400, "Bad Request", "invalid", 0, (PureSpan){0}); /* such as a text that is not UTF-8 */
+ respond(c, 400, "Bad Request", "invalid", (PureSpan){0}); /* such as a text that is not UTF-8 */
} else {
answer(c, result);
return;
@@ -765,7 +745,7 @@ static bool parse_head(Connection *c, char *head, size_t size, char **query) {
if (end) path += 1 + length;
}
static const struct { const char *prefix; RouteKind kind; } routes[] = {
- {"/objects/", ROUTE_OBJECT}, {"/views/", ROUTE_VIEW}, {"/inputs/", ROUTE_INPUT}, {"/queries/", ROUTE_QUERY}};
+ {"/views/", ROUTE_VIEW}, {"/inputs/", ROUTE_INPUT}, {"/queries/", ROUTE_QUERY}};
const char *key = "";
c->route = ROUTE_NONE;
for (size_t i = 0; i < sizeof(routes) / sizeof(*routes) && c->route == ROUTE_NONE; ++i)
@@ -781,12 +761,11 @@ static bool parse_head(Connection *c, char *head, size_t size, char **query) {
return false;
}
memcpy(c->key, key, key_size + 1);
- /* Each route has its method: documents GET and PUT, views GET, inputs
- * POST, queries PUT (registered) and GET (shown). */
- if ((c->route == ROUTE_OBJECT && (c->post || *query)) || (c->route == ROUTE_VIEW && (c->put || c->post)) ||
+ /* Each route has its method: views GET, inputs POST, queries PUT (registered) and GET (shown). */
+ if ((c->route == ROUTE_VIEW && (c->put || c->post)) ||
(c->route == ROUTE_INPUT && !c->post) || (c->route == ROUTE_QUERY && (c->post || (c->put && *query))))
return false;
- bool length_seen = false, condition_seen = false, host_seen = false;
+ bool length_seen = false, host_seen = false;
c->source[0] = 0;
c->sequence = 0;
c->batch = 1;
@@ -796,7 +775,6 @@ static bool parse_head(Connection *c, char *head, size_t size, char **query) {
bool counted = false;
c->body_size = 0;
c->json = false;
- c->expected = -1;
c->keep_alive = true;
line = next + 2;
while (*line) {
@@ -820,16 +798,6 @@ static bool parse_head(Connection *c, char *head, size_t size, char **query) {
} else if (!strcasecmp(line, "Host")) {
if (host_seen || !*value) return false;
host_seen = true;
- } else if (!strcasecmp(line, "If-Match")) {
- if (condition_seen || tail - value < 3 || *value != '"' || tail[-1] != '"') return false;
- tail[-1] = 0;
- if (!number(value + 1, INT64_MAX, &n) || !n) return false;
- c->expected = (int64_t)n;
- condition_seen = true;
- } else if (!strcasecmp(line, "If-None-Match")) {
- if (condition_seen || strcmp(value, "*")) return false;
- c->expected = 0;
- condition_seen = true;
} else if (!strcasecmp(line, "Content-Type")) {
c->json = !strncasecmp(value, "application/json", 16);
} else if (!strcasecmp(line, "Accept")) {
@@ -865,10 +833,10 @@ static bool parse_head(Connection *c, char *head, size_t size, char **query) {
}
if (!*c->source != !c->sequence || (*c->source && c->route != ROUTE_INPUT) || (counted && !*c->source) ||
c->sequence > UINT64_MAX - c->batch) return false;
- /* A document's PUT names the revision it was made against; a query's replaces what was registered. */
- if (c->put) return host_seen && length_seen && condition_seen == (c->route != ROUTE_QUERY);
- if (c->post) return host_seen && !condition_seen; /* no Content-Length: no body */
- return host_seen && !condition_seen && c->body_size == 0;
+ /* A query's PUT replaces what was registered. */
+ if (c->put) return host_seen && length_seen;
+ if (c->post) return host_seen; /* no Content-Length: no body */
+ return host_seen && c->body_size == 0;
}
static void put_u32(unsigned char *p, uint32_t v) { for (unsigned i = 0; i < 4; ++i) p[i] = (unsigned char)(v >> (8 * i)); }
@@ -911,9 +879,7 @@ static const char *resolve(Connection *c, char *query, Route *route, int *status
}
route->app = app;
route->module = c->module = m;
- if (route->kind == ROUTE_NONE || (route->kind == ROUTE_OBJECT && !m->collections) ||
- (route->kind == ROUTE_QUERY && !app->owned)) return "route"; /* a query reads an app's states */
- if (route->kind == ROUTE_OBJECT) return NULL;
+ if (route->kind == ROUTE_NONE || (route->kind == ROUTE_QUERY && !app->owned)) return "route"; /* a query reads states */
if (route->kind == ROUTE_QUERY) { /* the view is the query module's, found when shown */
route->from = 0;
route->count = SIZE_MAX;
@@ -945,7 +911,6 @@ static const char *resolve(Connection *c, char *query, Route *route, int *status
route->count = SIZE_MAX;
const PureInputShape *shape = route->kind == ROUTE_INPUT ? &m->input[found] : NULL;
bool message = shape && shape->kind == PURE_KIND_MESSAGE;
- if (message && shape->keyed) return "keyed";
if (*c->source && !message) return "delivery";
if (!app->owned) c->source[0] = 0; /* an app without states or outputs applies it as a message */
if (shape) {
@@ -1115,14 +1080,14 @@ static bool open_seal(Connection *c) {
static void body_done(Connection *c) {
if (*c->seal.suite && !open_seal(c)) {
release_ticket(c); /* never made ready: it never runs */
- respond(c, 400, "Bad Request", "seal", 0, (PureSpan){0});
+ respond(c, 400, "Bad Request", "seal", (PureSpan){0});
return;
}
const char *why = c->route == ROUTE_INPUT && c->json ? convert(c) : NULL;
if (why) {
release_ticket(c); /* never made ready: it never runs */
- if (!strcmp(why, "payload_budget") || !strcmp(why, "memory")) respond(c, 503, "Service Unavailable", why, 0, (PureSpan){0});
- else respond(c, 400, "Bad Request", why, 0, (PureSpan){0});
+ if (!strcmp(why, "payload_budget") || !strcmp(why, "memory")) respond(c, 503, "Service Unavailable", why, (PureSpan){0});
+ else respond(c, 400, "Bad Request", why, (PureSpan){0});
return;
}
if (!pure_runtime_ready(c->ticket)) abort();
@@ -1205,7 +1170,7 @@ static void start_request(Connection *c, size_t head_size) {
}
/* A view's stream (docs/design/019): pushed from snapshots, so an owned app's. */
if (c->events && route.kind == ROUTE_VIEW && route.app->owned) { subscribe(c, &route, raw, raw_size); return; }
- /* A document's PUT carries its body; an input's event its payload's size,
+ /* A query's PUT carries its body; an input's event its payload's size,
* the payload (the fixed fields, then the body), and room for its JSON. */
bool write = route.kind == ROUTE_INPUT || c->put;
size_t table = route.kind == ROUTE_INPUT && c->json ? 8 * (size_t)route.module->input[c->input].field_count : 0;
@@ -1213,8 +1178,7 @@ static void start_request(Connection *c, size_t head_size) {
/* A batch has a table and a prefix for each of its messages. */
size_t input_size = route.kind == ROUTE_INPUT ? lead + c->batch * (table + c->field_bytes) + c->body_size :
write ? c->body_size : 0;
- PureResult admitted = pure_runtime_accept(&host->runtime, write ? PURE_INPUT_WRITE : PURE_INPUT_READ, c->key,
- route.kind == ROUTE_INPUT || route.kind == ROUTE_QUERY ? 0 : c->expected,
+ PureResult admitted = pure_runtime_accept(&host->runtime, write ? PURE_INPUT_WRITE : PURE_INPUT_READ, c->key, 0,
input_size, &c->ticket);
if (admitted != PURE_OK) {
c->ticket = (PureTicket){0};
@@ -1394,13 +1358,6 @@ static bool lend(Applied *out, PureMapping *mapping, Gather *gather, bool read,
return true;
}
-static bool copy_reply(void *context, PureSpan span) {
- Reply *r = context;
- if (span.size) memcpy(r->bytes + r->size, span.data, span.size);
- r->size += span.size;
- return true;
-}
-
/* ---- App routes -------------------------------------------------------- */
typedef struct { unsigned char *bytes; size_t size, capacity; bool failed; } Output;
@@ -1701,8 +1658,8 @@ static PureResult apply_input(PureApp *app, const Route *route, const PureEffect
}
if (batch > 1 && app_refused(app)) status = PURE_OK;
} else
- status = table ? pure_app_message_fields(app, route->target, NULL, -1, payload, placed) :
- pure_app_message(app, route->target, NULL, -1, payload);
+ status = table ? pure_app_message_fields(app, route->target, payload, placed) :
+ pure_app_message(app, route->target, payload);
Reply reply = {0};
if (status == PURE_OK) pure_app_reply(app, measure_reply, &reply);
out->reply = (PureSpan){reply.size ? reply.first : NULL, reply.size};
@@ -1721,40 +1678,10 @@ static void apply_effect(Executor *ex, const Job *job, Applied *out) {
out->status = show_view(app, &job->route, effect->available, out);
return;
}
- if (job->route.kind == ROUTE_INPUT) {
- out->status = apply_input(app, &job->route, effect, out);
- if (out->status == PURE_OK) ++ex->burst_events;
- ex->memory_refused += out->fault == PURE_FAULT_MEMORY;
- return;
- }
- bool read = effect->input == PURE_INPUT_READ;
- out->status = pure_app_message(app, read ? ex->show : ex->edit, effect->key,
- read ? -1 : effect->expected, read ? (PureSpan){0} : effect->value);
- out->revision = app->revision;
- if (out->status != PURE_OK) return;
- ++ex->burst_events;
- Reply reply = {0};
- pure_app_reply(app, measure_reply, &reply);
- if (!read) {
- /* Checked at start: an edit replies a field of its request. */
- out->reply = (PureSpan){reply.size ? reply.first : NULL, reply.size};
- return;
- }
- if (reply.size > effect->available) { out->status = PURE_BUDGET; return; }
- if (!reply.size) return;
- /* A stored document is lent, extent by extent, not copied. */
- PureMapping *mapping = app->document ? pure_draft_mapping(app->document) : NULL;
- if (mapping) {
- Gather gather = {NULL, 0, 0, mapping, true};
- bool read = pure_app_reply(app, gather_extents, &gather) == PURE_OK;
- if (lend(out, mapping, &gather, read, reply.size)) return;
- }
- unsigned char *copy = malloc(reply.size);
- if (!copy) { out->status = PURE_ALLOCATION; return; }
- reply = (Reply){.bytes = copy};
- pure_app_reply(app, copy_reply, &reply);
- out->resource = (PureResource){.body = {copy, reply.size}, .allocation = copy, .allocation_size = reply.size,
- .release = release_copy};
+ /* An input's event (what reaches an app's owner is a view or an input) */
+ out->status = apply_input(app, &job->route, effect, out);
+ if (out->status == PURE_OK) ++ex->burst_events;
+ ex->memory_refused += out->fault == PURE_FAULT_MEMORY;
}
/* ---- A surface in the node (serve/surface.h): one, run in its own thread,
@@ -1869,7 +1796,7 @@ static bool local_queue(Local *l, uint64_t seq, PureSpan payload) {
* why in error, when r has none. */
static bool local_bind(Local *l, const PureModule *r, char *error, size_t size) {
int input = pure_module_input(r, l->input_name);
- if (input < 0 || r->input[input].kind != PURE_KIND_MESSAGE || r->input[input].keyed) {
+ if (input < 0 || r->input[input].kind != PURE_KIND_MESSAGE) {
snprintf(error, size, "no input '%s' taking messages", l->input_name);
return false;
}
@@ -2141,10 +2068,8 @@ static void save_burst(Executor *ex, Applied *applied, size_t count) {
if (applied[i].status == PURE_OK) applied[i].status = saved;
}
-/* Run a round's requests in bursts. A burst ends before a document's key
- * repeats or before its documents would outgrow one save, so every request
- * is stored by its burst's save or fails with it; views and events of the
- * app's states join any burst. Each group of answers goes to finish.
+/* Run a round's requests in a burst: every request is stored by its save or
+ * fails with it. Each group of answers goes to finish.
*
* The owner takes a round's reads first and answers them at once: a
* round's requests come from different connections (each has one request
@@ -2167,20 +2092,9 @@ static void execute(Executor *ex, const Job *jobs, size_t count, void (*finish)(
for (size_t i = 0; ex->reads_first && i < count; ++i)
if (jobs[i].effect.input == PURE_INPUT_READ) apply_effect(ex, &jobs[i], &applied[reads++]);
if (reads) finish(context, applied, reads);
- size_t n = reads, start = reads, keys = 0;
- for (size_t i = 0; i < count; ++i) {
- if (ex->reads_first && jobs[i].effect.input == PURE_INPUT_READ) continue;
- /* A document joins a burst once (its revision moves once a save). */
- bool document = jobs[i].route.kind == ROUTE_OBJECT;
- if (document && (keys == PURE_APP_PENDING || pure_app_pending(&ex->app, jobs[i].effect.key))) {
- save_burst(ex, applied + start, n - start);
- finish(context, applied + start, n - start);
- start = n;
- keys = 0;
- }
- apply_effect(ex, &jobs[i], &applied[n++]);
- keys += document;
- }
+ size_t n = reads, start = reads;
+ for (size_t i = 0; i < count; ++i)
+ if (!ex->reads_first || jobs[i].effect.input != PURE_INPUT_READ) apply_effect(ex, &jobs[i], &applied[n++]);
save_burst(ex, applied + start, n - start);
finish(context, applied + start, n - start);
if (pinned) pure_store_pin(ex->store, false);
@@ -2218,20 +2132,19 @@ static long run_timers(Executor *ex) {
* its connection still waits for it. */
static void answer_applied(Host *host, Applied *a) {
if (!pure_runtime_complete(a->ticket, a->status, a->input == PURE_INPUT_READ ? &a->resource : NULL,
- a->status == PURE_OK ? a->reply : (PureSpan){0}, a->revision)) abort();
+ a->status == PURE_OK ? a->reply : (PureSpan){0}, 0)) abort();
Connection *c = host->by_slot[a->ticket.slot];
if (c && c->phase == WAITING && c->ticket.generation == a->ticket.generation) {
PureOutcome result;
if (!pure_runtime_result(a->ticket, &result)) abort();
/* A lent answer's body is its extents (the runtime charged them). */
if (result.status == PURE_OK && a->extents)
- respond_typed(c, 200, "OK", "none", result.revision, a->type ? a->type : "application/octet-stream",
+ respond_typed(c, 200, "OK", "none", a->type ? a->type : "application/octet-stream",
a->extents, a->extent_count, result.body.size);
else if (a->refused) {
- respond(c, 503, "Service Unavailable", a->refused, 0, (PureSpan){0});
+ respond(c, 503, "Service Unavailable", a->refused, (PureSpan){0});
release_ticket(c);
- } else if (a->route != ROUTE_OBJECT) answer_app(c, result, a->route == ROUTE_VIEW, a->fault, a->type);
- else answer(c, result);
+ } else answer_app(c, result, a->route == ROUTE_VIEW, a->fault, a->type);
drive(c); /* most answers fit the socket buffer at once */
}
free(a->extents);
@@ -2812,11 +2725,9 @@ static void let_go_query(Held *held) {
}
/* Whether a's states are laid out as b's: names, kinds, what each refers to,
- * keys, and the records and document collections behind them. */
+ * keys, and the records behind them. */
static bool same_states(const PureModule *a, const PureModule *b) {
- if (a->states != b->states || a->records != b->records || a->record_fields != b->record_fields ||
- a->collections != b->collections)
- return false;
+ if (a->states != b->states || a->records != b->records || a->record_fields != b->record_fields) return false;
for (uint32_t i = 0; i < a->states; ++i)
if (strcmp(a->state_names[i], b->state_names[i]) || a->state_type[i] != b->state_type[i] ||
a->state_ref[i] != b->state_ref[i] || a->state_flags[i] != b->state_flags[i] || a->state_key[i] != b->state_key[i])
@@ -2828,8 +2739,6 @@ static bool same_states(const PureModule *a, const PureModule *b) {
for (uint32_t i = 0; i < a->record_fields; ++i)
if (strcmp(a->record_field_names[i], b->record_field_names[i]) || a->record_field_type[i] != b->record_field_type[i])
return false;
- for (uint32_t i = 0; i < a->collections; ++i)
- if (strcmp(a->collection_names[i], b->collection_names[i])) return false;
return true;
}
@@ -2919,7 +2828,7 @@ static void serve_bursts(Host *host) {
while (pure_runtime_take(&host->runtime, &effect) == PURE_TAKE_EFFECT)
host->jobs[count++] = (Job){host, effect, host->routes[effect.ticket.slot], NULL, NULL};
if (!count) return;
- if (!apps[0].owned) { /* the one app of the host, of documents alone: run here */
+ if (!apps[0].owned) { /* the one app of the host, without states or outputs: run here */
execute(&host->local, host->jobs, count, answer_burst, NULL);
continue;
}
@@ -3321,12 +3230,10 @@ static bool replies_borrow(const PureModule *m, uint32_t input) {
/* ---- Main ------------------------------------------------------------- */
-/* A module from its file for an app, which serves documents when it keeps
- * a keyed collection; false, with why, when it does not load or cannot be
- * served. The module the app had is kept (see retired_modules). */
+/* A module from its file for an app; false, with why, when it does not load
+ * or cannot be served. The module the app had is kept (see retired_modules). */
static bool load_module(App *app, const char *path) {
PureModule *m = calloc(1, sizeof(*m));
- bool documents;
char error[160];
if (!m) {
snprintf(app->why, sizeof(app->why), "Module: out of memory");
@@ -3337,17 +3244,9 @@ static bool load_module(App *app, const char *path) {
free(m);
return false;
}
- /* Documents are served when the module keeps a keyed collection: its keyed
- * messages show and edit read and write one by key. Without one, inputs
- * named show and edit are messages as any other. */
- int show = pure_module_input(m, "show"), edit = pure_module_input(m, "edit");
- documents = m->collections > 0;
- bool served = !documents || (show >= 0 && edit >= 0 && m->input[show].kind == PURE_KIND_MESSAGE && m->input[show].keyed &&
- m->input[edit].kind == PURE_KIND_MESSAGE && m->input[edit].keyed);
- if (!served) snprintf(app->why, sizeof(app->why), "Module: documents need keyed messages named show and edit");
+ bool served = true;
for (uint32_t i = 0; served && i < m->inputs; ++i) {
- bool replied = (documents && i == (uint32_t)edit) || (m->input[i].kind == PURE_KIND_MESSAGE && !m->input[i].keyed);
- if (replied && !replies_borrow(m, i)) {
+ if (m->input[i].kind == PURE_KIND_MESSAGE && !replies_borrow(m, i)) {
snprintf(app->why, sizeof(app->why), "Module: %s may reply only a field of its message", m->input_names[i]);
served = false;
}
@@ -3367,9 +3266,6 @@ static bool load_module(App *app, const char *path) {
retired_modules = kept;
}
app->module = m;
- app->documents = documents;
- app->show = (uint32_t)(show >= 0 ? show : 0);
- app->edit = (uint32_t)(edit >= 0 ? edit : 0);
return true;
}
@@ -3490,8 +3386,6 @@ static void take_moved(Host *host) {
* when the store does not open or the module does not start on it. */
static bool start_executor(Executor *ex, App *app) {
ex->module = app->module;
- ex->show = app->show;
- ex->edit = app->edit;
if (pure_store_open(app->store, &ex->store) != PURE_OK) {
snprintf(app->why, sizeof(app->why), "Cannot open the store");
return false;
@@ -3702,8 +3596,7 @@ static void stop_shard(Host *host) {
free(host->moved);
}
-/* The input a link's stream goes to: one taking messages (not keyed) of the
- * sender's shape, or, named its fields, one that takes them by name
+/* The input a link's stream goes to: one taking messages of the sender's shape, or, named its fields, one that takes them by name
* (pure_module_takes: each record is laid out anew for it). */
/* In a host of apps the name is APP/INPUT. What it finds is a binding: the
* app's place, the module it runs and the input in it, the listener
@@ -3725,7 +3618,7 @@ static int link_input(void *context, const char *name, uint32_t shape, const uns
}
const PureModule *m = atomic_load_explicit(&apps[a].live, memory_order_acquire);
int index = m ? pure_module_input(m, name) : -1;
- if (index < 0 || m->input[index].kind != PURE_KIND_MESSAGE || m->input[index].keyed) return -PURE_LINK_NO_INPUT;
+ if (index < 0 || m->input[index].kind != PURE_KIND_MESSAGE) return -PURE_LINK_NO_INPUT;
if (pure_module_shape(m, (uint32_t)index) != shape && !fields) return -PURE_LINK_OTHER_SHAPE;
if (pure_module_shape(m, (uint32_t)index) != shape && !pure_module_takes(m, (uint32_t)index, fields, size))
return -PURE_LINK_FIELDS_LACKED;
@@ -4527,7 +4420,6 @@ static bool change_app(App *app) {
return false;
}
ex->module = app->module;
- ex->show = app->show, ex->edit = app->edit;
memset(ex->next, 0, sizeof(ex->next));
memset(ex->trimmed, 0, sizeof(ex->trimmed));
memset(ex->held, 0, sizeof(ex->held));