test: exercise native connection churn
This commit is contained in:
@@ -7,6 +7,7 @@
|
|||||||
- 加固 native HTTP/1.1 解析边界:校验 Host、重复 Content-Length、Connection token、Transfer-Encoding、控制字符和响应头名称;补充 HTTP/1.0 Keep-Alive、HEAD、204 以及异常请求测试。
|
- 加固 native HTTP/1.1 解析边界:校验 Host、重复 Content-Length、Connection token、Transfer-Encoding、控制字符和响应头名称;补充 HTTP/1.0 Keep-Alive、HEAD、204 以及异常请求测试。
|
||||||
- 补充慢请求体超时、大请求体和同连接管线请求验收,确认请求体未完整读取时不会进入业务 Handler,连续请求不会串线。
|
- 补充慢请求体超时、大请求体和同连接管线请求验收,确认请求体未完整读取时不会进入业务 Handler,连续请求不会串线。
|
||||||
- 补充 `MaxConnections` 满载拒绝与槽位释放验收,确认请求完成后连接容量可再次使用。
|
- 补充 `MaxConnections` 满载拒绝与槽位释放验收,确认请求完成后连接容量可再次使用。
|
||||||
|
- 增加 8 并发、800 次建连/断连的状态池 churn 验收,覆盖连接对象回收与容量复用。
|
||||||
|
|
||||||
## v0.5.0
|
## v0.5.0
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,7 @@
|
|||||||
- native 当前拒绝 chunked 请求体,仍使用标准 `http.Request` / `http.ResponseWriter` 作为桥接;解析器、连接抽象和生产压测将在后续阶段继续下沉。
|
- native 当前拒绝 chunked 请求体,仍使用标准 `http.Request` / `http.ResponseWriter` 作为桥接;解析器、连接抽象和生产压测将在后续阶段继续下沉。
|
||||||
- 已补充慢请求体超时、大请求体、HTTP/1.0 Keep-Alive、HEAD/204 和管线请求验收;Linux、慢客户端规模、TLS 和持续运行压测仍待在目标环境验证。
|
- 已补充慢请求体超时、大请求体、HTTP/1.0 Keep-Alive、HEAD/204 和管线请求验收;Linux、慢客户端规模、TLS 和持续运行压测仍待在目标环境验证。
|
||||||
- 已验证 `MaxConnections` 满载时快速拒绝新连接、请求完成后释放容量;后续仍需在目标部署环境观察文件描述符、内核 socket 缓冲和反向代理联动。
|
- 已验证 `MaxConnections` 满载时快速拒绝新连接、请求完成后释放容量;后续仍需在目标部署环境观察文件描述符、内核 socket 缓冲和反向代理联动。
|
||||||
|
- 已用 8 个并发 worker 完成 800 次建连/断连 race 验收;真实部署仍需补文件描述符和跨平台 socket 行为观测。
|
||||||
- 已具备全量 race、vet、build 验收;生产压测、长连接规模和真实部署验证仍待完成。
|
- 已具备全量 race、vet、build 验收;生产压测、长连接规模和真实部署验证仍待完成。
|
||||||
- 自动验收入口:`python3 scripts/verifyPerformance.py`;增加 `--load --output /tmp/goOmniPerf.json` 可重复运行独立进程并发与内存矩阵。
|
- 自动验收入口:`python3 scripts/verifyPerformance.py`;增加 `--load --output /tmp/goOmniPerf.json` 可重复运行独立进程并发与内存矩阵。
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -64,7 +65,6 @@ func TestServerRejectsUnsupportedBodyAndLimits(t *testing.T) {
|
|||||||
}
|
}
|
||||||
done := make(chan error, 1)
|
done := make(chan error, 1)
|
||||||
go func() { done <- server.Serve(context.Background(), listener) }()
|
go func() { done <- server.Serve(context.Background(), listener) }()
|
||||||
waitForServer(t, listener.Addr().String())
|
|
||||||
defer stopServer(t, server, listener, done)
|
defer stopServer(t, server, listener, done)
|
||||||
|
|
||||||
for _, requestText := range []string{
|
for _, requestText := range []string{
|
||||||
@@ -462,7 +462,6 @@ func TestServerConnectionLimitReleasesAfterRequest(t *testing.T) {
|
|||||||
}
|
}
|
||||||
done := make(chan error, 1)
|
done := make(chan error, 1)
|
||||||
go func() { done <- server.Serve(context.Background(), listener) }()
|
go func() { done <- server.Serve(context.Background(), listener) }()
|
||||||
waitForServer(t, listener.Addr().String())
|
|
||||||
defer stopServer(t, server, listener, done)
|
defer stopServer(t, server, listener, done)
|
||||||
|
|
||||||
first, err := net.Dial("tcp", listener.Addr().String())
|
first, err := net.Dial("tcp", listener.Addr().String())
|
||||||
@@ -512,6 +511,61 @@ func TestServerConnectionLimitReleasesAfterRequest(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestServerConnectionChurnReusesStateSafely(t *testing.T) {
|
||||||
|
application := omni.NewWithRouter(netrouter.New())
|
||||||
|
application.GET("/health", func(ctx *omni.Context) error { return ctx.OK("ok") })
|
||||||
|
server := native.New(application)
|
||||||
|
server.MaxConnections = 16
|
||||||
|
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
done := make(chan error, 1)
|
||||||
|
go func() { done <- server.Serve(context.Background(), listener) }()
|
||||||
|
waitForServer(t, listener.Addr().String())
|
||||||
|
defer stopServer(t, server, listener, done)
|
||||||
|
|
||||||
|
const workers = 8
|
||||||
|
const requestsPerWorker = 100
|
||||||
|
errors := make(chan error, workers)
|
||||||
|
var group sync.WaitGroup
|
||||||
|
for worker := 0; worker < workers; worker++ {
|
||||||
|
group.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer group.Done()
|
||||||
|
for index := 0; index < requestsPerWorker; index++ {
|
||||||
|
conn, err := net.Dial("tcp", listener.Addr().String())
|
||||||
|
if err != nil {
|
||||||
|
errors <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, writeErr := io.WriteString(conn, "GET /health HTTP/1.1\r\nHost: test\r\nConnection: close\r\n\r\n")
|
||||||
|
if writeErr == nil {
|
||||||
|
response, readErr := http.ReadResponse(bufio.NewReader(conn), &http.Request{Method: http.MethodGet})
|
||||||
|
if readErr != nil {
|
||||||
|
writeErr = readErr
|
||||||
|
} else {
|
||||||
|
response.Body.Close()
|
||||||
|
if response.StatusCode != http.StatusOK {
|
||||||
|
writeErr = fmt.Errorf("unexpected status %d", response.StatusCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
conn.Close()
|
||||||
|
if writeErr != nil {
|
||||||
|
errors <- writeErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
group.Wait()
|
||||||
|
close(errors)
|
||||||
|
for err := range errors {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestServerReadHeaderTimeout(t *testing.T) {
|
func TestServerReadHeaderTimeout(t *testing.T) {
|
||||||
application := omni.NewWithRouter(netrouter.New())
|
application := omni.NewWithRouter(netrouter.New())
|
||||||
application.GET("/health", func(ctx *omni.Context) error { return ctx.OK("ok") })
|
application.GET("/health", func(ctx *omni.Context) error { return ctx.OK("ok") })
|
||||||
|
|||||||
Reference in New Issue
Block a user