3proxy/tests/cases/maxconn.py
Vladimir Dubrovin 98e8dfb004
Some checks are pending
C/C++ CI Linux / ${{ matrix.target }} (ubuntu-24.04-arm) (push) Waiting to run
C/C++ CI Linux / ${{ matrix.target }} (ubuntu-latest) (push) Waiting to run
C/C++ CI MacOS / ${{ matrix.target }} (macos-15) (push) Waiting to run
C/C++ CI Windows / ${{ matrix.target }} (windows-2022) (push) Waiting to run
C/C++ CI cmake / ${{ matrix.target }} (macos-15) (push) Waiting to run
C/C++ CI cmake / ${{ matrix.target }} (ubuntu-24.04-arm) (push) Waiting to run
C/C++ CI cmake / ${{ matrix.target }} (ubuntu-latest) (push) Waiting to run
C/C++ CI cmake / ${{ matrix.target }} (windows-2022) (push) Waiting to run
C/C++ CI cmake / ubuntu-latest (wolfSSL) (push) Waiting to run
Change waiting loops to semaphores for child counters
2026-10-10 23:10:57 +03:00

108 lines
3.7 KiB
Python

"""maxconn: a service serves at most that many connections at once, and a
connection over the limit waits for one of them to finish."""
import os
import signal
import socket
import time
def run(t):
# The origin runs in a process of its own, so the limit only applies to
# the mapper in front of it.
origin = t.free_port()
t.start("maxconn_origin", f"""
auth iponly
allow *
http echo * /echo**
httpsrv -p{origin}
""", ports=[origin])
mapped = t.free_port()
server = t.start("maxconn", f"""
log
maxconn 2
auth iponly
allow *
tcppm {mapped} 127.0.0.1 {origin}
""", ports=[mapped])
def connect():
sock = socket.create_connection(("127.0.0.1", mapped), 5)
opened.append(sock)
return sock
def served(sock, timeout):
"""Ask over a kept alive connection; True once the whole reply is in,
so the connection is ready for the next request."""
sock.sendall(b"GET /echo HTTP/1.1\r\nHost: 127.0.0.1\r\n\r\n")
sock.settimeout(timeout)
data = b""
try:
while b"\r\n\r\n" not in data:
chunk = sock.recv(4096)
if not chunk:
return False
data += chunk
head, body = data.split(b"\r\n\r\n", 1)
length = 0
for line in head.split(b"\r\n")[1:]:
name, _, value = line.partition(b":")
if name.strip().lower() == b"content-length":
length = int(value)
while len(body) < length:
chunk = sock.recv(4096)
if not chunk:
return False
body += chunk
return head.startswith(b"HTTP/1.1 200")
except OSError:
return False
opened = []
try:
# the probe that saw the port open holds a slot until its session
# ends, so wait for both slots to be free before taking them
deadline = time.time() + 5
while True:
first, second = connect(), connect()
both = served(first, 1) and served(second, 1)
if both or time.time() > deadline:
break
first.close()
second.close()
t.eq(True, both, "two connections are served under maxconn 2")
third = connect()
t.eq(False, served(third, 1.5),
"a third connection waits while both slots are held")
t.contains(t.wait_output(server, "too many connected clients"),
"too many connected clients",
"a service at its limit says so in the log")
first.close()
t.eq(True, served(third, 5),
"the waiting connection is served once a slot is freed")
t.eq(True, served(second, 5),
"a connection holding a slot keeps being served")
if os.name == "nt":
t.skip("reload while at the limit (no SIGUSR1 on Windows)")
else:
waiting = connect()
t.eq(False, served(waiting, 1),
"the limit holds again with two connections open")
server.proc.send_signal(signal.SIGUSR1)
# A connection made the instant the signal arrives can land on
# the listener being replaced and be reset; give the new service
# the moment it needs.
time.sleep(1.5)
fresh = connect()
t.eq(True, served(fresh, 5),
"a reload while the limit is reached starts a service with "
"its slots free")
t.eq(None, server.proc.poll(), "the process survives the reload")
finally:
for sock in opened:
sock.close()