From 8fe9ce66573be53ff56fe2a8afc693dabeae9585 Mon Sep 17 00:00:00 2001 From: eunhhu Date: Tue, 29 Sep 2026 18:04:24 +0100 Subject: Tick a stateless app's timers, and wait for them on the monotonic clock run_timers ticked nothing in an app without states, taking a timer to be able to change nothing else; but a tick may send, and the outbox is the app's state (the host gives such an app an owner for it). A timer that only sends never ticked (review 004, F4). Now an app ticks when it has states or outputs; one with neither runs on every connection thread and has nothing a tick could do. When a timer is due was reckoned on the wall clock: set back, a timer waited for the time it had reached again (review 004, an earlier P2). The next tick is now reckoned on the monotonic clock; events still see the wall clock (now). check.py's server_timers runs the review's heartbeat (a timer, an output, no state) and sees it tick, then again with the wall clock set back an hour 0.2 s in (build/clock-back.so, an LD_PRELOAD of clock_gettime): 29 ticks in 0.6 s, where the server before this stops at 9. --- tests/check.py | 35 ++++++++++++++++++++++++++++++++--- tests/clock-back.c | 23 +++++++++++++++++++++++ 2 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 tests/clock-back.c (limited to 'tests') diff --git a/tests/check.py b/tests/check.py index cbcf6cb..de4f066 100644 --- a/tests/check.py +++ b/tests/check.py @@ -55,10 +55,10 @@ def compile_module(aup, source_path, out): class HttpHost: """A Pure HTTP host on a fresh port; stops with its statistics.""" - def __init__(self, args): + def __init__(self, args, env=None): import select self.process = subprocess.Popen([str(a) for a in args] + ['--port', '0'], stdout=subprocess.PIPE, - stderr=subprocess.PIPE) + stderr=subprocess.PIPE, env=env) ready, _, _ = select.select([self.process.stdout], [], [], 10) assert ready, 'HTTP host did not start' line = self.process.stdout.readline() @@ -2329,10 +2329,18 @@ view stamped_view = stamped """ +HEARTBEAT_SOURCE = """input beat = every 20 ms +output pulse = message { value: number } +on beat { send pulse { value: 1 } } +view label = "heartbeat" +""" + + def server_timers(aup, pure, work): """A served app's timers tick on the server's clock and its events see the time they were applied, on one thread and with an owner; the ticks - are saved, so a restarted server goes on from them.""" + are saved, so a restarted server goes on from them. An app with no state + but an output ticks too: its sends are its state (review 004, F4).""" import time module, source = work / 'beat.pmod', work / 'beat.aup' source.write_text(BEAT_SOURCE) @@ -2358,6 +2366,27 @@ def server_timers(aup, pure, work): stats = host.stop() assert stats[0]['ticks'] >= 5, stats seen[threads] = beats + module, source = work / 'heartbeat.pmod', work / 'heartbeat.aup' + source.write_text(HEARTBEAT_SOURCE) + compile_module(aup, source, module) + host = HttpHost([pure / 'build/pure-serve', '--module', module, '--store', work / 'heartbeat']) + try: + time.sleep(0.45) + finally: + stats = host.stop() + assert stats[0]['ticks'] >= 10, stats + seen['stateless'] = stats[0]['ticks'] + # The wall clock set back an hour 0.2 s in (build/clock-back.so): the + # timers wait on the monotonic clock, so they go on ticking. + import os + host = HttpHost([pure / 'build/pure-serve', '--module', module, '--store', work / 'heartbeat-back'], + env={**os.environ, 'LD_PRELOAD': str(pure / 'build/clock-back.so')}) + try: + time.sleep(0.6) + finally: + stats = host.stop() + assert stats[0]['ticks'] >= 20, stats + seen['clock_set_back'] = stats[0]['ticks'] return seen diff --git a/tests/clock-back.c b/tests/clock-back.c new file mode 100644 index 0000000..50870ca --- /dev/null +++ b/tests/clock-back.c @@ -0,0 +1,23 @@ +#define _GNU_SOURCE +/* LD_PRELOAD: the wall clock (CLOCK_REALTIME) set back an hour from + * CLOCK_BACK_MS milliseconds (200 unless given) after the process first + * asks for it, as a person or NTP may set it. A program's timers must go on + * all the same: they wait on the monotonic clock (tests/check.py). */ +#include +#include +#include + +int clock_gettime(clockid_t id, struct timespec *t) { + static int (*real)(clockid_t, struct timespec *); + static long long began = -1; + if (!real) *(void **)&real = dlsym(RTLD_NEXT, "clock_gettime"); /* as POSIX's dlsym example does */ + int result = real(id, t); + if (result || id != CLOCK_REALTIME) return result; + struct timespec now; + real(CLOCK_MONOTONIC, &now); + long long ms = (long long)now.tv_sec * 1000 + now.tv_nsec / 1000000; + if (began < 0) began = ms; + const char *after = getenv("CLOCK_BACK_MS"); + if (ms - began >= (after ? atoll(after) : 200)) t->tv_sec -= 3600; + return result; +} -- cgit v1.2.3