From 2a0f601cbd3ea6af1bb031b29a07d5facb0a4579 Mon Sep 17 00:00:00 2001 From: eunhhu Date: Thu, 1 Oct 2026 02:19:04 +0100 Subject: 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. --- tests/check.py | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) (limited to 'tests') diff --git a/tests/check.py b/tests/check.py index b982256..47ddf23 100644 --- a/tests/check.py +++ b/tests/check.py @@ -3659,6 +3659,73 @@ def server_meaning(aup, pure, work): return {'values': len(expected), 'refused': refused, 'exits': len(exits), 'checkpoints': checkpoints} +WORK_SOURCE = """record Item { + n = 0 +} +state items: list of Item +input add = message { n: number } +input touch = message { step: number } +on add { + items.append(Item { n: add.n }) +} +on touch { + for i in items { + i.n = i.n + touch.step + } +} +view total = sum(i.n for i in items) +view big = each i in items where i.n > 0 { + n = i.n +} +""" + + +def server_work(aup, pure, work): + """A work budget (docs/design/017) over HTTP: as the list grows, the + walking event, the folding view and the filtered table each come to run + past the budget, from then on answer 422 work, and a refused event + changes nothing; the cheap event that adds an item still runs.""" + source = work / 'work.aup' + source.write_text(WORK_SOURCE) + compile_module(aup, source, work / 'work.pmod') + host = HttpHost([pure / 'build/pure-serve', '--module', work / 'work.pmod', '--store', work / 'work-store', '--work', 400]) + first = {} + try: + def total(): + status, _, _, body = http(host.port, 'GET', '/views/total') + return status, body + + for n in range(1, 200): + status, why, _, _ = http(host.port, 'POST', '/inputs/add?n=1') + assert status == 200, (n, status, why) # adding stays cheap + before = total() + status, why, _, _ = http(host.port, 'POST', '/inputs/touch?step=1') + assert status in (200, 422) and (status == 200 or why == 'work'), (n, status, why) + if status == 422: + first.setdefault('touch', n) + assert total() == before, n # refused: nothing changed + else: + assert 'touch' not in first, n # once past the budget, always past it + status, why, _, body = http(host.port, 'GET', '/views/total') + assert status == 200 or (status, why) == (422, 'work'), (n, status, why) + if status == 422: + first.setdefault('total', n) + else: + assert 'total' not in first, n + status, why, _, body = http(host.port, 'GET', '/views/big') + assert status == 200 or (status, why) == (422, 'work'), (n, status, why) + if status == 422: + first.setdefault('big', n) + else: + assert 'big' not in first and json.loads(body)['rows'] == n, (n, body[:80]) + if len(first) == 3: + break + assert len(first) == 3 and first['touch'] < first['big'], first # a walk's block is longer than a filter's + finally: + host.stop() + return first + + def main(): global EDITOR parser = argparse.ArgumentParser() @@ -3707,6 +3774,7 @@ def main(): report['server_answers'] = server_answers(aup, pure, work) report['server_answers_memory'] = server_answers_memory(aup, pure, work) report['server_meaning'] = server_meaning(aup, pure, work) + report['server_work'] = server_work(aup, pure, work) print(json.dumps(report)) -- cgit v1.2.3