Repository navigation
Description
Summary
With transport="polling", a CONNECT packet larger than REFLEX_SOCKET_MAX_HTTP_BUFFER_SIZE (default 1 MB) stops the page. The client makes one reconnect attempt. The handshake GET of that attempt never completes. The page does not hydrate, no connection toast shows, and no further attempt is made in the 5–8 s observation windows.
The websocket transport does not have this problem. Found while reviewing #7605 (fix for #7486). It happens on main (4be8b4d) and on #7605.
Repro
rxconfig.py:
import reflex as rx
config = rx.Config(app_name="repro")repro/repro.py:
import reflex as rx
class ReproState(rx.State):
local: str = rx.LocalStorage("small", name="repro-local")
def index() -> rx.Component:
return rx.vstack(
rx.text(rx.cond(rx.State.is_hydrated, "true", "false"), id="hydrated"),
rx.text(ReproState.local.length()),
)
app = rx.App()
app.add_page(index)- Start the app with
REFLEX_TRANSPORT=pollingset for both the frontend and the backend. The backend rejects the transport if only the frontend has it. - Open the page and wait until
#hydratedshowstrue. - In the browser console, run
localStorage.setItem('repro-local', 'x'.repeat(1_100_000)). - Reload the page.
Observed
Chromium (Playwright, headless). Request log after the reload, times in ms:
55 GET handshake (no sid) -> 200
80 GET poll (sid A)
189 POST CONNECT packet (sid A) -> 400
191 GET poll (sid A) -> 200 (session closed; client logs disconnect "transport close")
357 GET handshake (no sid) -> never completes
#hydratedstaysfalse. No toast shows, because only one connection error is recorded.- A direct
curl "http://127.0.0.1:8000/_event/?EIO=4&transport=polling"handshake to the same backend answers in under 1 ms during the stall. The backend is not blocked. - During the stall,
ssshows two browser connections to the backend with about 975 KB in the send queue: oneESTAB, oneFIN-WAIT-1. The server answers 400 to the large POST without reading its body. The unsent body seems to block the browser's connections, so the next handshake request is not served. - On
mainthe second attempt starts at once. On fix(state): back off when the connection closes before connecting #7605 it starts after the 200 ms backoff. The stall is the same. - I did not measure how long the stall lasts after 8 s. socket.io-client's connect timeout (20 s by default) may end it.
Expected
The client records each failed attempt, shows the connection toast, and keeps retrying with backoff, as it does with the websocket transport. The page hydrates after the storage is reduced.
Notes
- A reverse proxy that answers 413 to the large POST, without the server seeing the body, does not stall. That case is handled in fix(state): back off when the connection closes before connecting #7605.
- A possible direction: read (drain) the request body before the 400 response, or close the connection with the response, so the browser does not hold a half-sent request.
Activity
linear-code commentedon Oct 20, 2026
ENG-13476