From 74df7d2494c83e15af623661ffc1253bbe3cc6c7 Mon Sep 17 00:00:00 2001 From: autcir Date: Sat, 10 Oct 2026 13:47:22 +0200 Subject: [PATCH] fix(proxy): HTTP/2 verso l'upstream disattivabile con OPENVIDIA_HTTP2=0 Con HTTP/2 un solo GOAWAY di NVIDIA (ConnectionTerminated) chiude insieme tutti gli stream in corso: su soliso, con 5 sessioni opencode in parallelo, ogni chiusura faceva fallire tutte le sessioni con "Invalid openvidia/openai-compatible-chat stream event". Con OPENVIDIA_HTTP2=0 ogni richiesta ha la sua connessione HTTP/1.1 e una chiusura costa uno stream solo. Il default resta HTTP/2. Co-Authored-By: Claude Opus 5.5 --- openvidia/config.py | 11 +++++++++++ openvidia/proxy_app.py | 2 +- tests/test_upstream_http2.py | 17 +++++++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 tests/test_upstream_http2.py diff --git a/openvidia/config.py b/openvidia/config.py index f9b1346..6b4953d 100644 --- a/openvidia/config.py +++ b/openvidia/config.py @@ -77,6 +77,17 @@ def httpx_timeout_kwargs() -> dict[str, float]: UPSTREAM_CHAT = UPSTREAM_BASE + "chat/completions" +def upstream_http2() -> bool: + """Whether upstream requests share one multiplexed HTTP/2 connection. + + With HTTP/2 a single GOAWAY from the upstream (``ConnectionTerminated``) + kills every in-flight stream at once, and a client mid-stream sees a + broken event. ``OPENVIDIA_HTTP2=0`` gives each request its own HTTP/1.1 + connection, so a closed connection costs one stream, not all of them. + """ + return os.environ.get("OPENVIDIA_HTTP2", "1").strip().lower() not in ("0", "false", "no", "off") + + def outbound_proxy() -> str | None: """Return outbound HTTP/SOCKS5 proxy URL for upstream requests. diff --git a/openvidia/proxy_app.py b/openvidia/proxy_app.py index 0e7e51a..c8a76f3 100644 --- a/openvidia/proxy_app.py +++ b/openvidia/proxy_app.py @@ -313,7 +313,7 @@ def create_app(state: ProxyState, web_dir: Path | None = None) -> FastAPI: limits = httpx.Limits(max_keepalive_connections=100, max_connections=200, keepalive_expiry=30.0) proxy_url = config.outbound_proxy() client = httpx.AsyncClient( - http2=True, + http2=config.upstream_http2(), proxy=proxy_url if proxy_url else None, limits=limits, timeout=httpx.Timeout(**config.httpx_timeout_kwargs()), diff --git a/tests/test_upstream_http2.py b/tests/test_upstream_http2.py new file mode 100644 index 0000000..68c7f28 --- /dev/null +++ b/tests/test_upstream_http2.py @@ -0,0 +1,17 @@ +from openvidia import config + + +def test_http2_is_the_default(monkeypatch): + monkeypatch.delenv("OPENVIDIA_HTTP2", raising=False) + assert config.upstream_http2() is True + + +def test_http2_can_be_switched_off(monkeypatch): + for value in ("0", "false", "no", "off", " OFF "): + monkeypatch.setenv("OPENVIDIA_HTTP2", value) + assert config.upstream_http2() is False + + +def test_any_other_value_keeps_http2(monkeypatch): + monkeypatch.setenv("OPENVIDIA_HTTP2", "1") + assert config.upstream_http2() is True