diff options
Diffstat (limited to 'core')
| -rw-r--r-- | core/module.c | 53 | ||||
| -rw-r--r-- | core/module.h | 25 | ||||
| -rw-r--r-- | core/test-module.c | 78 |
3 files changed, 144 insertions, 12 deletions
diff --git a/core/module.c b/core/module.c index 2688395..a5bc3c5 100644 --- a/core/module.c +++ b/core/module.c @@ -165,7 +165,7 @@ static void write_i64(unsigned char *p, int64_t value) { const char *pure_fault_name(PureFault fault) { static const char *const names[PURE_FAULT_COUNT] = {"none", "divide", "non_finite", "clamp", "limit", "not_found", - "encoding", "exists", "memory", "overflow"}; + "encoding", "exists", "memory", "overflow", "work"}; return fault < PURE_FAULT_COUNT ? names[fault] : "none"; } @@ -1437,10 +1437,17 @@ typedef struct { static PureResult fault(PureApp *app, PureFault kind, uint32_t pc) { app->fault = kind; app->fault_pc = pc; - return kind == PURE_FAULT_LIMIT ? PURE_LIMIT : kind == PURE_FAULT_NOT_FOUND ? PURE_NOT_FOUND : + return kind == PURE_FAULT_LIMIT || kind == PURE_FAULT_WORK ? PURE_LIMIT : kind == PURE_FAULT_NOT_FOUND ? PURE_NOT_FOUND : kind == PURE_FAULT_EXISTS ? PURE_CONFLICT : kind == PURE_FAULT_MEMORY ? PURE_BUDGET : PURE_INVALID; } +/* Count what the unit running (an event, or a view's evaluation) is about + * to run, as written: false past the app's work budget. */ +static bool charge(PureApp *app, uint64_t instructions) { + app->work += instructions; + return !app->work_budget || app->work <= app->work_budget; +} + static bool reserve(PureArena *arena, size_t more) { if (more <= arena->capacity - arena->used) return true; size_t capacity = arena->capacity ? arena->capacity : 256; @@ -2598,6 +2605,7 @@ static PureResult run(const PureModule *m, PureValue *values, PureBlock block, E Slot high = stack[--sp], low = stack[--sp]; slice_of(list, m->state_key[arg >> 8 & 0xFF] - 1u, low, high, &from, &to); } + if (!charge(app, (uint64_t)inner.length * (to - from))) { kind = PURE_FAULT_WORK; goto failed; } /* as written */ if (!op->shape || !fold_shaped(m, values, op, list, &per, &fold)) { if (op->range) /* only the items a range of keys allows can count */ range_bounds(m, values, &m->range[op->range - 1], list, m->state_key[arg >> 8 & 0xFF] - 1u, &per, &from, &to); @@ -2628,7 +2636,13 @@ static PureResult run(const PureModule *m, PureValue *values, PureBlock block, E if (arg & PURE_EACH_SLICED) { /* the items with keys from low up to before high */ Slot high = stack[--sp], low = stack[--sp]; slice_of(app->lists[walk.list], m->state_key[walk.list] - 1u, low, high, &walk.at, &walk.end); - } else if (op->range) /* a where that bounds the key: only the items in bounds */ + } + size_t items = pure_list_count(app->lists[walk.list]); /* as written: what it starts with, the list only shrinking */ + if (!charge(app, (uint64_t)inner.length * ((walk.end < items ? walk.end : items) - walk.at))) { + kind = PURE_FAULT_WORK; + goto failed; + } + if (!(arg & PURE_EACH_SLICED) && op->range) /* a where that bounds the key: only the items in bounds */ range_bounds(m, values, &m->range[op->range - 1], app->lists[walk.list], m->state_key[walk.list] - 1u, &per, &walk.at, &walk.end); while (walk.at < walk.end && walk.at < pure_list_count(app->lists[walk.list])) { @@ -3083,6 +3097,13 @@ static void clear_rows(PureApp *app) { void pure_app_memory(PureApp *app, size_t bytes) { app->memory = bytes; } +void pure_app_work(PureApp *app, uint64_t instructions) { + app->work_budget = instructions; + for (unsigned i = 0; i < app->row_tables; ++i) app->rows[i].changes = UINT64_MAX; /* a filter refused before may pass */ +} + +PureFault pure_app_view_fault(const PureApp *app) { return app->view_fault; } + size_t pure_app_state_bytes(const PureApp *app) { size_t bytes = (app->outbox ? pure_list_size(app->outbox) : 0) + (app->heard ? pure_list_size(app->heard) : 0); for (uint32_t left = app->counted; left; left &= left - 1) { @@ -3108,9 +3129,12 @@ static PureResult dispatch(PureApp *app, uint32_t event, Event *ev) { memcpy(pending, app->values, sizeof(app->values)); app->fault = PURE_FAULT_NONE; app->fault_pc = UINT32_MAX; + app->work = 0; PureResult result = PURE_OK; for (uint32_t i = 0; i < m->handlers && result == PURE_OK; ++i) - if (m->handler[i].event == event) result = run(m, pending, m->handler[i].code, ev, NULL); + if (m->handler[i].event == event) + result = charge(app, m->handler[i].code.length) ? run(m, pending, m->handler[i].code, ev, NULL) + : fault(app, PURE_FAULT_WORK, m->handler[i].code.start); /* An edit needs the revision it was made against. A save rebases an * edited document and releasing texts may reclaim earlier text versions: * keep such a reply by copying it first. */ @@ -3420,7 +3444,11 @@ static PureResult evaluate(PureApp *app, uint32_t view, size_t row, Slot *out) { if (!argue(app, view, &ev)) return PURE_INVALID; PureFault fault = app->fault; uint32_t fault_pc = app->fault_pc; - PureResult result = run(app->module, values, app->module->view_code[view], &ev, out); + PureBlock code = app->module->view_code[view]; + app->work = 0; + app->fault = PURE_FAULT_NONE; + PureResult result = charge(app, code.length) ? run(app->module, values, code, &ev, out) : PURE_LIMIT; + app->view_fault = result == PURE_OK ? PURE_FAULT_NONE : app->fault != PURE_FAULT_NONE ? app->fault : PURE_FAULT_WORK; app->fault = fault; app->fault_pc = fault_pc; return result; @@ -3461,9 +3489,18 @@ static struct PureRows *filtered(PureApp *app, uint32_t table) { } if (rows->changes == app->changes && (!m->view_arguments[table] || rows->given == app->arguments_given)) return rows; rows->count = 0; + /* A unit of work of its own, counted as written: the filter for every item. */ + const PureList *list = app->lists[m->view_list[table]]; + app->work = 0; + rows->fault = charge(app, (uint64_t)m->view_code[table].length * pure_list_count(list)) ? PURE_FAULT_NONE : PURE_FAULT_WORK; + uint64_t work = app->work; + if (rows->fault != PURE_FAULT_NONE) { + rows->changes = app->changes; + rows->given = app->arguments_given; + return rows; + } /* A leaf of the list at a time when the filter has lane forms; a leaf * where an item would fault goes item by item, leaving that item out. */ - const PureList *list = app->lists[m->view_list[table]]; PureRun run; if (m->view_plan[table]) { double store[PURE_MODULE_STACK][PURE_LIST_RUN], scratch[PURE_LIST_RUN]; @@ -3488,6 +3525,7 @@ static struct PureRows *filtered(PureApp *app, uint32_t table) { } else for (size_t item = 0, items = pure_list_count(list); item < items; ++item) if (!keep_evaluated(app, table, rows, item)) return NULL; + app->work = work; /* the filter's, whichever items ran alone */ rows->changes = app->changes; rows->given = app->arguments_given; return rows; @@ -3501,6 +3539,7 @@ bool pure_app_row_item(PureApp *app, uint32_t table, size_t row, size_t *item) { return row < pure_list_count(app->lists[m->view_list[table]]); } struct PureRows *rows = filtered(app, table); + app->view_fault = rows ? rows->fault : PURE_FAULT_NONE; if (!rows || row >= rows->count) return false; *item = rows->items[row]; return true; @@ -3528,6 +3567,7 @@ bool pure_app_item_row(PureApp *app, uint32_t table, size_t item, size_t *row) { /* A view outside a table (column false), or a column and a row of its table. */ static PureResult view_of(PureApp *app, uint32_t view, bool column, size_t row, uint8_t type, Slot *slot) { const PureModule *m = app->module; + app->view_fault = PURE_FAULT_NONE; if (view >= m->views) return PURE_INVALID; uint8_t kind = m->view_type[view] == PURE_VIEW_BOOL ? PURE_VIEW_NUM : m->view_type[view]; /* a bool's value is 0 or 1 */ if (kind != type || (m->view_table[view] != 0) != column) return PURE_INVALID; @@ -3559,6 +3599,7 @@ size_t pure_app_rows(PureApp *app, uint32_t table) { const PureModule *m = app->module; if (table >= m->views || m->view_type[table] != PURE_VIEW_TABLE) return 0; struct PureRows *rows = filtered(app, table); + app->view_fault = rows ? rows->fault : PURE_FAULT_NONE; return rows ? rows->count : m->view_code[table].length ? 0 : pure_list_count(app->lists[m->view_list[table]]); } diff --git a/core/module.h b/core/module.h index e106fc7..c4919c9 100644 --- a/core/module.h +++ b/core/module.h @@ -153,14 +153,15 @@ typedef enum { PURE_EDGE_DOWN, PURE_EDGE_UP } PureEdge; /* EXISTS: an item with that key is already in the keyed list (the event * reports PURE_CONFLICT). MEMORY: the event would leave the states holding * more than the app's budget (pure_app_memory; it reports PURE_BUDGET). - * OVERFLOW: an i64 past its range. */ + * OVERFLOW: an i64 past its range. WORK: the event (or a view's evaluation) + * would run more than the app's work budget (pure_app_work; PURE_LIMIT). */ typedef enum { PURE_FAULT_NONE, PURE_FAULT_DIVIDE, PURE_FAULT_NON_FINITE, PURE_FAULT_CLAMP, PURE_FAULT_LIMIT, PURE_FAULT_NOT_FOUND, PURE_FAULT_ENCODING, PURE_FAULT_EXISTS, - PURE_FAULT_MEMORY, PURE_FAULT_OVERFLOW, PURE_FAULT_COUNT + PURE_FAULT_MEMORY, PURE_FAULT_OVERFLOW, PURE_FAULT_WORK, PURE_FAULT_COUNT } PureFault; /* A fault's name as hosts report it: none, divide, non_finite, clamp, limit, - * not_found, encoding, exists, memory, overflow. */ + * not_found, encoding, exists, memory, overflow, work. */ const char *pure_fault_name(PureFault); /* plan, shape and range are the runtime's, set at load: a fold whose block @@ -377,6 +378,8 @@ typedef struct { PureFault fault; uint32_t fault_pc; size_t memory; /* the budget of what the states hold (0: none): pure_app_memory */ + uint64_t work_budget, work; /* pure_app_work's budget (0: none), and what the unit running has used */ + PureFault view_fault; /* why the last view evaluated had no value (pure_app_view_fault) */ char why[128]; /* why the last start or replace was refused, when it says (else empty; a failed start keeps it) */ uint32_t counted; /* the states it counts, a bit each: lists (not a message's rows) and texts */ uint64_t ticks, events; @@ -384,7 +387,7 @@ typedef struct { * sets it, so a script can replay any time; views never see it. */ double now; uint64_t changes; /* counts every change of states, never back: what a filter's rows are fresh for */ - struct PureRows { uint32_t table; uint64_t changes, given; size_t *items, count, capacity; } *rows; /* a filtered table's items */ + struct PureRows { uint32_t table; uint64_t changes, given; size_t *items, count, capacity; PureFault fault; } *rows; /* a filtered table's items */ unsigned row_tables; /* Current message or text event, kept until pure_app_finish. */ void *document; @@ -544,6 +547,20 @@ void pure_app_memory(PureApp *, size_t bytes); /* What the states hold now, as the budget counts it. */ size_t pure_app_state_bytes(const PureApp *); +/* An app's work budget (docs/design/017-apps-on-one-host.md): what one + * event, or one evaluation of a view (a value, a cell, a table's filter), + * may run, in instructions counted as written, whatever the plan: a block's + * length each time it runs, and a fold's or a walk's block length for each + * item of its list (of its slice, when it takes one) it goes over, all + * counted as the fold or walk begins. Past it the event fails with the fault + * PURE_FAULT_WORK (PURE_LIMIT) and changes nothing, and a view has no value. + * 0: no budget, at first. */ +void pure_app_work(PureApp *, uint64_t instructions); +/* Why the last view evaluated on this app (or a table's rows asked for) + * had no value: PURE_FAULT_WORK past the work budget, another fault, or + * PURE_FAULT_NONE when it had one. */ +PureFault pure_app_view_fault(const PureApp *); + /* Readers on other threads. Right after a save an app can make a snapshot of * its saved states, for evaluating views anywhere while it keeps applying * events: numbers are copied; text states share their stored versions and 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(); |
