Skip to content

Commit 67ffca6

Browse files
authored
Merge pull request #32 from treeform/net-hard
Harden UDP parsing and speed up reactor for 10k conns
2 parents d237fb2 + 0fbbd19 commit 67ffca6

8 files changed

Lines changed: 1346 additions & 188 deletions

File tree

‎.github/workflows/build.yml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,5 @@ jobs:
2323
cd ..
2424
nimby install -g "${{ github.event.repository.name }}/${{ github.event.repository.name }}.nimble"
2525
- run: nim r tests/tests.nim
26+
- run: nim r tests/test_hardening.nim
27+
- run: nim r tests/test_timeseries.nim

‎README.md‎

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@
1010

1111
## About
1212

13-
Netty is a reliable connection over UDP aimed at games. Normally UDP packets can get duplicated, dropped, or come out of order. Netty makes sure packets are not duplicated, re-sends them if they get dropped, and all packets come in order. UDP packets might also get split if they are above 512 bytes and also can fail to be sent if they are bigger than 1-2k. Netty breaks up big packets and sends them in pieces making sure each piece comes reliably in order. Finally sometimes it's impossible for two clients to communicate direclty with TCP because of NATs, but Netty provides hole punching which allows them to connect.
13+
Netty is a reliable connection over UDP aimed at games. Normally UDP packets can get duplicated, dropped, or come out of order. Netty makes sure packets are not duplicated, re-sends them if they get dropped, and all packets come in order. UDP packets might also get split if they are above 512 bytes and also can fail to be sent if they are bigger than 1-2k. Netty breaks up big packets and sends them in pieces making sure each piece comes reliably in order. Finally sometimes it is impossible for two clients to communicate directly with TCP because of NATs, but Netty can send hole punch probes to help them connect.
1414

1515
### Documentation
1616

1717
API reference: https://treeform.github.io/netty
1818

19-
## Is Netty a implementation of TCP?
19+
## Is Netty an implementation of TCP?
2020

21-
TCP is really bad for short latency sensitive messages. TCP was designed for throughput (downloading files) not latency (games). Netty will resend stuff faster than TCP, Netty will not buffer and you also get nat punch-through (which TCP does not have). Netty is basically "like TCP but for games". You should not be using Netty if you are will be sending large mount of data. By default Netty is capped at 250K of data in flight.
21+
TCP is really bad for short latency sensitive messages. TCP was designed for throughput (downloading files) not latency (games). Netty will resend stuff faster than TCP, Netty will not buffer and you also get NAT punch-through probes (which TCP does not have). Netty is basically "like TCP but for games". You should not be using Netty if you will be sending large amounts of data. By default Netty is capped at 250KB of data in flight (`reactor.maxInFlight`, configurable).
2222

2323
## Features:
2424

@@ -30,10 +30,11 @@ TCP is really bad for short latency sensitive messages. TCP was designed for thr
3030
| packet ordering | yes | no | yes |
3131
| packet splitting | yes | no | yes |
3232
| packet retry | yes | no | yes |
33-
| packet reduplication | yes | no | yes |
33+
| packet deduplication | yes | no | yes |
3434
| hole punch through | no | yes | yes |
3535
| connection handling | yes | no | yes |
36-
| congestion control | yes | no | yes |
36+
| congestion control | yes | no | no |
37+
| fixed in-flight byte cap | no | no | yes |
3738

3839

3940
# Echo Server/Client example
@@ -45,7 +46,7 @@ import netty
4546
4647
# listen for a connection on localhost port 1999
4748
var server = newReactor("127.0.0.1", 1999)
48-
echo "Listenting for UDP on 127.0.0.1:1999"
49+
echo "Listening for UDP on 127.0.0.1:1999"
4950
# main loop
5051
while true:
5152
# must call tick to both read and write
@@ -87,7 +88,7 @@ while true:
8788
import netty
8889
8990
var server = newReactor("127.0.0.1", 2001)
90-
echo "Listenting for UDP on 127.0.0.1:2001"
91+
echo "Listening for UDP on 127.0.0.1:2001"
9192
while true:
9293
server.tick()
9394
for connection in server.newConnections:

‎docs/bench-baseline.md‎

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Reactor bench baseline
2+
3+
Target: **10k connections** — Istrolid1 peaked around 3k concurrent players;
4+
Istrolid2 should handle that easily, with 10k as headroom.
5+
6+
```
7+
nim r -d:release -d:nettyBench tests/bench_reactor.nim
8+
# default scale is 10000
9+
```
10+
11+
## Environment
12+
13+
- Host: macOS darwin 24.1.0 (arm64)
14+
- Nim: 2.2.4
15+
- Flags: `-d:release -d:nettyBench`
16+
- Default scale: 10000
17+
18+
## Current baseline (post perf work, scale=10000)
19+
20+
| scenario | conns | ticks | msgs | bytes | msgs/s | mean tick us | p99 tick us | occupied MB |
21+
|---|---:|---:|---:|---:|---:|---:|---:|---:|
22+
| many-idle | 10000 | 200 | 0 | 0 | 0 | 215.5 | 282 | 40.0 |
23+
| many-active | 5000 | 200 | 819800 | 3279200 | 838653 | 4887.6 | 5631 | 39.4 |
24+
| fan-in-large | 4 | 200 | 800 | 6400000 | 12748 | 313.8 | 462 | 0.1 |
25+
| churn | 216 | 2000 | 1937 | 9685 | 91847 | 10.5 | 18 | 0.0 |
26+
27+
## Reference at scale=1000 (smoke)
28+
29+
| scenario | conns | mean tick us | msgs/s |
30+
|---|---:|---:|---:|
31+
| many-idle | 1000 | ~19 | 0 |
32+
| many-active | 500 | ~439 | ~1.1M |
33+
34+
## Reading the 10k numbers for Istrolid2
35+
36+
- **3k players** is below this bench's idle=10k / active=5k load.
37+
- **many-active ~4.9ms mean tick** at 5k sending every tick is a stress ceiling, not a typical frame (games traffic is far sparser per conn).
38+
- **many-idle ~0.22ms** at 10k is the "connected but quiet" cost — still scans every connection each tick; next win if needed.
39+
- Prefer improving **10k** numbers over chasing 100k (establish is too slow/flaky on localhost UDP).
40+
41+
## What already landed
42+
43+
- `Table[uint32, Connection]` for O(1) `getConn`
44+
- Per-sequence receive window (no O(n) inserts)
45+
- `Deque` send queue + part pool
46+
- ACK bundling (`AckBundleMagic`)
47+
- Reused `outBuf`, `MaxUdpRecv`, `MaxPartsPerTick`
48+
- `DefaultMaxConnections = 10_000`

0 commit comments

Comments
 (0)