Summary
A live goroutine profile of a public visor holding 989 transports to 480 peers (STCPR 432, SQUICR 338, SUDPH 191, WEBRTC 20, SWTR 8) shows WebRTC is by far the most resource-expensive carrier per transport:
| Type |
Goroutines / transport |
Notes |
| STCPR / STCP |
~1 |
just the universal managedTransport.readLoop; plain TCP needs no helper — cheapest |
| DMSG |
~1 + shared sessions |
dmsg sessions to servers are shared/amortized across all dmsg streams |
| SUDPH |
~3 |
readLoop + reliable-UDP layer over a shared udpdemux |
| SQUICR / SWTR |
~3-4 |
readLoop + quic-go per-Connection machinery; shared unified QUIC listener |
| WEBRTC |
~24-28 |
readLoop + pion ICE agent + DTLS + SCTP (~20-24) + media (~4, removed in #3940) — ~20x a TCP transport |
Baseline is exactly 1 goroutine/transport (989 readLoop for 989 transports). Everything above that is carrier-specific. Memory footprint tracks the same ranking (pion allocates ICE candidate tables, DTLS buffers, SCTP reassembly queues; TCP is just the socket + noise buffers).
On a webrtc-heavy exit — e.g. a resource-constrained ARM board — a few hundred webrtc transports means several thousand parked goroutines competing for the CPU the route-setup handshake needs.
Proposal
Adjust autoconnect / transport-type selection to prefer the cheap directly-reachable carriers (STCPR, SQUICR, SUDPH) and treat WebRTC as a fallback reserved for where it's actually required — NAT traversal the others can't achieve, and browser (wasm-visor) reachability. Where a peer is reachable via STCPR/SQUICR/SUDPH, autoconnect shouldn't also stand up a WebRTC transport to it.
Related: reduce per-WebRTC-transport cost where WebRTC is used
pkg/transport/network/webrtc_native.go currently sets only DetachDataChannels() (+ the empty MediaEngine from #3940). Bigger structural levers remain:
- Shared ICE
UDPMux (SettingEngine.SetICEUDPMux): one shared UDP socket + one read loop for all PeerConnections instead of per-conn gathering — collapses the largest chunk of the ~24 ICE goroutines into a constant. Standard pion server-scaling technique.
- ICE-lite on public visors (
SettingEngine.SetLite(true)): a directly-reachable node can skip the full ICE connectivity-check state machine, shedding most ICE agent goroutines. Gate to public nodes only.
- Restrict gathering (
SetNetworkTypes([UDP4]), ensure mDNS disabled) for fewer gather goroutines.
Context
Found while diagnosing the wasm (browser) visor wedging on per-exit DMSG-relay accumulation (fixed in #3939). The autoconnect side of the same theme — don't pile on expensive transport types when cheap ones reach the peer.
Summary
A live goroutine profile of a public visor holding 989 transports to 480 peers (STCPR 432, SQUICR 338, SUDPH 191, WEBRTC 20, SWTR 8) shows WebRTC is by far the most resource-expensive carrier per transport:
managedTransport.readLoop; plain TCP needs no helper — cheapestBaseline is exactly 1 goroutine/transport (989
readLoopfor 989 transports). Everything above that is carrier-specific. Memory footprint tracks the same ranking (pion allocates ICE candidate tables, DTLS buffers, SCTP reassembly queues; TCP is just the socket + noise buffers).On a webrtc-heavy exit — e.g. a resource-constrained ARM board — a few hundred webrtc transports means several thousand parked goroutines competing for the CPU the route-setup handshake needs.
Proposal
Adjust autoconnect / transport-type selection to prefer the cheap directly-reachable carriers (STCPR, SQUICR, SUDPH) and treat WebRTC as a fallback reserved for where it's actually required — NAT traversal the others can't achieve, and browser (wasm-visor) reachability. Where a peer is reachable via STCPR/SQUICR/SUDPH, autoconnect shouldn't also stand up a WebRTC transport to it.
Related: reduce per-WebRTC-transport cost where WebRTC is used
pkg/transport/network/webrtc_native.gocurrently sets onlyDetachDataChannels()(+ the empty MediaEngine from #3940). Bigger structural levers remain:UDPMux(SettingEngine.SetICEUDPMux): one shared UDP socket + one read loop for all PeerConnections instead of per-conn gathering — collapses the largest chunk of the ~24 ICE goroutines into a constant. Standard pion server-scaling technique.SettingEngine.SetLite(true)): a directly-reachable node can skip the full ICE connectivity-check state machine, shedding most ICE agent goroutines. Gate to public nodes only.SetNetworkTypes([UDP4]), ensure mDNS disabled) for fewer gather goroutines.Context
Found while diagnosing the wasm (browser) visor wedging on per-exit DMSG-relay accumulation (fixed in #3939). The autoconnect side of the same theme — don't pile on expensive transport types when cheap ones reach the peer.