CoplayDev/unity-mcp · warning · ConnectionError
Connection closed while reading
Error message
Connection closed while reading
What it means
Raised by read_exact() in stress_mcp.py when asyncio.StreamReader.read() returns empty bytes before the full requested length is received, indicating the Unity MCP bridge closed the TCP connection. Under the multi-client stress test this is a routine event — connections are churned, the bridge restarts on domain reload, and the client_loop catches this and reconnects with backoff.
Source
Thrown at tools/stress_mcp.py:58
if project_path:
# Match status for the given project if possible
if proj and project_path in proj:
if 0 < port < 65536:
return port
else:
if 0 < port < 65536:
return port
except Exception:
pass
return default_port
async def read_exact(reader: asyncio.StreamReader, n: int) -> bytes:
buf = b""
while len(buf) < n:
chunk = await reader.read(n - len(buf))
if not chunk:
raise ConnectionError("Connection closed while reading")
buf += chunk
return buf
async def read_frame(reader: asyncio.StreamReader) -> bytes:
header = await read_exact(reader, 8)
(length,) = struct.unpack(">Q", header)
if length <= 0 or length > (64 * 1024 * 1024):
raise ValueError(f"Invalid frame length: {length}")
return await read_exact(reader, length)
async def write_frame(writer: asyncio.StreamWriter, payload: bytes) -> None:
header = struct.pack(">Q", len(payload))
writer.write(header)
writer.write(payload)
await asyncio.wait_for(writer.drain(), timeout=TIMEOUT)
View on GitHub (pinned to c21bf496bc)
Solutions
- Expected under stress — client_loop catches ConnectionError/OSError and reconnects with exponential backoff. Monitor 'disconnects' in the stats output.
- Reduce --clients if disconnects dominate successful pings, indicating the bridge's connection ceiling is the bottleneck.
- Ensure Unity is open and the bridge is active before launching; check unity_port in the status JSON.
- Pause the reload_churn_task or reduce storm-count if domain reloads are tearing down the bridge too frequently.
Defensive patterns
Strategy: retry
Validate before calling
import socket
def bridge_listening(host: str, port: int) -> bool:
try:
with socket.create_connection((host, port), timeout=2.0):
return True
except OSError:
return False Try / catch
# client_loop already handles this:
except (ConnectionError, OSError, asyncio.IncompleteReadError, asyncio.TimeoutError):
stats["disconnects"] += 1
await asyncio.sleep(reconnect_delay)
reconnect_delay = min(reconnect_delay * 1.5, 2.0)
continue Prevention
- Connection drops are expected during stress — rely on the built-in reconnect-with-backoff.
- Tune --clients to stay under the bridge's connection ceiling.
- Avoid running during heavy recompilation unless testing reconnect behavior specifically.
When it happens
Trigger: The bridge dropped the connection during a ping/read cycle: editor recompilation triggered a domain reload (bridge restart), the number of concurrent clients exceeded the bridge's connection cap, or the editor was closed/crashed. Also fires when connecting to a port where nothing is listening and the OS resets immediately.
Common situations: Running stress_mcp.py with high --clients during Unity recompilation; the bridge's max-connection limit is lower than the client count; editor closed mid-run; loopback socket reset by the OS.
Understand the failure class
- Connection failures: ECONNREFUSED, ECONNRESET, and friends — why connections get refused, reset, or dropped.
Related errors
- Connection closed while reading
- Invalid frame length: {length}
- Unexpected handshake from server: {line!r}
- Invalid frame length: {length}
- Unexpected handshake from server: {line!r}
AI-assisted analysis of CoplayDev/unity-mcp@c21bf496bc (2026-08-13).
Data as JSON: /api/errors/36455c9335d979fb.
Report an issue: GitHub.