aboutsummaryrefslogtreecommitdiffstats
path: root/core/test-module.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 /core/test-module.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 'core/test-module.c')
-rw-r--r--core/test-module.c78
1 files changed, 76 insertions, 2 deletions
diff --git a/core/test-module.c b/core/test-module.c
index 4fe5182..f627dea 100644
--- a/core/test-module.c
+++ b/core/test-module.c
@@ -2692,13 +2692,14 @@ static void lane_folds(void) {
double x = 0, y = 0;
bool ok_a = pure_app_view(&a, v, &x), ok_b = pure_app_view(&b, v, &y);
faults += !ok_a;
+ CHECK(a.work == b.work); /* counted as written, however it ran */
if (!same_number(ok_a, x, ok_b, y)) {
fprintf(stderr, "lane round %u view %u: %d %g / %d %g\n", round, v, ok_a, x, ok_b, y);
abort();
}
}
size_t rows = pure_app_rows(&a, VIEWS);
- CHECK(rows == pure_app_rows(&b, VIEWS));
+ CHECK(rows == pure_app_rows(&b, VIEWS) && a.work == b.work);
for (size_t row = 0; row < rows; ++row) {
size_t item_a, item_b;
CHECK(pure_app_row_item(&a, VIEWS, row, &item_a) && pure_app_row_item(&b, VIEWS, row, &item_b) && item_a == item_b);
@@ -2706,6 +2707,7 @@ static void lane_folds(void) {
double x = 0, y = 0;
bool ok_a = pure_app_cell(&a, VIEWS + 1 + c, row, &x), ok_b = pure_app_cell(&b, VIEWS + 1 + c, row, &y);
++cells;
+ CHECK(a.work == b.work);
if (!same_number(ok_a, x, ok_b, y)) {
fprintf(stderr, "lane round %u column %u row %zu: %d %g / %d %g\n", round, c, row, ok_a, x, ok_b, y);
abort();
@@ -2714,7 +2716,8 @@ static void lane_folds(void) {
}
passes += pure_app_passes(&a);
PureResult went_a = pure_app_input(&a, 0, PURE_EDGE_DOWN), went_b = pure_app_input(&b, 0, PURE_EDGE_DOWN);
- if (went_a != went_b || a.fault != b.fault || a.fault_pc != b.fault_pc || memcmp(a.values, b.values, 4 * sizeof(double))) {
+ if (went_a != went_b || a.fault != b.fault || a.fault_pc != b.fault_pc || a.work != b.work ||
+ memcmp(a.values, b.values, 4 * sizeof(double))) {
fprintf(stderr, "lane round %u handler: %d %d %u / %d %d %u\n", round, went_a, a.fault, a.fault_pc, went_b, b.fault,
b.fault_pc);
abort();
@@ -4334,6 +4337,76 @@ static void links(void) {
* nothing; one that frees them goes on; sends waiting for their receivers
* count, so a full outbox refuses more until some are trimmed; a delivery
* refused so is not heard, and comes again; an answer has its send's room. */
+/* ---- Work budgets (docs/design/017) ------------------------------------
+ * An event, or one evaluation of a view, runs at most the app's work
+ * budget, counted as written as it begins: a block's length, and a fold's or
+ * a walk's block length for every item of its list. Past it an event fails
+ * with work and changes nothing; a view, a cell or a table's rows have no
+ * value, and pure_app_view_fault says why. (lane_folds checks that a planned
+ * fold counts what the same fold run item by item counts.) */
+static void work_budget(void) {
+ CHECK(!strcmp(pure_fault_name(PURE_FAULT_WORK), "work"));
+ /* The shop: restock runs 3 instructions; clean 23, and its walk's 22 for each item stocked. */
+ CHECK(load(walk_spec(walk_code, LEN(walk_code), walk_inputs, 3, walk_handlers, 3, walk_lists, NULL, 0), &module_a) == PURE_OK);
+ PureApp app;
+ CHECK(pure_app_start(&app, &module_a, (PureData){0}) == PURE_OK);
+ unsigned char payload[8];
+ for (uint32_t item = 0; item < 5; ++item) {
+ put32(put32(payload, item), 2);
+ CHECK(pure_app_message(&app, 2, NULL, -1, (PureSpan){payload, 8}) == PURE_OK && app.work == 3);
+ }
+ pure_app_work(&app, 23 + 22 * 5 - 1);
+ CHECK(pure_app_input(&app, 1, PURE_EDGE_DOWN) == PURE_LIMIT && app.fault == PURE_FAULT_WORK);
+ for (size_t i = 0; i < 5; ++i) CHECK(pure_list_get(app.lists[W_STOCK], i, 1).num == 2); /* nothing changed */
+ pure_app_work(&app, 23 + 22 * 5);
+ CHECK(pure_app_input(&app, 1, PURE_EDGE_DOWN) == PURE_OK && app.work == 23 + 22 * 5);
+ for (size_t i = 0; i < 5; ++i) CHECK(pure_list_get(app.lists[W_STOCK], i, 1).num == 3);
+ pure_app_work(&app, 2);
+ put32(put32(payload, 9), 1);
+ CHECK(pure_app_message(&app, 2, NULL, -1, (PureSpan){payload, 8}) == PURE_LIMIT && app.fault == PURE_FAULT_WORK);
+ CHECK(pure_list_count(app.lists[W_STOCK]) == 5);
+ pure_app_work(&app, 0); /* none */
+ CHECK(pure_app_message(&app, 2, NULL, -1, (PureSpan){payload, 8}) == PURE_OK && pure_list_count(app.lists[W_STOCK]) == 6);
+ pure_app_stop(&app);
+ pure_module_release(&module_a);
+
+ /* Views: open is 4 instructions and its fold's 3 for each task. */
+ CHECK(load(fold_spec(fold_code, LEN(fold_code), fold_views, LEN(fold_views), fold_handlers, 2), &module_a) == PURE_OK);
+ CHECK(pure_app_start(&app, &module_a, (PureData){0}) == PURE_OK);
+ for (const char *word = "abc"; *word; ++word)
+ CHECK(pure_app_text(&app, 0, (PureSpan){(const unsigned char *)word, 1}) == PURE_OK && app.work == 7);
+ double value;
+ CHECK(number_view(&app, 0) == 3 && app.work == 4 + 3 * 3 && pure_app_view_fault(&app) == PURE_FAULT_NONE);
+ pure_app_work(&app, 4 + 3 * 3 - 1);
+ CHECK(!pure_app_view(&app, 0, &value) && pure_app_view_fault(&app) == PURE_FAULT_WORK);
+ CHECK(app.fault == PURE_FAULT_NONE); /* a view leaves the app's own fault alone */
+ pure_app_work(&app, 4 + 3 * 3);
+ CHECK(number_view(&app, 0) == 3 && pure_app_view_fault(&app) == PURE_FAULT_NONE);
+ pure_app_work(&app, 0);
+ CHECK(pure_app_input(&app, 1, PURE_EDGE_DOWN) == PURE_OK && pure_app_point(&app, 1, 0) == PURE_OK &&
+ pure_app_input(&app, 1, PURE_EDGE_DOWN) == PURE_OK && pure_app_point(&app, 1, 1) == PURE_OK &&
+ pure_app_input(&app, 1, PURE_EDGE_DOWN) == PURE_OK); /* all done */
+ CHECK(!pure_app_view(&app, 4, &value) && pure_app_view_fault(&app) == PURE_FAULT_NOT_FOUND); /* fewest open: none */
+ pure_app_stop(&app);
+ pure_module_release(&module_a);
+
+ /* A table's filter is a unit of its own: 3 instructions for each task; a cell's column is 2. */
+ CHECK(load(filter_spec(filter_code, LEN(filter_code), filter_views, 3), &module_a) == PURE_OK);
+ CHECK(pure_app_start(&app, &module_a, (PureData){0}) == PURE_OK);
+ for (const char *word = "abcd"; *word; ++word)
+ CHECK(pure_app_text(&app, 0, (PureSpan){(const unsigned char *)word, 1}) == PURE_OK);
+ pure_app_work(&app, 3 * 4 - 1);
+ size_t at;
+ CHECK(pure_app_rows(&app, 0) == 0 && pure_app_view_fault(&app) == PURE_FAULT_WORK && !pure_app_row_item(&app, 0, 0, &at));
+ CHECK(!pure_app_cell(&app, 2, 0, &value) && pure_app_view_fault(&app) == PURE_FAULT_WORK);
+ pure_app_work(&app, 3 * 4); /* the rows refused before are asked again */
+ CHECK(pure_app_rows(&app, 0) == 4 && app.work == 3 * 4 && pure_app_view_fault(&app) == PURE_FAULT_NONE);
+ CHECK(pure_app_cell(&app, 2, 3, &value) && value == 3 && app.work == 2);
+ pure_app_stop(&app);
+ pure_module_release(&module_a);
+ puts("{\"work_budget\":\"events-views-cells-rows-rolled-back\"}");
+}
+
static void memory_budget(void) {
enum { ADD, DROP, SEND_KEY, OUT, TAKE, ANSWER };
static const S nums[] = {{"n", 0}};
@@ -5166,6 +5239,7 @@ int main(void) {
bools();
integers();
memory_budget();
+ work_budget();
fold_mutations();
pointing();
list_mutations();