aboutsummaryrefslogtreecommitdiffstats
path: root/serve/serve.c
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-10-01 02:19:04 +0100
committereunhhu <[email protected]>2026-10-01 02:19:04 +0100
commit2a0f601cbd3ea6af1bb031b29a07d5facb0a4579 (patch)
tree70b134543c91bb3e2b0e3b344e91896bad1d9973 /serve/serve.c
parenta09ef095623c3b3f951edff2eb0c013708159f70 (diff)
downloadpure-2a0f601cbd3ea6af1bb031b29a07d5facb0a4579.tar.gz
pure-2a0f601cbd3ea6af1bb031b29a07d5facb0a4579.zip
Bound what an event or a view may run: a work budget (017)
An event's run was bounded only by its lists: a walk or a fold is its block's length for each item, and a list holds up to 16 million. One event could hold the owner for seconds, one view a connection thread. pure_app_work(app, n) budgets one unit of work: an event, or one evaluation of a view, a cell or a table's filter. It is counted as written, as each part begins: a block's length, and a fold's or walk's block length for every item of its list (or slice), whatever the plan runs (lanes, a group or rank pass, a range). Nothing is counted inside the interpreter's loop. Past the budget an event fails with the new fault work and changes nothing (PURE_LIMIT, so 422 work over HTTP); a view, a cell or a table's rows have no value and pure_app_view_fault says work, which pure-serve answers 422 work, for a whole table when a cell of it ran past. pure-serve --work N sets it (0: none). test-module counts exact amounts on the shop's walk, the tasks' fold view and a filtered table, and lane_folds now checks that a planned fold counts what the same fold run item by item counts (3,600 folds, every view, cell, filter and handler). server_work grows a list over HTTP until the walk, the table and the fold each pass a budget of 400.
Diffstat (limited to 'serve/serve.c')
-rw-r--r--serve/serve.c39
1 files changed, 27 insertions, 12 deletions
diff --git a/serve/serve.c b/serve/serve.c
index 6d9d5d6..b283874 100644
--- a/serve/serve.c
+++ b/serve/serve.c
@@ -83,6 +83,8 @@ static PureSealKey *seal_key;
static bool sealed_only;
static struct Sender { char name[PURE_SOURCE + 1]; unsigned char key[PURE_SEAL_KEY]; } senders[PURE_MODULE_INPUTS];
static unsigned sender_count;
+static size_t memory_budget; /* --memory: the app's budget of what its states hold (0: none) */
+static uint64_t work_budget; /* --work: what an event or a view's evaluation may run (0: none) */
/* A delivery's X-Pure-Seal: the suite, the sender's session (its
* encapsulated key), the seal's number in it and, when the sender sealed
@@ -1333,6 +1335,7 @@ static PureResult show_given(PureApp *app, const Route *route, size_t available,
} else {
*type = "application/json";
size_t rows = pure_app_rows(app, view), from = route->from < rows ? route->from : rows;
+ if (pure_app_view_fault(app) == PURE_FAULT_WORK) status = PURE_LIMIT, rows = from = 0; /* its filter's */
size_t end = from + (route->count < rows - from ? route->count : rows - from);
/* Each column's key, ,"name": (the first row's first without the comma), made once. */
struct { uint32_t view; size_t length; char key[PURE_MODULE_NAME + 4]; } columns[PURE_MODULE_VIEWS];
@@ -1355,26 +1358,33 @@ static PureResult show_given(PureApp *app, const Route *route, size_t available,
int64_t integer;
if (!pure_app_cell_i64(app, columns[k].view, row, &integer)) put_bytes(&o, "null", 4);
else put_integer(&o, integer);
- continue;
- }
- if (kind == PURE_VIEW_NUM || kind == PURE_VIEW_BOOL) {
+ } else if (kind == PURE_VIEW_NUM || kind == PURE_VIEW_BOOL) {
if (!pure_app_cell(app, columns[k].view, row, &value)) put_bytes(&o, "null", 4);
else if (kind == PURE_VIEW_BOOL) put_bytes(&o, value ? "true" : "false", value ? 4 : 5);
else put_number(&o, value);
- continue;
+ } else {
+ size_t mark = o.size;
+ put_bytes(&o, "\"", 1);
+ if (pure_app_cell_text(app, columns[k].view, row, put_json_span, &o) == PURE_OK) put_bytes(&o, "\"", 1);
+ else if (!o.failed) {
+ o.size = mark;
+ put_bytes(&o, "null", 4);
+ }
}
- size_t mark = o.size;
- put_bytes(&o, "\"", 1);
- if (pure_app_cell_text(app, columns[k].view, row, put_json_span, &o) == PURE_OK) put_bytes(&o, "\"", 1);
- else if (!o.failed) {
- o.size = mark;
- put_bytes(&o, "null", 4);
+ if (pure_app_view_fault(app) == PURE_FAULT_WORK) { /* a cell past the work budget fails the table */
+ status = PURE_LIMIT;
+ end = row;
+ break;
}
}
put_bytes(&o, "}", 1);
}
put_bytes(&o, "]}", 2);
}
+ if (status != PURE_OK && pure_app_view_fault(app) == PURE_FAULT_WORK) { /* past the work budget: 422 work */
+ answer->fault = PURE_FAULT_WORK;
+ status = PURE_LIMIT;
+ }
if (status == PURE_OK && !o.bytes && !(o.bytes = malloc(1))) o.failed = true; /* an empty value has a buffer too */
if (status == PURE_OK && o.failed) status = PURE_ALLOCATION;
if (status == PURE_OK && o.size > available) status = PURE_BUDGET;
@@ -1925,6 +1935,7 @@ static PureResult run_query(const Job *job, PureSnapshot *snapshot, Applied *a)
}
PureApp reader = {0};
pure_app_read(&reader, snapshot);
+ pure_app_work(&reader, work_budget);
reader.module = &held->module; /* the app's states, the query's views */
Route route = job->route;
route.target = (uint32_t)pure_module_view(&held->module, job->effect.key);
@@ -2262,7 +2273,6 @@ static void accept_all(Host *host) {
}
}
-static size_t memory_budget; /* --memory: the app's budget of what its states hold (0: none) */
static bool start_executor(Executor *ex, const PureModule *module, uint32_t show, uint32_t edit, const char *store) {
ex->module = module;
@@ -2275,6 +2285,7 @@ static bool start_executor(Executor *ex, const PureModule *module, uint32_t show
}
pure_app_background_checkpoints(&ex->app, true);
pure_app_memory(&ex->app, memory_budget);
+ pure_app_work(&ex->app, work_budget);
return true;
}
@@ -2294,6 +2305,7 @@ static bool start_shard(Host *host, const PureModule *module, bool documents, ui
host->module = module;
host->documents = documents;
host->wake = -1;
+ pure_app_work(&host->reader, work_budget); /* the views it shows from snapshots */
if (!host->owner && !start_executor(&host->local, module, show, edit, store)) return false;
host->work = calloc(host->capacity, sizeof(*host->work));
host->table = calloc(host->capacity, sizeof(*host->table));
@@ -2683,6 +2695,7 @@ int main(int argc, char **argv) {
else if (!strcmp(argv[i], "--link-idle-ms") && number(argv[i + 1], 86400000, &value) && value) link_idle_ms = value;
else if (!strcmp(argv[i], "--budget") && number(argv[i + 1], SIZE_MAX, &value) && value) budget = (size_t)value;
else if (!strcmp(argv[i], "--memory") && number(argv[i + 1], SIZE_MAX, &value) && value) memory_budget = (size_t)value;
+ else if (!strcmp(argv[i], "--work") && number(argv[i + 1], UINT64_MAX, &value) && value) work_budget = value;
else if (!strcmp(argv[i], "--window") && number(argv[i + 1], SIZE_MAX, &value) && value) window_bytes = (size_t)value;
else if (!strcmp(argv[i], "--connections") && number(argv[i + 1], 1u << 20, &value) && value) capacity = (size_t)value;
else if (!strcmp(argv[i], "--threads") && !strcmp(argv[i + 1], "auto")) threads = 0;
@@ -2874,7 +2887,7 @@ usage:
fputs("pure-serve --module FILE.pmod --store DIRECTORY [--port 8767] [--threads 1|N|auto] [--budget BYTES] "
"[--connections 1024] [--timeout-ms 10000] [--send OUTPUT=URL ...] [--name NAME] "
"[--send-ca FILE] [--reply OUTPUT=INPUT ...] [--seal OUTPUT=KEY ...] [--key FILE] [--sealed-only] "
- "[--sender NAME=KEY ...] [--link-port PORT] [--senders FILE] [--link-idle-ms 30000] [--memory BYTES] "
+ "[--sender NAME=KEY ...] [--link-port PORT] [--senders FILE] [--link-idle-ms 30000] [--memory BYTES] [--work N] "
"[--window 4194304] [--surface LIBRARY [-- ARGUMENTS]]\n"
" --send OUTPUT=pure://host:port/INPUT: to a Pure node's --link-port, the way between nodes (UDP, no\n"
" connection, docs/design/012-link-protocol.md; its key by --seal OUTPUT=KEY)\n"
@@ -2884,6 +2897,8 @@ usage:
" .pmod unless given; the first line out says it)\n"
" --memory BYTES: what the app's states may hold (lists' items, texts, sends not yet stored): an event\n"
" past it is refused, 503 memory (docs/design/013-memory-budget.md)\n"
+ " --work N: what an event, or one evaluation of a view, may run, in instructions counted as written: past\n"
+ " it the event is refused and the view has no value, 422 work (docs/design/017-apps-on-one-host.md)\n"
" --window BYTES: what each --send holds of its sends beyond what its receiver has (and 65,536 sends);\n"
" the outbox keeps the rest\n", stderr);
return 2;