body-drain: add -role server|client|both and -header-timeout
Lets each side run out of fds in its own process; document ulimit -Hn (Go raises soft limit to hard) and Linux hang vs macOS RST difference. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GbnJj9ZqEx9nUvzkjdwfMq
This commit is contained in:
@@ -6,9 +6,46 @@ properly drain+close response bodies so connections are reused").
|
||||
```
|
||||
go run ./99-experiments/body-drain -mode all
|
||||
go run ./99-experiments/body-drain -mode leak -n 200
|
||||
ulimit -n 64 && go run ./99-experiments/body-drain -mode leak -n 200 # watch it die
|
||||
```
|
||||
|
||||
## Two processes: watch each side run out of fds on its own
|
||||
|
||||
In one process the client and server share one fd limit and each leaked
|
||||
request costs *two* fds (client socket + accepted server socket). The server
|
||||
usually loses first — `accept` fails with `too many open files`, its accept
|
||||
loop retries with backoff forever, and the client silently hangs waiting for
|
||||
headers on a connection nobody will ever accept. Realistic, but confusing. So
|
||||
split them:
|
||||
|
||||
```
|
||||
# terminal 1
|
||||
go run ./99-experiments/body-drain -role server
|
||||
|
||||
# terminal 2 — client is the one out of fds
|
||||
ulimit -Hn 64; ulimit -Sn 64
|
||||
go run ./99-experiments/body-drain -role client -mode leak -n 200
|
||||
# request 60 failed: dial tcp 127.0.0.1:8099: socket: too many open files
|
||||
```
|
||||
|
||||
Now swap: limit the *server* terminal instead. It logs
|
||||
`http: Accept error: accept tcp ...: too many open files; retrying in 5ms`,
|
||||
while the client (unlimited) either hangs (Linux — pass `-header-timeout 3s`
|
||||
to turn that into `timeout awaiting response headers`) or gets
|
||||
`read: connection reset by peer` (macOS).
|
||||
|
||||
Which error string you see tells you which side is exhausted:
|
||||
|
||||
| error | who ran out |
|
||||
|---|---|
|
||||
| `dial tcp ...: socket: too many open files` | the client (this process) |
|
||||
| `http: Accept error: accept ...: too many open files` | the server |
|
||||
| `timeout awaiting response headers` / `connection reset by peer` | the client is fine; the *server* can't accept |
|
||||
|
||||
Note on `ulimit`: since Go 1.19 the runtime raises the soft `RLIMIT_NOFILE`
|
||||
to the hard limit at startup, so lowering only the soft limit does nothing.
|
||||
bash's plain `ulimit -n 64` sets both; zsh's sets only the soft one, hence
|
||||
the explicit `-Hn`.
|
||||
|
||||
## What it does
|
||||
|
||||
A local `httptest.Server` returns a 1 MiB body and counts accepted TCP
|
||||
|
||||
Reference in New Issue
Block a user