3proxy/tests/cases/loop.py
Vladimir Dubrovin cc98c498fe An event loop: sockets and timers for one thread
src/loop.c, not used yet: one thread waiting for many sockets and
timers, calling back what waits for them, for clients to be served by
a thread each serves many of.

Sockets are waited for with epoll on Linux, kqueue on the BSDs and
macOS, poll() (WSAPoll() on Windows) elsewhere or with LOOP_POLL,
level-triggered; one with nothing to wait for is taken from the system,
or an error or a hang up would be reported all the same. One deleted
while the sockets ready are being called is not called.

Timers wait in a wheel of 1024 one-second slots. One due within a turn
waits at the head of the slot of its second, all there due in the same
second; one due later behind them, in the order of their seconds, or,
if due before the last there, at the head of the last slot of the
turn, looked at again then. Moving a timer later costs nothing while
it waits in the wheel: it is looked at when its old deadline comes and
moved then. When its slot comes a timer due within the second waits in
a heap, so it fires to the millisecond, as does one set within the
second. The clock is monotonic.

Another thread posts to the loop, waking it with an eventfd, a pipe, or
on Windows a UDP socket sending to itself.

tests/looptest.c tests it on its own, timers on a clock of its own
through several turns of the wheel: built by CMake, run by the loop
case.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 21:30:13 +03:00

31 lines
1.2 KiB
Python

"""The event loop on its own: tests/looptest.c, built by CMake next to the
3proxy binary, run with a few seeds for its timers. Skipped where it was not
built (the Makefiles do not build it)."""
import os
import subprocess
def run(t):
name = "looptest.exe" if os.name == "nt" else "looptest"
path = os.path.join(os.path.dirname(os.path.abspath(t.binary)), name)
if not os.path.exists(path):
t.skip("looptest is built by CMake only")
return
for seed in ("1", "2", "3"):
try:
done = subprocess.run([path, seed], stdout=subprocess.PIPE,
stderr=subprocess.STDOUT, timeout=120)
except subprocess.TimeoutExpired:
t.fail(f"looptest {seed} finishes")
continue
out = done.stdout.decode(errors="replace")
for line in out.splitlines():
if line.startswith("ok "):
t.ok(f"seed {seed}: {line[3:]}")
elif line.startswith("FAIL "):
label, _, what = line[5:].partition(": ")
t.fail(f"seed {seed}: {label}", actual=what)
if done.returncode and "FAIL " not in out:
t.fail(f"looptest {seed} exits cleanly", 0, done.returncode)