fix: --stall-after Budget der Rettungsstation client-kumulativ zählen
Der Testschalter zählte bisher pro Anfrage, sodass eine Lampe nach ihrem Empfangs-Timeout einfach per Range neu verband und ein frisches Budget bekam – am EVK reichten fünf Reconnects, um trotz --stall-after das komplette Image zu übertragen. Station verfolgt die ausgelieferten Bytes jetzt pro Client-IP über alle Anfragen hinweg (Station.reserve_stall_bytes, unter dem bestehenden Lock), sodass kein erneutes Verbinden das Budget auffüllt. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
2f25714248
commit
3f27ef4b26
@@ -278,6 +278,62 @@ def test_stall_after_stops_sending_but_keeps_the_connection(tmp_path):
|
||||
s.stop()
|
||||
|
||||
|
||||
def test_stall_after_budget_survives_a_resumed_request(tmp_path):
|
||||
"""--stall-after must stop a client for good, not just for one request.
|
||||
|
||||
On real hardware, a lamp whose download stalls hits its own receive timeout, gives up
|
||||
on the connection, and opens a new one with a Range header picking up right where it
|
||||
left off - exactly what test_stall_after_stops_sending_but_keeps_the_connection above
|
||||
exercises for a single request. A per-request byte counter would hand that resumed
|
||||
request a brand-new --stall-after allowance, so the "stall" would only ever delay
|
||||
delivery instead of actually stopping it - which is what was observed on the EVK: the
|
||||
server stalled at 65536 bytes each time, but the lamp reconnected five times and walked
|
||||
away with the complete image. This test reproduces that reconnect and checks that the
|
||||
second, resumed request gets no further bytes at all.
|
||||
"""
|
||||
s = Station(tmp_path, "--stall-after", "1000")
|
||||
try:
|
||||
make_image(s.image_dir / "ninlight_evk_ota.img", "evk", "1.0.0", b"y" * 200000)
|
||||
|
||||
# First request: burns through the whole --stall-after budget, then the server
|
||||
# holds the connection open and sends nothing further.
|
||||
sock = socket.create_connection(("127.0.0.1", s.port), timeout=5)
|
||||
sock.sendall(b"GET /ninlight_evk_ota.img HTTP/1.1\r\nHost: x\r\n\r\n")
|
||||
got = b""
|
||||
sock.settimeout(2)
|
||||
try:
|
||||
while len(got) < 200000:
|
||||
chunk = sock.recv(65536)
|
||||
if not chunk:
|
||||
break
|
||||
got += chunk
|
||||
except socket.timeout:
|
||||
pass
|
||||
assert len(got) < 20000 # far less than the full image, same bound as above
|
||||
sock.close() # the lamp's watchdog gives up and reconnects, like on real hardware
|
||||
|
||||
# Second request, resuming with Range exactly like a real lamp would. This
|
||||
# client's cumulative budget is already spent, so it must get zero more bytes of
|
||||
# body - not a fresh --stall-after allowance just because it's a new connection.
|
||||
sock2 = socket.create_connection(("127.0.0.1", s.port), timeout=5)
|
||||
sock2.sendall(b"GET /ninlight_evk_ota.img HTTP/1.1\r\nHost: x\r\nRange: bytes=1000-\r\n\r\n")
|
||||
sock2.settimeout(2)
|
||||
header = b""
|
||||
while b"\r\n\r\n" not in header:
|
||||
header += sock2.recv(4096)
|
||||
assert b" 206 " in header.split(b"\r\n", 1)[0]
|
||||
body_after_header = header.split(b"\r\n\r\n", 1)[1]
|
||||
assert body_after_header == b"", "resumed request must not receive any body bytes"
|
||||
try:
|
||||
more = sock2.recv(1)
|
||||
except socket.timeout:
|
||||
more = None
|
||||
assert more in (None, b""), "server sent data after the client's stall budget was spent"
|
||||
sock2.close()
|
||||
finally:
|
||||
s.stop()
|
||||
|
||||
|
||||
def test_concurrent_downloads_are_all_recorded(station):
|
||||
"""Several lamps hitting the station at once must not lose or corrupt status.json.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user