# US-037 — Concurrent request handling in the node relay bridge Status: planned Priority: Critical (blocks public friends-test deployment) Stage: Designed ## Context `RelayHttpBridge._run` (`packages/node/meshnet_node/relay_bridge.py`) handles `relay-http-request` envelopes serially inside its recv loop: `_handle_request` blocks on `urllib.request.urlopen(..., timeout=300)` before the next envelope is read. Off-LAN this is a correctness bug, not just a throughput limit: a node can be the **head** of one inference route and a **downstream hop** of another at the same time. While the head request occupies the bridge (up to 300 s of generation), the other route's per-token `/forward` calls sit unread in the WebSocket buffer — overlapping routes through a shared node are effectively broken. Decision (grilled 2026-07-06): dispatch requests on a **bounded worker pool, default 8, configurable**. Rejected alternatives: unbounded thread-per-request (a public deployment exposes volunteer machines to request stampedes) and reject-when-full (bouncing `/forward` hops kills other routes' in-flight sessions; queueing beyond the cap is today's behavior, just 8-wide). ## Design - Recv loop only parses envelopes and submits to a `ThreadPoolExecutor(max_workers=N)`; workers run `_handle_request` and send the response frame(s). - All sends on the single relay WebSocket go through a `threading.Lock`. With US-036 a response may be multiple frames — the lock is held **per frame**, so streams from concurrent requests interleave frame-atomically (receivers demux by `request_id`). - `N` defaults to 8; configurable via `meshnet-node start --relay-concurrency N` (env `MESHNET_RELAY_CONCURRENCY`). Requests beyond `N` queue in the executor. - On reconnect (`_run`'s outer loop), in-flight workers from the dead connection may still try to send; the send helper swallows failures on a closed socket, and the relay side times the orphaned request out (US-036 idle timeout / existing 310 s cap). - `stop()` shuts the executor down without waiting for stragglers (daemon threads, same as today's bridge thread). ## Acceptance criteria - While one relayed request is in flight (slow local handler), a second `relay-http-request` to the same node completes without waiting for the first - Responses are correctly matched by `request_id` when they complete out of order - More than `N` simultaneous requests queue and all eventually complete; thread count never exceeds `N` workers - Bridge survives a relay reconnect with workers still in flight (no crash, no deadlock; orphaned responses dropped) - Extend `tests/test_gossip_and_relay.py` - `python -m pytest` passes from repo root