commit 9ffb37cdb97adb31b9dbee3579f450d722859e6b
| author | 斟酌 鵬兄 <tgckpg@gmail.com> |
| date | 2026-06-18T11:56:47Z |
| subject | LOG_DEBUG since tcp_keepalive is a thing |
commit 9ffb37cdb97adb31b9dbee3579f450d722859e6b
Author: 斟酌 鵬兄 <tgckpg@gmail.com>
Date: 2026-06-18T11:56:47Z
LOG_DEBUG since tcp_keepalive is a thing
---
src/stream_conn.c | 10 ++--
tests/test_tcp_idle_timeout.py | 113 +++++++++++++++++++++++++++++++++++++++++
2 files changed, 118 insertions(+), 5 deletions(-)
diff --git a/src/stream_conn.c b/src/stream_conn.c
index ff87b2d..1271c1b 100644
--- a/src/stream_conn.c
+++ b/src/stream_conn.c
@@ -23,9 +23,9 @@ void free_conn(conn_t *conn) {
}
if (conn->idle_ev) {
- event_free(conn->idle_ev);
- conn->idle_ev = NULL;
- }
+ event_free(conn->idle_ev);
+ conn->idle_ev = NULL;
+ }
if (conn->client != NULL) {
bufferevent_free(conn->client);
@@ -87,9 +87,9 @@ static void conn_idle_timeout_cb(evutil_socket_t fd, short events, void *arg)
return;
}
- LOG_WARN("stream connection idle timed out",
+ LOG_DEBUG("stream connection idle timed out",
"listen", _LOGV_ENDPOINT(&conn->route->listen),
- "upstream", _LOGV_ENDPOINT(&conn->route->upstream)
+ "upstream", _LOGV_ENDPOINT(&conn->route->upstream),
);
free_conn(conn);
diff --git a/tests/test_tcp_idle_timeout.py b/tests/test_tcp_idle_timeout.py
index ce1a8ab..d875270 100644
--- a/tests/test_tcp_idle_timeout.py
+++ b/tests/test_tcp_idle_timeout.py
@@ -10,6 +10,53 @@ from .support import (
start_tracked_stream_server,
)
+async def delayed_response_upload_handler(
+ reader: asyncio.StreamReader,
+ writer: asyncio.StreamWriter,
+) -> None:
+ try:
+ try:
+ header = await reader.readuntil(b"\r\n\r\n")
+ except asyncio.IncompleteReadError as e:
+ # The test harness/proxy readiness check may open a TCP
+ # connection and close it without sending any request bytes.
+ # Ignore that. A partial HTTP request is still a real failure.
+ if not e.partial:
+ return
+ raise AssertionError(f"incomplete request headers: {e.partial!r}") from e
+
+ content_length = None
+ for line in header.decode("latin1", errors="replace").split("\r\n"):
+ if line.lower().startswith("content-length:"):
+ content_length = int(line.split(":", 1)[1].strip())
+ break
+
+ assert content_length is not None, "missing Content-Length"
+
+ received = 0
+ while received < content_length:
+ chunk = await reader.read(min(65536, content_length - received))
+ if not chunk:
+ break
+ received += len(chunk)
+
+ assert received == content_length, (
+ f"upload body truncated: received={received} expected={content_length}"
+ )
+
+ body = b"ok\n"
+ writer.write(
+ b"HTTP/1.1 200 OK\r\n"
+ + f"Content-Length: {len(body)}\r\n".encode("ascii")
+ + b"Connection: close\r\n"
+ + b"\r\n"
+ + body
+ )
+ await writer.drain()
+
+ finally:
+ writer.close()
+ await writer.wait_closed()
async def echo_handler(reader: asyncio.StreamReader, writer: asyncio.StreamWriter) -> None:
try:
@@ -146,8 +193,74 @@ async def test_tcp_idle_timeout_keeps_active_connection_open() -> None:
finally:
await backend_server.close()
+async def test_tcp_idle_timeout_keeps_long_one_way_upload_open() -> None:
+ proxy_bin = os.environ.get("TINYPROXY_BIN")
+ if not proxy_bin:
+ raise SkipTest("TINYPROXY_BIN is not set")
+
+ conf_text = (
+ f"listen"
+ f" tcp {LISTEN_HOST}:{PROXY_PORT}"
+ f" tcp {LISTEN_HOST}:{BACKEND_PORT}"
+ f" idle_timeout=2\n"
+ )
+
+ backend_server = await start_tracked_stream_server(
+ delayed_response_upload_handler,
+ LISTEN_HOST,
+ BACKEND_PORT,
+ backlog=128,
+ )
+
+ try:
+ async with run_tinyproxy_with_conf(
+ proxy_bin=proxy_bin,
+ conf_text=conf_text,
+ listen_host=LISTEN_HOST,
+ listen_port=PROXY_PORT,
+ proto="tcp",
+ ):
+ reader, writer = await asyncio.open_connection(LISTEN_HOST, PROXY_PORT)
+
+ try:
+ body_size = 6 * 1024 * 1024
+ chunk_size = 64 * 1024
+ delay = 0.05
+
+ req = (
+ b"PUT /v2/test/blobs/uploads/fake?digest=sha256:deadbeef HTTP/1.1\r\n"
+ b"Host: fake-registry\r\n"
+ b"User-Agent: test-buildkit/repro\r\n"
+ b"Content-Type: application/octet-stream\r\n"
+ + f"Content-Length: {body_size}\r\n".encode("ascii")
+ + b"Connection: close\r\n"
+ + b"\r\n"
+ )
+
+ writer.write(req)
+ await writer.drain()
+
+ chunk = b"x" * chunk_size
+ sent = 0
+
+ while sent < body_size:
+ n = min(chunk_size, body_size - sent)
+ writer.write(chunk[:n])
+ await writer.drain()
+ sent += n
+ await asyncio.sleep(delay)
+
+ resp = await asyncio.wait_for(reader.read(), timeout=5.0)
+
+ assert b"HTTP/1.1 200 OK" in resp, resp
+ assert resp.endswith(b"\r\n\r\nok\n"), resp
+ finally:
+ await close_writer(writer)
+ finally:
+ await backend_server.close()
TESTS = [
("test_tcp_idle_timeout_closes_idle_connection", test_tcp_idle_timeout_closes_idle_connection),
("test_tcp_idle_timeout_keeps_active_connection_open", test_tcp_idle_timeout_keeps_active_connection_open),
+ ("test_tcp_idle_timeout_keeps_long_one_way_upload_open", test_tcp_idle_timeout_keeps_long_one_way_upload_open),
]