refactor: modernize code with Go 1.22+ features
Apply modern Go patterns across the codebase:
- Replace `interface{}` with `any` (Go 1.18+)
- Use `for range n` instead of `for i := 0; i < n; i++` (Go 1.22+)
- Replace `sort.Slice` with `slices.Sort` from slices package
- Simplify sync.WaitGroup patterns with errgroup where appropriate
- Add Makefile targets for modernize analyzer
Total: 84 files updated, net reduction of 79 lines
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
e7306a0c72
commit
f145a8770e
22
Makefile
22
Makefile
@ -289,6 +289,26 @@ lint:
|
||||
go vet ./...; \
|
||||
fi
|
||||
|
||||
# 现代化检查(检测可使用新 Go 特性的代码)
|
||||
modernize:
|
||||
@echo "Running modernize analyzer..."
|
||||
@if command -v modernize >/dev/null 2>&1; then \
|
||||
modernize ./internal/...; \
|
||||
else \
|
||||
echo "modernize not installed. Run: go install golang.org/x/tools/go/analysis/passes/modernize/cmd/modernize@latest"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
# 现代化检查并自动修复
|
||||
modernize-fix:
|
||||
@echo "Running modernize analyzer with auto-fix..."
|
||||
@if command -v modernize >/dev/null 2>&1; then \
|
||||
modernize -fix ./internal/...; \
|
||||
else \
|
||||
echo "modernize not installed. Run: go install golang.org/x/tools/go/analysis/passes/modernize/cmd/modernize@latest"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
# 代码检查
|
||||
check: fmt lint test-all
|
||||
@echo "All checks passed."
|
||||
@ -405,6 +425,8 @@ help:
|
||||
@echo "Quality:"
|
||||
@echo " make fmt - Format code"
|
||||
@echo " make lint - Run linter"
|
||||
@echo " make modernize - Check for modern Go patterns"
|
||||
@echo " make modernize-fix - Auto-fix modern Go patterns"
|
||||
@echo " make check - Format + lint + test"
|
||||
@echo ""
|
||||
@echo "Dependencies:"
|
||||
|
||||
@ -36,7 +36,7 @@ const DefaultBodyThreshold = 64 * 1024 // 64KB
|
||||
// 使用 singleton 模式避免多个适配器实例创建多个 pool,
|
||||
// 提高内存复用效率。该 pool 被 HTTP/2 和 HTTP/3 适配器共享。
|
||||
var bufferPoolInstance = &sync.Pool{
|
||||
New: func() interface{} {
|
||||
New: func() any {
|
||||
buf := make([]byte, 4096) // 4KB 初始缓冲区
|
||||
return &buf
|
||||
},
|
||||
@ -73,7 +73,7 @@ type CommonAdapter struct {
|
||||
func NewCommonAdapter() *CommonAdapter {
|
||||
return &CommonAdapter{
|
||||
CtxPool: sync.Pool{
|
||||
New: func() interface{} {
|
||||
New: func() any {
|
||||
return &fasthttp.RequestCtx{}
|
||||
},
|
||||
},
|
||||
|
||||
@ -327,7 +327,7 @@ func NewBenchmarkContextPool(size int) *BenchmarkContextPool {
|
||||
pool: make(chan *BenchmarkContext, size),
|
||||
}
|
||||
// 预填充池
|
||||
for i := 0; i < size; i++ {
|
||||
for range size {
|
||||
p.pool <- DefaultBenchmarkContext()
|
||||
}
|
||||
return p
|
||||
|
||||
@ -3,7 +3,7 @@ package tools
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sort"
|
||||
"slices"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
@ -61,16 +61,14 @@ func (lg *FasthttpLoadGenerator) Run(n int, concurrency int) *LoadGenStats {
|
||||
|
||||
start := time.Now()
|
||||
|
||||
for i := 0; i < concurrency; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for range concurrency {
|
||||
wg.Go(func() {
|
||||
req := fasthttp.AcquireRequest()
|
||||
resp := fasthttp.AcquireResponse()
|
||||
defer fasthttp.ReleaseRequest(req)
|
||||
defer fasthttp.ReleaseResponse(resp)
|
||||
|
||||
for j := 0; j < requestsPerWorker; j++ {
|
||||
for range requestsPerWorker {
|
||||
req.SetRequestURI("http://" + lg.addr + "/")
|
||||
req.Header.SetMethod("GET")
|
||||
|
||||
@ -83,7 +81,7 @@ func (lg *FasthttpLoadGenerator) Run(n int, concurrency int) *LoadGenStats {
|
||||
errorChan <- err
|
||||
}
|
||||
}
|
||||
}()
|
||||
})
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
@ -118,9 +116,7 @@ func (lg *FasthttpLoadGenerator) Run(n int, concurrency int) *LoadGenStats {
|
||||
|
||||
// Calculate latency distribution
|
||||
if len(latencies) > 0 {
|
||||
sort.Slice(latencies, func(i, j int) bool {
|
||||
return latencies[i] < latencies[j]
|
||||
})
|
||||
slices.Sort(latencies)
|
||||
|
||||
lg.stats.MinLatency = latencies[0]
|
||||
lg.stats.MaxLatency = latencies[len(latencies)-1]
|
||||
|
||||
@ -50,7 +50,7 @@ func createTestTargets(n int) ([]TestTarget, func()) {
|
||||
targets := make([]TestTarget, n)
|
||||
cleanups := make([]func(), n)
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
for i := range n {
|
||||
body := GenerateTestData(Size1KB)
|
||||
addr, cleanup := SimpleMockBackend(200, body)
|
||||
targets[i] = TestTarget{
|
||||
@ -83,7 +83,7 @@ func CreateWeightedTestTargets(n int) ([]TestTarget, func()) {
|
||||
targets := make([]TestTarget, n)
|
||||
cleanups := make([]func(), n)
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
for i := range n {
|
||||
body := GenerateTestData(Size1KB)
|
||||
addr, cleanup := SimpleMockBackend(200, body)
|
||||
// Vary weights: 1, 2, 3, etc.
|
||||
@ -111,7 +111,7 @@ func CreateDelayedTestTargets(n int, baseDelay time.Duration) ([]TestTarget, fun
|
||||
targets := make([]TestTarget, n)
|
||||
cleanups := make([]func(), n)
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
for i := range n {
|
||||
body := GenerateTestData(Size1KB)
|
||||
// Each target has increasing delay
|
||||
delay := baseDelay * time.Duration(i+1)
|
||||
@ -140,7 +140,7 @@ func CreateErrorTestTargets(n int, baseErrorRate float64) ([]TestTarget, func())
|
||||
targets := make([]TestTarget, n)
|
||||
cleanups := make([]func(), n)
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
for i := range n {
|
||||
body := GenerateTestData(Size1KB)
|
||||
// Vary error rates slightly per target
|
||||
errorRate := baseErrorRate + float64(i)*0.05
|
||||
|
||||
22
internal/cache/cache_bench_test.go
vendored
22
internal/cache/cache_bench_test.go
vendored
@ -32,7 +32,7 @@ func BenchmarkFileCacheGet(b *testing.B) {
|
||||
fc := NewFileCache(int64(size), 0, 1*time.Hour)
|
||||
|
||||
// 预填充缓存
|
||||
for i := 0; i < size; i++ {
|
||||
for i := range size {
|
||||
path := fmt.Sprintf("/file%d.txt", i)
|
||||
data := []byte("cached data content")
|
||||
_ = fc.Set(path, data, int64(len(data)), time.Now())
|
||||
@ -62,7 +62,7 @@ func BenchmarkFileCacheSet(b *testing.B) {
|
||||
fc := NewFileCache(int64(size), 0, 1*time.Hour)
|
||||
|
||||
// 预填充到容量上限
|
||||
for i := 0; i < size; i++ {
|
||||
for i := range size {
|
||||
path := fmt.Sprintf("/file%d.txt", i)
|
||||
data := []byte("cached data content")
|
||||
_ = fc.Set(path, data, int64(len(data)), time.Now())
|
||||
@ -88,7 +88,7 @@ func BenchmarkFileCacheSet_Pooled(b *testing.B) {
|
||||
fc := NewFileCache(int64(size), 0, 1*time.Hour)
|
||||
|
||||
// 预填充到容量上限,触发淘汰和 entry 复用
|
||||
for i := 0; i < size; i++ {
|
||||
for i := range size {
|
||||
path := fmt.Sprintf("/file%d.txt", i)
|
||||
data := []byte("cached data content")
|
||||
_ = fc.Set(path, data, int64(len(data)), time.Now())
|
||||
@ -128,7 +128,7 @@ func BenchmarkFileCacheConcurrent(b *testing.B) {
|
||||
fc := NewFileCache(int64(size), 0, 1*time.Hour)
|
||||
|
||||
// 预填充缓存
|
||||
for i := 0; i < size; i++ {
|
||||
for i := range size {
|
||||
path := fmt.Sprintf("/file%d.txt", i)
|
||||
data := []byte("cached data content")
|
||||
_ = fc.Set(path, data, int64(len(data)), time.Now())
|
||||
@ -160,7 +160,7 @@ func BenchmarkFileCacheGetOnly(b *testing.B) {
|
||||
fc := NewFileCache(1000, 0, 1*time.Hour)
|
||||
|
||||
// 预填充缓存
|
||||
for i := 0; i < 1000; i++ {
|
||||
for i := range 1000 {
|
||||
path := fmt.Sprintf("/static/file%d.css", i)
|
||||
data := make([]byte, 1024) // 1KB 数据
|
||||
_ = fc.Set(path, data, int64(len(data)), time.Now())
|
||||
@ -186,7 +186,7 @@ func BenchmarkFileCacheSizeEviction(b *testing.B) {
|
||||
|
||||
// 预填充到接近容量上限
|
||||
data := make([]byte, 1024) // 1KB 每条
|
||||
for i := 0; i < 1000; i++ {
|
||||
for i := range 1000 {
|
||||
path := fmt.Sprintf("/file%d.txt", i)
|
||||
_ = fc.Set(path, data, int64(len(data)), time.Now())
|
||||
}
|
||||
@ -205,7 +205,7 @@ func BenchmarkFileCacheLRUTouch(b *testing.B) {
|
||||
fc := NewFileCache(100, 0, 1*time.Hour)
|
||||
|
||||
// 预填充缓存
|
||||
for i := 0; i < 100; i++ {
|
||||
for i := range 100 {
|
||||
path := fmt.Sprintf("/file%d.txt", i)
|
||||
data := []byte("cached data")
|
||||
_ = fc.Set(path, data, int64(len(data)), time.Now())
|
||||
@ -224,7 +224,7 @@ func BenchmarkProxyCacheGet(b *testing.B) {
|
||||
pc := NewProxyCache(nil, false, 0, 0, 0)
|
||||
|
||||
// 预填充缓存
|
||||
for i := 0; i < 1000; i++ {
|
||||
for i := range 1000 {
|
||||
origKey := fmt.Sprintf("key%d", i)
|
||||
hashKey := hashKeyBench(origKey)
|
||||
data := []byte("response body")
|
||||
@ -264,7 +264,7 @@ func BenchmarkProxyCacheConcurrent(b *testing.B) {
|
||||
pc := NewProxyCache(nil, false, 0, 0, 0)
|
||||
|
||||
// 预填充缓存
|
||||
for i := 0; i < 1000; i++ {
|
||||
for i := range 1000 {
|
||||
origKey := fmt.Sprintf("key%d", i)
|
||||
hashKey := hashKeyBench(origKey)
|
||||
data := []byte("response body")
|
||||
@ -301,7 +301,7 @@ func BenchmarkFileCacheSharded(b *testing.B) {
|
||||
// 单锁缓存
|
||||
b.Run(fmt.Sprintf("SingleLock_Size%d", size), func(b *testing.B) {
|
||||
fc := NewFileCache(int64(size), 0, 1*time.Hour)
|
||||
for i := 0; i < size; i++ {
|
||||
for i := range size {
|
||||
path := fmt.Sprintf("/file%d.txt", i)
|
||||
data := []byte("cached data")
|
||||
_ = fc.Set(path, data, int64(len(data)), time.Now())
|
||||
@ -322,7 +322,7 @@ func BenchmarkFileCacheSharded(b *testing.B) {
|
||||
// 分片缓存
|
||||
b.Run(fmt.Sprintf("Sharded_Size%d", size), func(b *testing.B) {
|
||||
sc := NewShardedFileCache(int64(size), 0, 1*time.Hour)
|
||||
for i := 0; i < size; i++ {
|
||||
for i := range size {
|
||||
path := fmt.Sprintf("/file%d.txt", i)
|
||||
data := []byte("cached data")
|
||||
_ = sc.Set(path, data, int64(len(data)), time.Now())
|
||||
|
||||
14
internal/cache/file_cache_allocation_test.go
vendored
14
internal/cache/file_cache_allocation_test.go
vendored
@ -50,7 +50,7 @@ func BenchmarkFileCacheSetAllocation_Update(b *testing.B) {
|
||||
// 预填充缓存
|
||||
data := []byte("test data content for benchmark")
|
||||
size := int64(len(data))
|
||||
for i := 0; i < 1000; i++ {
|
||||
for i := range 1000 {
|
||||
path := fmt.Sprintf("/update/file%d.txt", i)
|
||||
fc.Set(path, data, size, time.Now())
|
||||
}
|
||||
@ -78,7 +78,7 @@ func BenchmarkFileCacheSetAllocation_Eviction(b *testing.B) {
|
||||
// 预填充到容量上限
|
||||
data := []byte("test data content for benchmark")
|
||||
size := int64(len(data))
|
||||
for i := 0; i < 100; i++ {
|
||||
for i := range 100 {
|
||||
path := fmt.Sprintf("/evict/file%d.txt", i)
|
||||
fc.Set(path, data, size, time.Now())
|
||||
}
|
||||
@ -103,7 +103,7 @@ func BenchmarkFileCacheSetAllocation_EvictionWithPool(b *testing.B) {
|
||||
size := int64(len(data))
|
||||
|
||||
// 预填充
|
||||
for i := 0; i < 100; i++ {
|
||||
for i := range 100 {
|
||||
path := fmt.Sprintf("/pool/file%d.txt", i)
|
||||
fc.Set(path, data, size, time.Now())
|
||||
}
|
||||
@ -128,7 +128,7 @@ func BenchmarkFileCacheSetAllocation_MemoryLimit(b *testing.B) {
|
||||
size := int64(len(data))
|
||||
|
||||
// 预填充到接近上限
|
||||
for i := 0; i < 900; i++ {
|
||||
for i := range 900 {
|
||||
path := fmt.Sprintf("/mem/file%d.txt", i)
|
||||
fc.Set(path, data, size, time.Now())
|
||||
}
|
||||
@ -172,7 +172,7 @@ func BenchmarkFileCacheSetAllocation_ConcurrentEviction(b *testing.B) {
|
||||
size := int64(len(data))
|
||||
|
||||
// 预填充
|
||||
for i := 0; i < 100; i++ {
|
||||
for i := range 100 {
|
||||
path := fmt.Sprintf("/concevict/file%d.txt", i)
|
||||
fc.Set(path, data, size, time.Now())
|
||||
}
|
||||
@ -201,7 +201,7 @@ func BenchmarkFileCacheEntryPool_GetPut(b *testing.B) {
|
||||
}
|
||||
|
||||
// 预填充池
|
||||
for i := 0; i < 100; i++ {
|
||||
for range 100 {
|
||||
pool.Put(&FileEntry{})
|
||||
}
|
||||
|
||||
@ -235,4 +235,4 @@ func BenchmarkFileCacheLRUList_PushFront(b *testing.B) {
|
||||
lruList.Remove(old)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
22
internal/cache/sharded_cache.go
vendored
22
internal/cache/sharded_cache.go
vendored
@ -26,19 +26,19 @@ const shardCount = 16
|
||||
|
||||
// FileCacheShard 单个缓存分片。
|
||||
type FileCacheShard struct {
|
||||
entries map[string]*FileEntry
|
||||
lruList *list.List
|
||||
maxEntries int64
|
||||
maxSize int64
|
||||
inactive time.Duration
|
||||
entries map[string]*FileEntry
|
||||
lruList *list.List
|
||||
maxEntries int64
|
||||
maxSize int64
|
||||
inactive time.Duration
|
||||
currentSize int64
|
||||
mu sync.RWMutex
|
||||
entryPool sync.Pool
|
||||
mu sync.RWMutex
|
||||
entryPool sync.Pool
|
||||
}
|
||||
|
||||
// ShardedFileCache 分片文件缓存。
|
||||
type ShardedFileCache struct {
|
||||
shards [shardCount]*FileCacheShard
|
||||
shards [shardCount]*FileCacheShard
|
||||
maxEntries int64
|
||||
maxSize int64
|
||||
inactive time.Duration
|
||||
@ -61,7 +61,7 @@ func NewShardedFileCache(maxEntries, maxSize int64, inactive time.Duration) *Sha
|
||||
perShardSize = maxSize
|
||||
}
|
||||
|
||||
for i := 0; i < shardCount; i++ {
|
||||
for i := range shardCount {
|
||||
shard := &FileCacheShard{
|
||||
maxEntries: perShardEntries,
|
||||
maxSize: perShardSize,
|
||||
@ -83,7 +83,7 @@ func NewShardedFileCache(maxEntries, maxSize int64, inactive time.Duration) *Sha
|
||||
func (s *ShardedFileCache) getShard(path string) *FileCacheShard {
|
||||
h := fnv.New64a()
|
||||
h.Write([]byte(path))
|
||||
return s.shards[h.Sum64() % shardCount]
|
||||
return s.shards[h.Sum64()%shardCount]
|
||||
}
|
||||
|
||||
// Get 获取缓存的文件。
|
||||
@ -262,4 +262,4 @@ func (sh *FileCacheShard) evictLRU() {
|
||||
return
|
||||
}
|
||||
sh.removeEntry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
@ -89,22 +89,22 @@ func TestGenerateConfigYAMLFieldsCoverage(t *testing.T) {
|
||||
typ reflect.Type
|
||||
name string
|
||||
}{
|
||||
{reflect.TypeOf(GeoIPConfig{}), "GeoIPConfig"},
|
||||
{reflect.TypeOf(AuthRequestConfig{}), "AuthRequestConfig"},
|
||||
{reflect.TypeOf(LuaGlobalSettings{}), "LuaGlobalSettings"},
|
||||
{reflect.TypeOf(LimitRateConfig{}), "LimitRateConfig"},
|
||||
{reflect.TypeOf(TypesConfig{}), "TypesConfig"},
|
||||
{reflect.TypeFor[GeoIPConfig](), "GeoIPConfig"},
|
||||
{reflect.TypeFor[AuthRequestConfig](), "AuthRequestConfig"},
|
||||
{reflect.TypeFor[LuaGlobalSettings](), "LuaGlobalSettings"},
|
||||
{reflect.TypeFor[LimitRateConfig](), "LimitRateConfig"},
|
||||
{reflect.TypeFor[TypesConfig](), "TypesConfig"},
|
||||
}
|
||||
|
||||
for _, c := range checks {
|
||||
for i := 0; i < c.typ.NumField(); i++ {
|
||||
tag := c.typ.Field(i).Tag.Get("yaml")
|
||||
for field := range c.typ.Fields() {
|
||||
tag := field.Tag.Get("yaml")
|
||||
fieldName := strings.Split(tag, ",")[0]
|
||||
if fieldName == "" || fieldName == "-" {
|
||||
continue
|
||||
}
|
||||
if !strings.Contains(yamlStr, fieldName) {
|
||||
t.Errorf("%s.%s (yaml:%q) not found in GenerateConfigYAML output", c.name, c.typ.Field(i).Name, fieldName)
|
||||
t.Errorf("%s.%s (yaml:%q) not found in GenerateConfigYAML output", c.name, field.Name, fieldName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -615,7 +615,7 @@ func TestErrorPageManager_SuccessfulLoad(t *testing.T) {
|
||||
defaultPage := filepath.Join(tmpDir, "default.html")
|
||||
|
||||
for code, path := range pages {
|
||||
content := []byte(fmt.Sprintf("Error %d page", code))
|
||||
content := fmt.Appendf(nil, "Error %d page", code)
|
||||
if err := os.WriteFile(path, content, 0o644); err != nil {
|
||||
t.Fatalf("创建页面 %d 失败: %v", code, err)
|
||||
}
|
||||
|
||||
@ -669,15 +669,13 @@ func TestLinuxSendfile_WithTCPConn(t *testing.T) {
|
||||
|
||||
var serverConn net.Conn
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
wg.Go(func() {
|
||||
serverConn, _ = ln.Accept()
|
||||
// 读取所有数据
|
||||
buf := make([]byte, len(content))
|
||||
_, _ = io.ReadFull(serverConn, buf)
|
||||
serverConn.Close()
|
||||
}()
|
||||
})
|
||||
|
||||
clientConn, err := net.Dial("tcp", ln.Addr().String())
|
||||
if err != nil {
|
||||
@ -827,15 +825,13 @@ func TestLinuxSendfile_PartialTransfer(t *testing.T) {
|
||||
var serverConn net.Conn
|
||||
var received []byte
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
wg.Go(func() {
|
||||
serverConn, _ = ln.Accept()
|
||||
buf := make([]byte, len(content))
|
||||
n, _ := serverConn.Read(buf)
|
||||
received = buf[:n]
|
||||
serverConn.Close()
|
||||
}()
|
||||
})
|
||||
|
||||
clientConn, err := net.Dial("tcp", ln.Addr().String())
|
||||
if err != nil {
|
||||
@ -888,14 +884,12 @@ func TestLinuxSendfile_WithOffset(t *testing.T) {
|
||||
|
||||
var serverConn net.Conn
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
wg.Go(func() {
|
||||
serverConn, _ = ln.Accept()
|
||||
buf := make([]byte, 8*1024)
|
||||
_, _ = serverConn.Read(buf)
|
||||
serverConn.Close()
|
||||
}()
|
||||
})
|
||||
|
||||
clientConn, err := net.Dial("tcp", ln.Addr().String())
|
||||
if err != nil {
|
||||
|
||||
@ -439,7 +439,7 @@ func TestAdapterConcurrentRequests(t *testing.T) {
|
||||
concurrency := 10
|
||||
done := make(chan bool, concurrency)
|
||||
|
||||
for i := 0; i < concurrency; i++ {
|
||||
for range concurrency {
|
||||
go func() {
|
||||
req := httptest.NewRequest(http.MethodGet, "/test", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
@ -453,7 +453,7 @@ func TestAdapterConcurrentRequests(t *testing.T) {
|
||||
}
|
||||
|
||||
// 等待所有请求完成
|
||||
for i := 0; i < concurrency; i++ {
|
||||
for range concurrency {
|
||||
<-done
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,6 +16,7 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"slices"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -101,13 +102,7 @@ func TestIntegrationALPN(t *testing.T) {
|
||||
}
|
||||
|
||||
// 验证协议列表
|
||||
foundH2 := false
|
||||
for _, proto := range tlsConfig.NextProtos {
|
||||
if proto == "h2" {
|
||||
foundH2 = true
|
||||
break
|
||||
}
|
||||
}
|
||||
foundH2 := slices.Contains(tlsConfig.NextProtos, "h2")
|
||||
if !foundH2 {
|
||||
t.Error("ALPN config should include h2 protocol")
|
||||
}
|
||||
|
||||
@ -18,6 +18,7 @@ import (
|
||||
"math/big"
|
||||
"net"
|
||||
"net/http"
|
||||
"slices"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
@ -232,13 +233,7 @@ func TestALPNNegotiationH2(t *testing.T) {
|
||||
t.Fatal("ALPN config should not be nil")
|
||||
}
|
||||
|
||||
foundH2 := false
|
||||
for _, proto := range alpnConfig.NextProtos {
|
||||
if proto == "h2" {
|
||||
foundH2 = true
|
||||
break
|
||||
}
|
||||
}
|
||||
foundH2 := slices.Contains(alpnConfig.NextProtos, "h2")
|
||||
if !foundH2 {
|
||||
t.Error("ALPN config should include h2 protocol")
|
||||
}
|
||||
@ -397,11 +392,9 @@ func TestServeHTTP1Fallback(t *testing.T) {
|
||||
}()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
wg.Go(func() {
|
||||
server.serveHTTP1(serverConn)
|
||||
}()
|
||||
})
|
||||
|
||||
// 发送 HTTP/1.1 请求
|
||||
request := "GET /test HTTP/1.1\r\nHost: localhost\r\n\r\n"
|
||||
|
||||
@ -23,6 +23,7 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
"slices"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@ -330,13 +331,7 @@ func WrapTLSListener(ln net.Listener, tlsConfig *tls.Config) net.Listener {
|
||||
originalGetConfig := tlsConfig.GetConfigForClient
|
||||
tlsConfig.GetConfigForClient = func(hello *tls.ClientHelloInfo) (*tls.Config, error) {
|
||||
// 检查客户端是否支持 h2
|
||||
supportsH2 := false
|
||||
for _, proto := range hello.SupportedProtos {
|
||||
if proto == "h2" {
|
||||
supportsH2 = true
|
||||
break
|
||||
}
|
||||
}
|
||||
supportsH2 := slices.Contains(hello.SupportedProtos, "h2")
|
||||
|
||||
// 如果有原始回调,先调用它
|
||||
var cfg *tls.Config
|
||||
|
||||
@ -339,7 +339,7 @@ func TestServer_MultipleStop(t *testing.T) {
|
||||
server, _ := NewServer(cfg, handler, &tls.Config{})
|
||||
|
||||
// 多次调用 Stop 应该都是安全的
|
||||
for i := 0; i < 3; i++ {
|
||||
for i := range 3 {
|
||||
err := server.Stop()
|
||||
if err != nil {
|
||||
t.Errorf("Stop call %d returned error: %v", i+1, err)
|
||||
@ -358,7 +358,7 @@ func TestServer_MultipleGracefulStop(t *testing.T) {
|
||||
server, _ := NewServer(cfg, handler, &tls.Config{})
|
||||
|
||||
// 多次调用 GracefulStop 应该都是安全的
|
||||
for i := 0; i < 3; i++ {
|
||||
for i := range 3 {
|
||||
err := server.GracefulStop(1 * time.Second)
|
||||
if err != nil {
|
||||
t.Errorf("GracefulStop call %d returned error: %v", i+1, err)
|
||||
|
||||
@ -249,7 +249,7 @@ func createTestProxy(backendAddr, path string) (*proxy.Proxy, error) {
|
||||
// - path: 请求路径
|
||||
// - count: 预热请求数量
|
||||
func warmupProxy(p *proxy.Proxy, path string, count int) {
|
||||
for i := 0; i < count; i++ {
|
||||
for range count {
|
||||
ctx := &fasthttp.RequestCtx{}
|
||||
ctx.Request.SetRequestURI(path)
|
||||
ctx.Request.Header.SetMethod(fasthttp.MethodGet)
|
||||
@ -299,11 +299,11 @@ func BenchmarkE2EStaticFile(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
|
||||
paths := []string{"/small.css", "/medium.json", "/assets/app.js", "/index.html"}
|
||||
var counter uint64
|
||||
var counter atomic.Uint64
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
idx := atomic.AddUint64(&counter, 1)
|
||||
idx := counter.Add(1)
|
||||
ctx := &fasthttp.RequestCtx{}
|
||||
ctx.Request.SetRequestURI(paths[idx%uint64(len(paths))])
|
||||
ctx.Request.Header.SetMethod(fasthttp.MethodGet)
|
||||
@ -336,11 +336,11 @@ func BenchmarkE2EStaticFileCacheHit(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
|
||||
paths := []string{"/small.css", "/medium.json", "/assets/app.js", "/index.html"}
|
||||
var counter uint64
|
||||
var counter atomic.Uint64
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
idx := atomic.AddUint64(&counter, 1)
|
||||
idx := counter.Add(1)
|
||||
ctx := &fasthttp.RequestCtx{}
|
||||
ctx.Request.SetRequestURI(paths[idx%uint64(len(paths))])
|
||||
ctx.Request.Header.SetMethod(fasthttp.MethodGet)
|
||||
@ -568,7 +568,7 @@ ngx.header["X-Lua-Processed"] = "true"`
|
||||
finalHandler := chain.Apply(wrappedByLua)
|
||||
|
||||
// 预热 Lua 引擎(字节码缓存)
|
||||
for i := 0; i < 5; i++ {
|
||||
for range 5 {
|
||||
ctx := &fasthttp.RequestCtx{}
|
||||
ctx.Request.SetRequestURI("/api/test")
|
||||
ctx.Request.Header.SetMethod(fasthttp.MethodGet)
|
||||
@ -638,7 +638,7 @@ func BenchmarkE2EMultiLuaPhase(b *testing.B) {
|
||||
finalHandler := baseChain.Apply(wrappedByLua)
|
||||
|
||||
// 预热
|
||||
for i := 0; i < 5; i++ {
|
||||
for range 5 {
|
||||
ctx := &fasthttp.RequestCtx{}
|
||||
ctx.Request.SetRequestURI("/api/test")
|
||||
ctx.Request.Header.SetMethod(fasthttp.MethodGet)
|
||||
@ -884,7 +884,7 @@ func BenchmarkE2EMultipleRoutes(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
|
||||
// 使用原子计数器轮询不同路径
|
||||
var counter uint64
|
||||
var counter atomic.Uint64
|
||||
paths := []string{
|
||||
"/api/v1/users",
|
||||
"/api/v2/items",
|
||||
@ -899,7 +899,7 @@ func BenchmarkE2EMultipleRoutes(b *testing.B) {
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
idx := atomic.AddUint64(&counter, 1)
|
||||
idx := counter.Add(1)
|
||||
ctx := &fasthttp.RequestCtx{}
|
||||
ctx.Request.SetRequestURI(paths[idx%uint64(len(paths))])
|
||||
ctx.Request.Header.SetMethod(fasthttp.MethodGet)
|
||||
@ -955,7 +955,7 @@ func BenchmarkE2EMultipleRoutesWithMiddleware(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
|
||||
var counter uint64
|
||||
var counter atomic.Uint64
|
||||
testPaths := []string{
|
||||
"/api/users",
|
||||
"/api/users/42",
|
||||
@ -968,7 +968,7 @@ func BenchmarkE2EMultipleRoutesWithMiddleware(b *testing.B) {
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
idx := atomic.AddUint64(&counter, 1)
|
||||
idx := counter.Add(1)
|
||||
ctx := &fasthttp.RequestCtx{}
|
||||
ctx.Request.SetRequestURI(testPaths[idx%uint64(len(testPaths))])
|
||||
ctx.Request.Header.SetMethod(fasthttp.MethodGet)
|
||||
@ -1152,7 +1152,7 @@ func BenchmarkE2EInmemoryServer(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
|
||||
var counter uint64
|
||||
var counter atomic.Uint64
|
||||
paths := []string{"/api/test", "/static/small.css", "/static/medium.json", "/health", "/api/data"}
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
@ -1162,7 +1162,7 @@ func BenchmarkE2EInmemoryServer(b *testing.B) {
|
||||
defer fasthttp.ReleaseResponse(resp)
|
||||
|
||||
for pb.Next() {
|
||||
idx := atomic.AddUint64(&counter, 1)
|
||||
idx := counter.Add(1)
|
||||
req.SetRequestURI("http://localhost" + paths[idx%uint64(len(paths))])
|
||||
req.Header.SetMethod(fasthttp.MethodGet)
|
||||
req.Header.Set("Host", "example.com")
|
||||
@ -1316,11 +1316,11 @@ func BenchmarkE2ERewriteMiddleware(b *testing.B) {
|
||||
"/old-api/settings",
|
||||
"/v1/data/123",
|
||||
}
|
||||
var counter uint64
|
||||
var counter atomic.Uint64
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
idx := atomic.AddUint64(&counter, 1)
|
||||
idx := counter.Add(1)
|
||||
ctx := &fasthttp.RequestCtx{}
|
||||
ctx.Request.SetRequestURI(paths[idx%uint64(len(paths))])
|
||||
ctx.Request.Header.SetMethod(fasthttp.MethodGet)
|
||||
@ -1365,11 +1365,11 @@ func BenchmarkE2EAccessControl(b *testing.B) {
|
||||
allowedIPs := []string{"192.168.1.50", "192.168.1.200", "10.0.0.1", "10.1.2.3"}
|
||||
deniedIPs := []string{"192.168.1.100", "172.16.0.1", "8.8.8.8"}
|
||||
allIPs := append(allowedIPs, deniedIPs...)
|
||||
var counter uint64
|
||||
var counter atomic.Uint64
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
idx := atomic.AddUint64(&counter, 1)
|
||||
idx := counter.Add(1)
|
||||
clientIP := allIPs[idx%uint64(len(allIPs))]
|
||||
ctx := &fasthttp.RequestCtx{}
|
||||
ctx.Request.SetRequestURI("/api/test")
|
||||
@ -1416,11 +1416,11 @@ func BenchmarkE2ERateLimiter(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
|
||||
var counter uint64
|
||||
var counter atomic.Uint64
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
idx := atomic.AddUint64(&counter, 1)
|
||||
idx := counter.Add(1)
|
||||
ctx := &fasthttp.RequestCtx{}
|
||||
ctx.Request.SetRequestURI("/api/test")
|
||||
ctx.Request.Header.SetMethod(fasthttp.MethodGet)
|
||||
@ -1470,7 +1470,7 @@ func BenchmarkE2EBasicAuth(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
|
||||
// 预热认证(缓存 bcrypt 结果)
|
||||
for i := 0; i < 3; i++ {
|
||||
for range 3 {
|
||||
ctx := &fasthttp.RequestCtx{}
|
||||
ctx.Request.SetRequestURI("/api/test")
|
||||
ctx.Request.Header.SetMethod(fasthttp.MethodGet)
|
||||
|
||||
@ -37,7 +37,7 @@ func setupFailoverBackends(b *testing.B, count, healthyCount int) ([]*loadbalanc
|
||||
targets := make([]*loadbalance.Target, count)
|
||||
cleanups := make([]func(), count)
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
for i := range count {
|
||||
addr, cleanup := setupNetworkBackend(b, fasthttp.StatusOK, []byte(`{"backend":`+strconv.Itoa(i)+`}`))
|
||||
cleanups[i] = cleanup
|
||||
targets[i] = &loadbalance.Target{
|
||||
@ -215,8 +215,8 @@ func BenchmarkE2EFailover_DynamicToggle(b *testing.B) {
|
||||
for pb.Next() {
|
||||
// 每 100 次请求切换一个后端的健康状态
|
||||
count := toggleCounter.Add(1)
|
||||
if count % 100 == 0 {
|
||||
targetIdx := int(count / 100) % len(targets)
|
||||
if count%100 == 0 {
|
||||
targetIdx := int(count/100) % len(targets)
|
||||
current := targets[targetIdx].Healthy.Load()
|
||||
targets[targetIdx].Healthy.Store(!current)
|
||||
}
|
||||
@ -271,7 +271,7 @@ func BenchmarkE2EFailover_AllUnhealthy(b *testing.B) {
|
||||
// 验证负载均衡器选择逻辑的分配。
|
||||
func BenchmarkE2EFailover_SelectOnly(b *testing.B) {
|
||||
targets := make([]*loadbalance.Target, 5)
|
||||
for i := 0; i < 5; i++ {
|
||||
for i := range 5 {
|
||||
targets[i] = &loadbalance.Target{
|
||||
URL: "http://backend" + strconv.Itoa(i) + ":8080",
|
||||
Weight: 1,
|
||||
@ -287,4 +287,4 @@ func BenchmarkE2EFailover_SelectOnly(b *testing.B) {
|
||||
for b.Loop() {
|
||||
_ = rr.Select(targets)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -199,7 +199,7 @@ func TestVariablePerformance(t *testing.T) {
|
||||
// 执行多次展开
|
||||
start := time.Now()
|
||||
iterations := 10000
|
||||
for i := 0; i < iterations; i++ {
|
||||
for range iterations {
|
||||
_ = vc.Expand(template)
|
||||
}
|
||||
elapsed := time.Since(start)
|
||||
|
||||
@ -105,7 +105,7 @@ type Balancer interface {
|
||||
// 并发安全:counter 使用 atomic 操作,支持多 goroutine 并发调用。
|
||||
type RoundRobin struct {
|
||||
// counter 请求计数器,原子递增,用于确定轮询位置
|
||||
counter uint64
|
||||
counter atomic.Uint64
|
||||
}
|
||||
|
||||
// NewRoundRobin 创建一个新的轮询负载均衡器。
|
||||
@ -128,7 +128,7 @@ func (r *RoundRobin) Select(targets []*Target) *Target {
|
||||
}
|
||||
|
||||
// 原子地递增并获取计数器值
|
||||
idx := atomic.AddUint64(&r.counter, 1) - 1
|
||||
idx := r.counter.Add(1) - 1
|
||||
return healthy[idx%uint64(len(healthy))]
|
||||
}
|
||||
|
||||
@ -139,7 +139,7 @@ func (r *RoundRobin) Select(targets []*Target) *Target {
|
||||
// 并发安全:counter 使用 atomic 操作,支持多 goroutine 并发调用。
|
||||
type WeightedRoundRobin struct {
|
||||
// counter 请求计数器,原子递增,用于确定权重分布位置
|
||||
counter uint64
|
||||
counter atomic.Uint64
|
||||
}
|
||||
|
||||
// NewWeightedRoundRobin 创建一个新的权重轮询负载均衡器。
|
||||
@ -176,7 +176,7 @@ func (w *WeightedRoundRobin) Select(targets []*Target) *Target {
|
||||
}
|
||||
|
||||
// 使用原子计数器确定权重分布中的位置
|
||||
idx := atomic.AddUint64(&w.counter, 1) - 1
|
||||
idx := w.counter.Add(1) - 1
|
||||
pos := int(idx % uint64(totalWeight))
|
||||
|
||||
// 找到计算位置处的目标
|
||||
@ -430,7 +430,7 @@ func (r *RoundRobin) SelectExcluding(targets []*Target, excluded []*Target) *Tar
|
||||
}
|
||||
|
||||
// 原子地递增并获取计数器值
|
||||
idx := atomic.AddUint64(&r.counter, 1) - 1
|
||||
idx := r.counter.Add(1) - 1
|
||||
return available[idx%uint64(len(available))]
|
||||
}
|
||||
|
||||
@ -457,7 +457,7 @@ func (w *WeightedRoundRobin) SelectExcluding(targets []*Target, excluded []*Targ
|
||||
}
|
||||
|
||||
// 使用原子计数器确定权重分布中的位置
|
||||
idx := atomic.AddUint64(&w.counter, 1) - 1
|
||||
idx := w.counter.Add(1) - 1
|
||||
pos := int(idx % uint64(totalWeight))
|
||||
|
||||
// 找到计算位置处的目标
|
||||
|
||||
@ -34,7 +34,7 @@ import (
|
||||
// - 包含 count 个健康目标的切片,权重均为 1
|
||||
func generateTargets(count int) []*Target {
|
||||
targets := make([]*Target, count)
|
||||
for i := 0; i < count; i++ {
|
||||
for i := range count {
|
||||
targets[i] = &Target{
|
||||
URL: fmt.Sprintf("http://backend%d:8080", i),
|
||||
Weight: 1,
|
||||
@ -54,7 +54,7 @@ func generateTargets(count int) []*Target {
|
||||
// - 包含 count 个健康目标的切片,按 weights 分配权重
|
||||
func generateWeightedTargets(count int, weights []int) []*Target {
|
||||
targets := make([]*Target, count)
|
||||
for i := 0; i < count; i++ {
|
||||
for i := range count {
|
||||
weight := 1
|
||||
if i < len(weights) {
|
||||
weight = weights[i]
|
||||
@ -160,7 +160,7 @@ func BenchmarkConsistentHashSelect(b *testing.B) {
|
||||
ch.Rebuild(targets)
|
||||
|
||||
keys := make([]string, 100)
|
||||
for i := 0; i < 100; i++ {
|
||||
for i := range 100 {
|
||||
keys[i] = fmt.Sprintf("192.168.1.%d", i)
|
||||
}
|
||||
|
||||
@ -284,7 +284,7 @@ func BenchmarkIPHashSelect(b *testing.B) {
|
||||
iph := NewIPHash()
|
||||
|
||||
ips := make([]string, 100)
|
||||
for i := 0; i < 100; i++ {
|
||||
for i := range 100 {
|
||||
ips[i] = fmt.Sprintf("192.168.1.%d", i)
|
||||
}
|
||||
|
||||
|
||||
@ -124,12 +124,10 @@ func TestRoundRobin_Select(t *testing.T) {
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < 100; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for range 100 {
|
||||
wg.Go(func() {
|
||||
_ = rr.Select(targets)
|
||||
}()
|
||||
})
|
||||
}
|
||||
wg.Wait()
|
||||
})
|
||||
@ -148,7 +146,7 @@ func TestWeightedRoundRobin_Select(t *testing.T) {
|
||||
|
||||
// 统计选择次数
|
||||
counts := make(map[string]int)
|
||||
for i := 0; i < 400; i++ {
|
||||
for range 400 {
|
||||
got := wrr.Select(targets)
|
||||
if got == nil {
|
||||
t.Fatal("Select() = nil, want non-nil")
|
||||
@ -175,7 +173,7 @@ func TestWeightedRoundRobin_Select(t *testing.T) {
|
||||
|
||||
// 权重为0的目标应该被当作权重为1处理
|
||||
counts := make(map[string]int)
|
||||
for i := 0; i < 100; i++ {
|
||||
for range 100 {
|
||||
got := wrr.Select(targets)
|
||||
if got == nil {
|
||||
t.Fatal("Select() = nil, want non-nil")
|
||||
@ -225,7 +223,7 @@ func TestWeightedRoundRobin_Select(t *testing.T) {
|
||||
targets[1].Healthy.Store(true)
|
||||
|
||||
// 所有选择都应该落在健康目标上
|
||||
for i := 0; i < 50; i++ {
|
||||
for range 50 {
|
||||
got := wrr.Select(targets)
|
||||
if got == nil {
|
||||
t.Fatal("Select() = nil, want non-nil")
|
||||
@ -331,7 +329,7 @@ func TestIPHash_Select(t *testing.T) {
|
||||
// 使用相同的IP地址多次选择
|
||||
clientIP := "192.168.1.100"
|
||||
var firstSelection *Target
|
||||
for i := 0; i < 10; i++ {
|
||||
for range 10 {
|
||||
got := ih.SelectByIP(targets, clientIP)
|
||||
if got == nil {
|
||||
t.Fatal("SelectByIP() = nil, want non-nil")
|
||||
@ -447,12 +445,10 @@ func TestConnectionsAtomic(t *testing.T) {
|
||||
target.Healthy.Store(true)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < 1000; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for range 1000 {
|
||||
wg.Go(func() {
|
||||
IncrementConnections(target)
|
||||
}()
|
||||
})
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
@ -466,12 +462,10 @@ func TestConnectionsAtomic(t *testing.T) {
|
||||
target.Healthy.Store(true)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < 1000; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for range 1000 {
|
||||
wg.Go(func() {
|
||||
DecrementConnections(target)
|
||||
}()
|
||||
})
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
@ -486,20 +480,16 @@ func TestConnectionsAtomic(t *testing.T) {
|
||||
|
||||
var wg sync.WaitGroup
|
||||
// 500个增加
|
||||
for i := 0; i < 500; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for range 500 {
|
||||
wg.Go(func() {
|
||||
IncrementConnections(target)
|
||||
}()
|
||||
})
|
||||
}
|
||||
// 300个减少
|
||||
for i := 0; i < 300; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for range 300 {
|
||||
wg.Go(func() {
|
||||
DecrementConnections(target)
|
||||
}()
|
||||
})
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
@ -727,7 +717,7 @@ func TestConsistentHash(t *testing.T) {
|
||||
// 相同的键应该选择相同的目标
|
||||
key := "192.168.1.100"
|
||||
first := ch.SelectByKey(targets, key)
|
||||
for i := 0; i < 10; i++ {
|
||||
for range 10 {
|
||||
got := ch.SelectByKey(targets, key)
|
||||
if got == nil {
|
||||
t.Fatal("SelectByKey() = nil")
|
||||
@ -814,7 +804,7 @@ func TestConsistentHashSelectExcludingByKey(t *testing.T) {
|
||||
key := "192.168.1.100"
|
||||
|
||||
// 多次选择,验证不会选中排除的目标
|
||||
for i := 0; i < 100; i++ {
|
||||
for range 100 {
|
||||
got := ch.SelectExcludingByKey(targets, excluded, key)
|
||||
if got == nil {
|
||||
t.Fatal("SelectExcludingByKey() = nil, want non-nil")
|
||||
@ -877,17 +867,15 @@ func TestConsistentHashSelectExcludingByKey(t *testing.T) {
|
||||
key := "192.168.1.100"
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < 100; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for j := 0; j < 100; j++ {
|
||||
for range 100 {
|
||||
wg.Go(func() {
|
||||
for range 100 {
|
||||
got := ch.SelectExcludingByKey(targets, excluded, key)
|
||||
if got != nil && got.URL == targets[0].URL {
|
||||
t.Errorf("并发时选中了被排除的目标: %q", got.URL)
|
||||
}
|
||||
}
|
||||
}()
|
||||
})
|
||||
}
|
||||
wg.Wait()
|
||||
})
|
||||
@ -906,7 +894,7 @@ func TestConsistentHashSelectExcludingByKey(t *testing.T) {
|
||||
|
||||
// 相同键应该始终返回相同的目标
|
||||
var firstSelection *Target
|
||||
for i := 0; i < 50; i++ {
|
||||
for range 50 {
|
||||
got := ch.SelectExcludingByKey(targets, excluded, key)
|
||||
if got == nil {
|
||||
t.Fatal("SelectExcludingByKey() = nil, want non-nil")
|
||||
@ -1021,15 +1009,13 @@ func TestRoundRobin_SelectExcluding(t *testing.T) {
|
||||
excluded := []*Target{targets[0]}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < 100; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for range 100 {
|
||||
wg.Go(func() {
|
||||
got := rr.SelectExcluding(targets, excluded)
|
||||
if got != nil && got.URL == targets[0].URL {
|
||||
t.Errorf("选中了被排除的目标: %q", got.URL)
|
||||
}
|
||||
}()
|
||||
})
|
||||
}
|
||||
wg.Wait()
|
||||
})
|
||||
@ -1066,7 +1052,7 @@ func TestWeightedRoundRobin_SelectExcluding(t *testing.T) {
|
||||
excluded := []*Target{targets[1]}
|
||||
|
||||
// 排除高权重目标后,只应选低权重目标
|
||||
for i := 0; i < 20; i++ {
|
||||
for range 20 {
|
||||
got := wrr.SelectExcluding(targets, excluded)
|
||||
if got == nil {
|
||||
t.Fatal("SelectExcluding() = nil, want non-nil")
|
||||
@ -1518,15 +1504,13 @@ func TestLeastConnections_ConcurrentSelection(t *testing.T) {
|
||||
lc := NewLeastConnections()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < 100; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for range 100 {
|
||||
wg.Go(func() {
|
||||
got := lc.Select(targets)
|
||||
if got == nil {
|
||||
t.Error("并发Select() = nil, want non-nil")
|
||||
}
|
||||
}()
|
||||
})
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
@ -1542,15 +1526,13 @@ func TestWeightedRoundRobin_ConcurrentSelection(t *testing.T) {
|
||||
wrr := NewWeightedRoundRobin()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < 100; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for range 100 {
|
||||
wg.Go(func() {
|
||||
got := wrr.Select(targets)
|
||||
if got == nil {
|
||||
t.Error("并发Select() = nil, want non-nil")
|
||||
}
|
||||
}()
|
||||
})
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
@ -1564,7 +1546,7 @@ func TestIPHash_ConcurrentSelection(t *testing.T) {
|
||||
ih := NewIPHash()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < 100; i++ {
|
||||
for i := range 100 {
|
||||
wg.Add(1)
|
||||
go func(ip string) {
|
||||
defer wg.Done()
|
||||
@ -1691,7 +1673,7 @@ func TestWeightedRoundRobin_NegativeWeight(t *testing.T) {
|
||||
|
||||
// 负权重要被当作1处理
|
||||
counts := make(map[string]int)
|
||||
for i := 0; i < 100; i++ {
|
||||
for range 100 {
|
||||
got := wrr.Select(targets)
|
||||
if got == nil {
|
||||
t.Fatal("Select() = nil, want non-nil")
|
||||
@ -1885,7 +1867,7 @@ func TestConsistentHash_Select(t *testing.T) {
|
||||
|
||||
// 多次调用应该返回相同结果(空键的一致性)
|
||||
first := ch.Select(targets)
|
||||
for i := 0; i < 10; i++ {
|
||||
for range 10 {
|
||||
got := ch.Select(targets)
|
||||
if got == nil {
|
||||
t.Fatal("Select() = nil, want non-nil")
|
||||
@ -2013,7 +1995,7 @@ func TestRandomBalancer(t *testing.T) {
|
||||
b := NewRandom()
|
||||
|
||||
// 多次选择,验证总是选择连接数少的目标
|
||||
for i := 0; i < 100; i++ {
|
||||
for range 100 {
|
||||
selected := b.Select(targets)
|
||||
if selected == nil {
|
||||
t.Error("should select a target")
|
||||
@ -2037,7 +2019,7 @@ func TestRandomBalancer(t *testing.T) {
|
||||
|
||||
// 连接数相等时,两个目标都应该被选中
|
||||
counts := make(map[string]int)
|
||||
for i := 0; i < 100; i++ {
|
||||
for range 100 {
|
||||
selected := b.Select(targets)
|
||||
if selected != nil {
|
||||
counts[selected.URL]++
|
||||
|
||||
@ -17,6 +17,7 @@ package loadbalance
|
||||
import (
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"slices"
|
||||
"sort"
|
||||
"sync"
|
||||
)
|
||||
@ -138,9 +139,7 @@ func (c *ConsistentHash) rebuildCircle(targets []*Target) {
|
||||
}
|
||||
|
||||
// 排序哈希值
|
||||
sort.Slice(c.sortedHashes, func(i, j int) bool {
|
||||
return c.sortedHashes[i] < c.sortedHashes[j]
|
||||
})
|
||||
slices.Sort(c.sortedHashes)
|
||||
}
|
||||
|
||||
// hashKeyString 计算字符串的哈希值(使用 FNV-64a)。
|
||||
|
||||
@ -13,6 +13,7 @@
|
||||
package loadbalance
|
||||
|
||||
import (
|
||||
"maps"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
@ -143,13 +144,7 @@ func (m *SlowStartManager) updateEffectiveWeights() {
|
||||
|
||||
// 线性增长:从 1 增加到 BaseWeight
|
||||
progress := float64(elapsed) / float64(state.SlowStart)
|
||||
effectiveWeight := int(1 + progress*float64(state.BaseWeight-1))
|
||||
if effectiveWeight < 1 {
|
||||
effectiveWeight = 1
|
||||
}
|
||||
if effectiveWeight > state.BaseWeight {
|
||||
effectiveWeight = state.BaseWeight
|
||||
}
|
||||
effectiveWeight := min(max(int(1+progress*float64(state.BaseWeight-1)), 1), state.BaseWeight)
|
||||
|
||||
// 查找目标并更新 EffectiveWeight
|
||||
if m.findTarget != nil {
|
||||
@ -184,8 +179,6 @@ func (m *SlowStartManager) GetAllStates() map[string]*SlowStartState {
|
||||
defer m.mu.RUnlock()
|
||||
|
||||
result := make(map[string]*SlowStartState, len(m.targets))
|
||||
for k, v := range m.targets {
|
||||
result[k] = v
|
||||
}
|
||||
maps.Copy(result, m.targets)
|
||||
return result
|
||||
}
|
||||
|
||||
@ -81,7 +81,7 @@ func (m *LocationManager) Register(location string, handler fasthttp.RequestHand
|
||||
// 返回值:
|
||||
// - *LocationCaptureResult: 子请求响应结果
|
||||
// - error: 当前实现始终返回 nil
|
||||
func (m *LocationManager) Capture(parentCtx *fasthttp.RequestCtx, location string, opts map[string]interface{}) (*LocationCaptureResult, error) {
|
||||
func (m *LocationManager) Capture(parentCtx *fasthttp.RequestCtx, location string, opts map[string]any) (*LocationCaptureResult, error) {
|
||||
m.mu.Lock()
|
||||
handler, ok := m.handlers[location]
|
||||
m.mu.Unlock()
|
||||
@ -175,7 +175,7 @@ func RegisterLocationAPI(L *glua.LState, manager *LocationManager, ngx *glua.LTa
|
||||
uri := L.CheckString(1)
|
||||
|
||||
// 解析选项
|
||||
opts := make(map[string]interface{})
|
||||
opts := make(map[string]any)
|
||||
if L.GetTop() >= 2 {
|
||||
optionsTable := L.CheckTable(2)
|
||||
optionsTable.ForEach(func(key, value glua.LValue) {
|
||||
|
||||
@ -78,7 +78,7 @@ func TestLocationManagerCaptureWithOptions(t *testing.T) {
|
||||
parentCtx.Request.SetRequestURI("/parent")
|
||||
|
||||
// 使用自定义选项
|
||||
opts := map[string]interface{}{
|
||||
opts := map[string]any{
|
||||
"method": "POST",
|
||||
"body": "test body",
|
||||
}
|
||||
|
||||
@ -70,7 +70,7 @@ type TCPSocket struct {
|
||||
mu sync.RWMutex
|
||||
|
||||
// closed 关闭标记(原子操作)
|
||||
closed int32
|
||||
closed atomic.Int32
|
||||
}
|
||||
|
||||
// NewTCPSocket 创建新的 TCP socket 实例。
|
||||
@ -416,7 +416,7 @@ func (s *TCPSocket) ReceiveUntil(pattern string, inclusive bool) ([]byte, error)
|
||||
// 检查是否匹配模式
|
||||
if len(result) >= patternLen {
|
||||
matched := true
|
||||
for i := 0; i < patternLen; i++ {
|
||||
for i := range patternLen {
|
||||
if result[len(result)-patternLen+i] != patternBytes[i] {
|
||||
matched = false
|
||||
break
|
||||
@ -442,7 +442,7 @@ func (s *TCPSocket) Close() error {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
if !atomic.CompareAndSwapInt32(&s.closed, 0, 1) {
|
||||
if !s.closed.CompareAndSwap(0, 1) {
|
||||
return nil // 已经关闭
|
||||
}
|
||||
|
||||
@ -507,7 +507,7 @@ func (s *TCPSocket) setState(state SocketState) {
|
||||
|
||||
// IsClosed 检查是否已关闭
|
||||
func (s *TCPSocket) IsClosed() bool {
|
||||
return atomic.LoadInt32(&s.closed) == 1
|
||||
return s.closed.Load() == 1
|
||||
}
|
||||
|
||||
// LocalAddr 获取本地地址
|
||||
|
||||
@ -67,7 +67,7 @@ type TimerManager struct {
|
||||
schedulerL *glua.LState
|
||||
|
||||
// nextID 下一个定时器 ID(原子操作)
|
||||
nextID uint64
|
||||
nextID atomic.Uint64
|
||||
|
||||
// mu 定时器映射读写锁
|
||||
mu sync.Mutex
|
||||
@ -76,10 +76,10 @@ type TimerManager struct {
|
||||
queueMu sync.Mutex
|
||||
|
||||
// active 活跃定时器计数(原子操作)
|
||||
active int32
|
||||
active atomic.Int32
|
||||
|
||||
// stopping 停止标记(原子操作)
|
||||
stopping int32
|
||||
stopping atomic.Int32
|
||||
|
||||
// queueClosed 队列是否已关闭
|
||||
queueClosed bool
|
||||
@ -181,14 +181,14 @@ func NewTimerManager(engine *LuaEngine) *TimerManager {
|
||||
// - 回调不能捕获 upvalue(闭包变量),因为它们会在不同 goroutine 中执行
|
||||
// - 跨协程数据共享应使用 shared dict
|
||||
func (m *TimerManager) At(delay time.Duration, callback *glua.LFunction, args []glua.LValue) (*TimerHandle, error) {
|
||||
if atomic.LoadInt32(&m.stopping) != 0 {
|
||||
if m.stopping.Load() != 0 {
|
||||
return nil, nil // 服务器正在关闭,不接受新定时器
|
||||
}
|
||||
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
id := atomic.AddUint64(&m.nextID, 1)
|
||||
id := m.nextID.Add(1)
|
||||
|
||||
// 编译回调为 FunctionProto
|
||||
var proto *glua.FunctionProto
|
||||
@ -216,7 +216,7 @@ func (m *TimerManager) At(delay time.Duration, callback *glua.LFunction, args []
|
||||
})
|
||||
|
||||
m.timers[id] = entry
|
||||
atomic.AddInt32(&m.active, 1)
|
||||
m.active.Add(1)
|
||||
|
||||
return &TimerHandle{id: id, manager: m}, nil
|
||||
}
|
||||
@ -227,7 +227,7 @@ func (m *TimerManager) At(delay time.Duration, callback *glua.LFunction, args []
|
||||
// 检查取消信号,清理条目,并在队列满时丢弃回调。
|
||||
func (m *TimerManager) executeTimer(entry *TimerEntry) {
|
||||
defer func() {
|
||||
atomic.AddInt32(&m.active, -1)
|
||||
m.active.Add(-1)
|
||||
close(entry.done)
|
||||
}()
|
||||
|
||||
@ -319,7 +319,7 @@ func (m *TimerManager) Cancel(handle *TimerHandle) bool {
|
||||
|
||||
// 清理
|
||||
delete(m.timers, entry.id)
|
||||
atomic.AddInt32(&m.active, -1)
|
||||
m.active.Add(-1)
|
||||
|
||||
return true
|
||||
}
|
||||
@ -336,11 +336,11 @@ func (m *TimerManager) Cancel(handle *TimerHandle) bool {
|
||||
// - bool: true 表示所有定时器已正常完成,false 表示超时
|
||||
func (m *TimerManager) WaitAll(timeout time.Duration) bool {
|
||||
// 设置停止标志
|
||||
atomic.StoreInt32(&m.stopping, 1)
|
||||
m.stopping.Store(1)
|
||||
|
||||
// 等待所有定时器完成
|
||||
start := time.Now()
|
||||
for atomic.LoadInt32(&m.active) > 0 {
|
||||
for m.active.Load() > 0 {
|
||||
if time.Since(start) > timeout {
|
||||
// 超时,强制取消所有
|
||||
m.mu.Lock()
|
||||
@ -369,12 +369,12 @@ func (m *TimerManager) WaitAll(timeout time.Duration) bool {
|
||||
//
|
||||
// 注意:该方法是幂等的,可安全调用多次。
|
||||
func (m *TimerManager) Close() {
|
||||
if m == nil || atomic.LoadInt32(&m.stopping) != 0 {
|
||||
if m == nil || m.stopping.Load() != 0 {
|
||||
return // 已关闭或 nil
|
||||
}
|
||||
|
||||
// 1. 停止接受新定时器
|
||||
atomic.StoreInt32(&m.stopping, 1)
|
||||
m.stopping.Store(1)
|
||||
|
||||
// 2. 优雅关闭:等待回调队列排空
|
||||
m.gracefulShutdown(5 * time.Second)
|
||||
@ -415,7 +415,7 @@ func (m *TimerManager) gracefulShutdown(timeout time.Duration) {
|
||||
// 返回值:
|
||||
// - int32: 活跃定时器数量
|
||||
func (m *TimerManager) ActiveCount() int32 {
|
||||
return atomic.LoadInt32(&m.active)
|
||||
return m.active.Load()
|
||||
}
|
||||
|
||||
// RegisterTimerAPI 注册 ngx.timer API 到 Lua 状态机。
|
||||
|
||||
@ -324,11 +324,11 @@ func TestBoundarySharedDictConcurrentAccess(t *testing.T) {
|
||||
concurrency := 10
|
||||
iterations := 100
|
||||
|
||||
for i := 0; i < concurrency; i++ {
|
||||
for i := range concurrency {
|
||||
wg.Add(1)
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
for j := 0; j < iterations; j++ {
|
||||
for range iterations {
|
||||
key := "key-" + string(rune('0'+id%10))
|
||||
_, _ = dict.Set(key, "value", 0)
|
||||
_, _, _ = dict.Get(key)
|
||||
|
||||
@ -87,10 +87,10 @@ type CodeCache struct {
|
||||
ttl time.Duration
|
||||
|
||||
// 缓存命中次数
|
||||
hits uint64
|
||||
hits atomic.Uint64
|
||||
|
||||
// 缓存未命中次数
|
||||
misses uint64
|
||||
misses atomic.Uint64
|
||||
|
||||
// 读写锁
|
||||
mu sync.RWMutex
|
||||
@ -156,12 +156,12 @@ func (c *CodeCache) GetOrCompileInline(src string) (*glua.FunctionProto, error)
|
||||
c.mu.RUnlock()
|
||||
|
||||
if ok && !c.isExpired(cached) {
|
||||
atomic.AddUint64(&c.hits, 1)
|
||||
c.hits.Add(1)
|
||||
cached.AccessAt.Store(time.Now())
|
||||
return cached.Proto, nil
|
||||
}
|
||||
|
||||
atomic.AddUint64(&c.misses, 1)
|
||||
c.misses.Add(1)
|
||||
|
||||
// 编译脚本
|
||||
chunk, err := parse.Parse(strings.NewReader(src), "<inline>")
|
||||
@ -210,12 +210,12 @@ func (c *CodeCache) GetOrCompileFile(path string) (*glua.FunctionProto, error) {
|
||||
|
||||
// 检查是否需要重新加载
|
||||
if ok && !c.isExpired(cached) && !c.isFileChanged(cached) {
|
||||
atomic.AddUint64(&c.hits, 1)
|
||||
c.hits.Add(1)
|
||||
cached.AccessAt.Store(time.Now())
|
||||
return cached.Proto, nil
|
||||
}
|
||||
|
||||
atomic.AddUint64(&c.misses, 1)
|
||||
c.misses.Add(1)
|
||||
|
||||
// 读取文件
|
||||
content, err := os.ReadFile(path)
|
||||
@ -345,13 +345,13 @@ func (c *CodeCache) isFileChanged(cached *CachedProto) bool {
|
||||
func (c *CodeCache) Stats() (hits, misses uint64, size int) {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
return atomic.LoadUint64(&c.hits), atomic.LoadUint64(&c.misses), len(c.protos)
|
||||
return c.hits.Load(), c.misses.Load(), len(c.protos)
|
||||
}
|
||||
|
||||
// HitRate 返回缓存命中率
|
||||
func (c *CodeCache) HitRate() float64 {
|
||||
hits := atomic.LoadUint64(&c.hits)
|
||||
misses := atomic.LoadUint64(&c.misses)
|
||||
hits := c.hits.Load()
|
||||
misses := c.misses.Load()
|
||||
total := hits + misses
|
||||
if total == 0 {
|
||||
return 0
|
||||
|
||||
@ -90,7 +90,7 @@ type LuaEngine struct {
|
||||
|
||||
// 并发控制
|
||||
maxCoroutines int
|
||||
activeCount int32
|
||||
activeCount atomic.Int32
|
||||
|
||||
// 引擎统计
|
||||
stats EngineStats
|
||||
@ -181,7 +181,7 @@ func NewEngine(config *Config) (*LuaEngine, error) {
|
||||
cancel: cancel,
|
||||
sharedDictManager: NewSharedDictManager(),
|
||||
coroutinePool: sync.Pool{
|
||||
New: func() interface{} {
|
||||
New: func() any {
|
||||
// 注意:这里只是创建空的协程对象结构
|
||||
// 实际的协程通过 L.NewThread() 创建
|
||||
return &LuaCoroutine{}
|
||||
@ -262,9 +262,9 @@ func (e *LuaEngine) Close() {
|
||||
// - 使用完毕后必须调用 Close() 或 releaseCoroutine() 释放资源
|
||||
func (e *LuaEngine) NewCoroutine(req *fasthttp.RequestCtx) (*LuaCoroutine, error) {
|
||||
// 步骤1: 检查并发限制
|
||||
current := atomic.AddInt32(&e.activeCount, 1)
|
||||
current := e.activeCount.Add(1)
|
||||
if current > int32(e.maxCoroutines) {
|
||||
atomic.AddInt32(&e.activeCount, -1)
|
||||
e.activeCount.Add(-1)
|
||||
return nil, fmt.Errorf("max concurrent coroutines exceeded: %d/%d", current, e.maxCoroutines)
|
||||
}
|
||||
|
||||
@ -272,7 +272,7 @@ func (e *LuaEngine) NewCoroutine(req *fasthttp.RequestCtx) (*LuaCoroutine, error
|
||||
// 协程继承主 LState 的全局环境
|
||||
co, cancel := e.L.NewThread()
|
||||
if co == nil {
|
||||
atomic.AddInt32(&e.activeCount, -1)
|
||||
e.activeCount.Add(-1)
|
||||
return nil, fmt.Errorf("failed to create coroutine")
|
||||
}
|
||||
|
||||
@ -330,7 +330,7 @@ func (e *LuaEngine) releaseCoroutine(coro *LuaCoroutine) {
|
||||
coro.executionCancel = nil
|
||||
|
||||
// 步骤4: 更新计数
|
||||
atomic.AddInt32(&e.activeCount, -1)
|
||||
e.activeCount.Add(-1)
|
||||
atomic.AddUint64(&e.stats.CoroutinesClosed, 1)
|
||||
|
||||
// 步骤5: 放回池中(仅复用 LuaCoroutine 结构体内存)
|
||||
@ -365,7 +365,7 @@ func (e *LuaEngine) Stats() EngineStats {
|
||||
// 返回值:
|
||||
// - int32: 当前正在执行的协程数
|
||||
func (e *LuaEngine) ActiveCoroutines() int32 {
|
||||
return atomic.LoadInt32(&e.activeCount)
|
||||
return e.activeCount.Load()
|
||||
}
|
||||
|
||||
// SharedDictManager 返回共享字典管理器实例。
|
||||
|
||||
@ -244,14 +244,14 @@ func TestDelayedResponseWriter_Pool(t *testing.T) {
|
||||
ctx := mockRequestCtx()
|
||||
|
||||
// 预热池
|
||||
for i := 0; i < 100; i++ {
|
||||
for range 100 {
|
||||
ri := AcquireResponseInterceptor(ctx)
|
||||
ReleaseResponseInterceptor(ri)
|
||||
}
|
||||
|
||||
// 测试从池获取的性能
|
||||
start := time.Now()
|
||||
for i := 0; i < 10000; i++ {
|
||||
for range 10000 {
|
||||
ri := AcquireResponseInterceptor(ctx)
|
||||
ri.WriteString("test")
|
||||
ReleaseResponseInterceptor(ri)
|
||||
@ -270,7 +270,7 @@ func TestConcurrentAccess(t *testing.T) {
|
||||
var wg sync.WaitGroup
|
||||
errors := make(chan error, 100)
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
for i := range 100 {
|
||||
wg.Add(1)
|
||||
go func(idx int) {
|
||||
defer wg.Done()
|
||||
@ -464,7 +464,7 @@ func TestBodyFilterPhase(t *testing.T) {
|
||||
name: "large body",
|
||||
inputBody: strings.Repeat("x", 10000),
|
||||
filterFunc: func(b []byte) []byte {
|
||||
return append([]byte("size="), []byte(fmt.Sprintf("%d ", len(b)))...)
|
||||
return append([]byte("size="), fmt.Appendf(nil, "%d ", len(b))...)
|
||||
},
|
||||
expectedOutput: "size=10000 ",
|
||||
},
|
||||
@ -501,7 +501,7 @@ func TestFilterPhaseSuccessRate(t *testing.T) {
|
||||
successCount := 0
|
||||
var mu sync.Mutex
|
||||
|
||||
for i := 0; i < totalRequests; i++ {
|
||||
for i := range totalRequests {
|
||||
ctx := mockRequestCtx()
|
||||
drw := NewDelayedResponseWriter(ctx)
|
||||
drw.EnableFilterPhase()
|
||||
@ -537,14 +537,14 @@ func TestPerformanceOverhead(t *testing.T) {
|
||||
// 基准:正常写入
|
||||
ctx1 := mockRequestCtx()
|
||||
start := time.Now()
|
||||
for i := 0; i < 10000; i++ {
|
||||
for range 10000 {
|
||||
ctx1.Response.SetBodyString("Hello, World!")
|
||||
}
|
||||
baselineDuration := time.Since(start)
|
||||
|
||||
// 测试:延迟写入
|
||||
start = time.Now()
|
||||
for i := 0; i < 10000; i++ {
|
||||
for range 10000 {
|
||||
ctx := mockRequestCtx()
|
||||
drw := NewDelayedResponseWriter(ctx)
|
||||
drw.EnableFilterPhase()
|
||||
@ -896,7 +896,7 @@ func TestFilterPhaseFeasibility(t *testing.T) {
|
||||
const iterations = 100
|
||||
success := 0
|
||||
|
||||
for i := 0; i < iterations; i++ {
|
||||
for range iterations {
|
||||
ctx := mockRequestCtx()
|
||||
drw := NewDelayedResponseWriter(ctx)
|
||||
drw.EnableFilterPhase()
|
||||
@ -963,7 +963,7 @@ func TestFilterPhaseFeasibility(t *testing.T) {
|
||||
|
||||
// 基准
|
||||
start := time.Now()
|
||||
for i := 0; i < iterations; i++ {
|
||||
for range iterations {
|
||||
ctx := mockRequestCtx()
|
||||
ctx.Response.SetBodyString("test")
|
||||
}
|
||||
@ -971,7 +971,7 @@ func TestFilterPhaseFeasibility(t *testing.T) {
|
||||
|
||||
// 延迟写入
|
||||
start = time.Now()
|
||||
for i := 0; i < iterations; i++ {
|
||||
for range iterations {
|
||||
ctx := mockRequestCtx()
|
||||
drw := NewDelayedResponseWriter(ctx)
|
||||
drw.EnableFilterPhase()
|
||||
@ -1019,7 +1019,7 @@ func TestFilterPhaseMetrics(t *testing.T) {
|
||||
const iterations = 100
|
||||
|
||||
start := time.Now()
|
||||
for i := 0; i < iterations; i++ {
|
||||
for i := range iterations {
|
||||
ctx := mockRequestCtx()
|
||||
drw := NewDelayedResponseWriter(ctx)
|
||||
drw.EnableFilterPhase()
|
||||
@ -1177,13 +1177,11 @@ func BenchmarkFilterPhaseScalability(b *testing.B) {
|
||||
b.Run(fmt.Sprintf("goroutines-%d", goroutines), func(b *testing.B) {
|
||||
var wg sync.WaitGroup
|
||||
errors := make(chan error, b.N)
|
||||
var completed int32
|
||||
var completed atomic.Int32
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < goroutines; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for range goroutines {
|
||||
wg.Go(func() {
|
||||
for j := 0; j < b.N/goroutines; j++ {
|
||||
ctx := mockRequestCtx()
|
||||
drw := NewDelayedResponseWriter(ctx)
|
||||
@ -1193,10 +1191,10 @@ func BenchmarkFilterPhaseScalability(b *testing.B) {
|
||||
if err := drw.Flush(); err != nil {
|
||||
errors <- err
|
||||
} else {
|
||||
atomic.AddInt32(&completed, 1)
|
||||
completed.Add(1)
|
||||
}
|
||||
}
|
||||
}()
|
||||
})
|
||||
}
|
||||
wg.Wait()
|
||||
close(errors)
|
||||
@ -1373,7 +1371,7 @@ func TestFinalVerification(t *testing.T) {
|
||||
const total = 1000
|
||||
success := 0
|
||||
|
||||
for i := 0; i < total; i++ {
|
||||
for range total {
|
||||
ctx := mockRequestCtx()
|
||||
drw := NewDelayedResponseWriter(ctx)
|
||||
drw.EnableFilterPhase()
|
||||
@ -1439,7 +1437,7 @@ func TestFinalVerification(t *testing.T) {
|
||||
|
||||
// 基准
|
||||
start := time.Now()
|
||||
for i := 0; i < iterations; i++ {
|
||||
for range iterations {
|
||||
ctx := mockRequestCtx()
|
||||
ctx.Response.SetBodyString("test")
|
||||
ctx.Response.Header.Set("X-Test", "value")
|
||||
@ -1448,7 +1446,7 @@ func TestFinalVerification(t *testing.T) {
|
||||
|
||||
// Filter phase
|
||||
start = time.Now()
|
||||
for i := 0; i < iterations; i++ {
|
||||
for range iterations {
|
||||
ctx := mockRequestCtx()
|
||||
drw := NewDelayedResponseWriter(ctx)
|
||||
drw.EnableFilterPhase()
|
||||
|
||||
@ -432,7 +432,7 @@ func (drw *DelayedResponseWriter) DelHeader(key string) {
|
||||
|
||||
// ResponseInterceptorPool 响应拦截器对象池。
|
||||
var ResponseInterceptorPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
New: func() any {
|
||||
return &ResponseInterceptor{}
|
||||
},
|
||||
}
|
||||
@ -666,7 +666,7 @@ func (drw *DelayedResponseWriter) Redirect(uri string, statusCode int) {
|
||||
|
||||
// bufferPool body 缓冲区对象池。
|
||||
var bufferPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
New: func() any {
|
||||
buf := make([]byte, 0, 4096) // 4KB 初始容量
|
||||
return &buf
|
||||
},
|
||||
@ -747,10 +747,7 @@ func (bw *BufferedWriter) Write(p []byte) (int, error) {
|
||||
// 检查是否需要扩容
|
||||
if len(bw.buf)+len(p) > cap(bw.buf) {
|
||||
// 扩容
|
||||
newCap := cap(bw.buf) * 2
|
||||
if newCap < len(bw.buf)+len(p) {
|
||||
newCap = len(bw.buf) + len(p)
|
||||
}
|
||||
newCap := max(cap(bw.buf)*2, len(bw.buf)+len(p))
|
||||
newBuf := make([]byte, len(bw.buf), newCap)
|
||||
copy(newBuf, bw.buf)
|
||||
releaseBuffer(bw.buf)
|
||||
|
||||
@ -186,7 +186,7 @@ func BenchmarkTimerGracefulShutdown(b *testing.B) {
|
||||
})
|
||||
|
||||
// 创建一些定时器
|
||||
for j := 0; j < 10; j++ {
|
||||
for range 10 {
|
||||
manager.At(1*time.Millisecond, callback, nil)
|
||||
}
|
||||
|
||||
|
||||
@ -136,7 +136,7 @@ func TestLuaContextPoolMultipleReuse(t *testing.T) {
|
||||
defer engine.Close()
|
||||
|
||||
// 循环多次 release/acquire,验证状态始终正确
|
||||
for i := 0; i < 100; i++ {
|
||||
for range 100 {
|
||||
ctx := NewContext(engine, nil)
|
||||
ctx.SetVariable("iter", "val")
|
||||
ctx.Write([]byte("data"))
|
||||
|
||||
@ -174,7 +174,7 @@ func TestLuaMiddlewarePerformanceOverhead(t *testing.T) {
|
||||
// 测量 100 次执行的总时间
|
||||
iterations := 100
|
||||
start := time.Now()
|
||||
for i := 0; i < iterations; i++ {
|
||||
for range iterations {
|
||||
ctx := &fasthttp.RequestCtx{}
|
||||
handler(ctx)
|
||||
}
|
||||
|
||||
@ -460,7 +460,7 @@ func TestSchedulerConcurrentAccess(t *testing.T) {
|
||||
// 创建多个 LState 并发访问
|
||||
done := make(chan bool, 10)
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
for range 10 {
|
||||
go func() {
|
||||
L := glua.NewState()
|
||||
defer L.Close()
|
||||
@ -477,7 +477,7 @@ func TestSchedulerConcurrentAccess(t *testing.T) {
|
||||
}
|
||||
|
||||
// 等待所有 goroutine 完成
|
||||
for i := 0; i < 10; i++ {
|
||||
for range 10 {
|
||||
<-done
|
||||
}
|
||||
}
|
||||
|
||||
@ -116,7 +116,7 @@ type SocketOperation struct {
|
||||
LastActivity time.Time
|
||||
|
||||
// Result 操作结果
|
||||
Result interface{}
|
||||
Result any
|
||||
|
||||
// Error 操作错误
|
||||
Error error
|
||||
@ -125,7 +125,7 @@ type SocketOperation struct {
|
||||
Done chan struct{}
|
||||
|
||||
// completed 原子标记,1=已完成,0=未完成
|
||||
completed int32
|
||||
completed atomic.Int32
|
||||
}
|
||||
|
||||
// IsCompleted 检查操作是否已完成。
|
||||
@ -133,7 +133,7 @@ type SocketOperation struct {
|
||||
// 返回值:
|
||||
// - bool: true 表示已完成
|
||||
func (op *SocketOperation) IsCompleted() bool {
|
||||
return atomic.LoadInt32(&op.completed) == 1
|
||||
return op.completed.Load() == 1
|
||||
}
|
||||
|
||||
// Complete 标记操作完成。
|
||||
@ -143,8 +143,8 @@ func (op *SocketOperation) IsCompleted() bool {
|
||||
// 参数:
|
||||
// - result: 操作结果
|
||||
// - err: 操作错误(nil 表示成功)
|
||||
func (op *SocketOperation) Complete(result interface{}, err error) {
|
||||
if atomic.CompareAndSwapInt32(&op.completed, 0, 1) {
|
||||
func (op *SocketOperation) Complete(result any, err error) {
|
||||
if op.completed.CompareAndSwap(0, 1) {
|
||||
op.Result = result
|
||||
op.Error = err
|
||||
close(op.Done)
|
||||
@ -161,7 +161,7 @@ func (op *SocketOperation) Complete(result interface{}, err error) {
|
||||
// 返回值:
|
||||
// - interface{}: 操作结果
|
||||
// - error: 操作错误或上下文取消错误
|
||||
func (op *SocketOperation) Wait(ctx context.Context) (interface{}, error) {
|
||||
func (op *SocketOperation) Wait(ctx context.Context) (any, error) {
|
||||
select {
|
||||
case <-op.Done:
|
||||
return op.Result, op.Error
|
||||
@ -319,7 +319,7 @@ func (cm *CosocketManager) StartOperation(socket *TCPSocket, opType OperationTyp
|
||||
// - id: 操作 ID
|
||||
// - result: 操作结果
|
||||
// - err: 操作错误
|
||||
func (cm *CosocketManager) CompleteOperation(id uint64, result interface{}, err error) {
|
||||
func (cm *CosocketManager) CompleteOperation(id uint64, result any, err error) {
|
||||
cm.mu.Lock()
|
||||
op, exists := cm.operations[id]
|
||||
if exists {
|
||||
|
||||
@ -5,6 +5,8 @@
|
||||
// 作者:xfy
|
||||
package matcher
|
||||
|
||||
import "maps"
|
||||
|
||||
import "fmt"
|
||||
|
||||
// ConflictDetector 路径冲突检测器。
|
||||
@ -61,9 +63,7 @@ func (cd *ConflictDetector) Exists(path string) bool {
|
||||
// - map[string]string: 路径到 location 类型的映射
|
||||
func (cd *ConflictDetector) GetRegisteredPaths() map[string]string {
|
||||
result := make(map[string]string, len(cd.registeredPaths))
|
||||
for k, v := range cd.registeredPaths {
|
||||
result[k] = v
|
||||
}
|
||||
maps.Copy(result, cd.registeredPaths)
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
@ -91,7 +91,7 @@ func BenchmarkBodyLimitPathMatching(b *testing.B) {
|
||||
}
|
||||
|
||||
// 添加大量路径配置
|
||||
for i := 0; i < 100; i++ {
|
||||
for i := range 100 {
|
||||
path := "/api/v" + string(rune('0'+i%10)) + "/resource" + string(rune('0'+i%10))
|
||||
size := "1mb"
|
||||
if i%3 == 0 {
|
||||
|
||||
@ -260,13 +260,7 @@ func (r *slowReader) Read(p []byte) (n int, err error) {
|
||||
|
||||
// 每次只读取 chunkSize 字节
|
||||
remaining := len(r.data) - r.pos
|
||||
toRead := r.chunkSize
|
||||
if toRead > remaining {
|
||||
toRead = remaining
|
||||
}
|
||||
if toRead > len(p) {
|
||||
toRead = len(p)
|
||||
}
|
||||
toRead := min(min(r.chunkSize, remaining), len(p))
|
||||
|
||||
n = toRead
|
||||
copy(p, r.data[r.pos:r.pos+toRead])
|
||||
|
||||
@ -13,6 +13,7 @@ package compression
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/andybalholm/brotli"
|
||||
@ -82,13 +83,7 @@ func TestDefaultCompressibleTypes(t *testing.T) {
|
||||
// 检查关键类型
|
||||
expected := []string{"text/html", "text/css", "application/json"}
|
||||
for _, e := range expected {
|
||||
found := false
|
||||
for _, t := range types {
|
||||
if t == e {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
found := slices.Contains(types, e)
|
||||
if !found {
|
||||
t.Errorf("Expected type %s in default list", e)
|
||||
}
|
||||
|
||||
@ -429,14 +429,12 @@ func TestRateLimitedWriter_Concurrent(t *testing.T) {
|
||||
writesPerGoroutine := 10
|
||||
data := []byte("test")
|
||||
|
||||
for i := 0; i < goroutines; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for j := 0; j < writesPerGoroutine; j++ {
|
||||
for range goroutines {
|
||||
wg.Go(func() {
|
||||
for range writesPerGoroutine {
|
||||
_, _ = w.Write(data)
|
||||
}
|
||||
}()
|
||||
})
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
@ -65,10 +65,7 @@ func (w *RateLimitedWriter) Write(p []byte) (int, error) {
|
||||
w.bucket = w.rate // 简化:每秒补充 rate 个令牌
|
||||
}
|
||||
|
||||
chunk := int64(n - written)
|
||||
if chunk > w.bucket {
|
||||
chunk = w.bucket
|
||||
}
|
||||
chunk := min(int64(n-written), w.bucket)
|
||||
|
||||
nw, err := w.writer.Write(p[written : written+int(chunk)])
|
||||
written += nw
|
||||
|
||||
@ -15,6 +15,8 @@
|
||||
// 作者:xfy
|
||||
package middleware
|
||||
|
||||
import "slices"
|
||||
|
||||
import "github.com/valyala/fasthttp"
|
||||
|
||||
// Middleware 中间件接口,定义中间件的标准方法。
|
||||
@ -72,8 +74,8 @@ func NewChain(middlewares ...Middleware) *Chain {
|
||||
// A -> B -> C -> H -> C -> B -> A
|
||||
func (c *Chain) Apply(final fasthttp.RequestHandler) fasthttp.RequestHandler {
|
||||
handler := final
|
||||
for i := len(c.middlewares) - 1; i >= 0; i-- {
|
||||
handler = c.middlewares[i].Process(handler)
|
||||
for _, v := range slices.Backward(c.middlewares) {
|
||||
handler = v.Process(handler)
|
||||
}
|
||||
return handler
|
||||
}
|
||||
|
||||
@ -27,6 +27,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@ -218,10 +219,8 @@ func (ac *AccessControl) Check(ip net.IP) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, c := range ac.geoipConfig.DenyCountries {
|
||||
if country == c {
|
||||
return false
|
||||
}
|
||||
if slices.Contains(ac.geoipConfig.DenyCountries, country) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -238,10 +237,8 @@ checkAllow:
|
||||
if ac.geoip != nil && ac.geoipConfig.Enabled {
|
||||
country, err := ac.geoip.LookupCountry(ip)
|
||||
if err == nil && country != geoPrivateDeny {
|
||||
for _, c := range ac.geoipConfig.AllowCountries {
|
||||
if country == c {
|
||||
return true
|
||||
}
|
||||
if slices.Contains(ac.geoipConfig.AllowCountries, country) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -415,8 +412,8 @@ func (ac *AccessControl) getClientIP(ctx *fasthttp.RequestCtx) net.IP {
|
||||
// 使用右侧(最接近客户端)的非可信 IP
|
||||
if xff := ctx.Request.Header.Peek("X-Forwarded-For"); len(xff) > 0 {
|
||||
ips := strings.Split(string(xff), ",")
|
||||
for i := len(ips) - 1; i >= 0; i-- {
|
||||
ipStr := strings.TrimSpace(ips[i])
|
||||
for _, v := range slices.Backward(ips) {
|
||||
ipStr := strings.TrimSpace(v)
|
||||
if ip := net.ParseIP(ipStr); ip != nil {
|
||||
// 检查该 IP 是否在可信代理列表中
|
||||
trusted := false
|
||||
|
||||
@ -296,8 +296,8 @@ func parseArgon2idHash(hash string) (argon2Params, []byte, []byte, error) {
|
||||
paramsStr := parts[3]
|
||||
params := defaultArgon2Params
|
||||
|
||||
paramParts := strings.Split(paramsStr, ",")
|
||||
for _, p := range paramParts {
|
||||
paramParts := strings.SplitSeq(paramsStr, ",")
|
||||
for p := range paramParts {
|
||||
kv := strings.Split(p, "=")
|
||||
if len(kv) != 2 {
|
||||
continue
|
||||
@ -362,13 +362,13 @@ func (ba *BasicAuth) extractCredentials(ctx *fasthttp.RequestCtx) (string, strin
|
||||
|
||||
// 分割用户名:密码
|
||||
credentials := string(decoded)
|
||||
idx := strings.Index(credentials, ":")
|
||||
if idx == -1 {
|
||||
before, after, ok := strings.Cut(credentials, ":")
|
||||
if !ok {
|
||||
return "", "", false
|
||||
}
|
||||
|
||||
username := credentials[:idx]
|
||||
password := credentials[idx+1:]
|
||||
username := before
|
||||
password := after
|
||||
|
||||
return username, password, true
|
||||
}
|
||||
|
||||
@ -169,8 +169,8 @@ func parseAuthURL(url string) (string, bool, error) {
|
||||
if strings.HasPrefix(url, "https://") {
|
||||
isTLS = true
|
||||
url = strings.TrimPrefix(url, "https://")
|
||||
} else if strings.HasPrefix(url, "http://") {
|
||||
url = strings.TrimPrefix(url, "http://")
|
||||
} else if after, ok := strings.CutPrefix(url, "http://"); ok {
|
||||
url = after
|
||||
}
|
||||
|
||||
// 提取主机部分
|
||||
|
||||
@ -329,7 +329,7 @@ func TestGeoIPLookup_ConcurrentAccess(t *testing.T) {
|
||||
geoip := setupTestGeoIP(t, "allow")
|
||||
|
||||
done := make(chan bool, 10)
|
||||
for i := 0; i < 10; i++ {
|
||||
for range 10 {
|
||||
go func() {
|
||||
_, err := geoip.LookupCountry(net.ParseIP("2.125.160.216"))
|
||||
assert.NoError(t, err)
|
||||
@ -337,7 +337,7 @@ func TestGeoIPLookup_ConcurrentAccess(t *testing.T) {
|
||||
}()
|
||||
}
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
for range 10 {
|
||||
<-done
|
||||
}
|
||||
}
|
||||
|
||||
@ -142,7 +142,7 @@ func newTokenBucketLimiter(cfg *config.RateLimitConfig) (*RateLimiter, error) {
|
||||
}
|
||||
|
||||
// 初始化 16 个分段锁桶
|
||||
for i := 0; i < 16; i++ {
|
||||
for i := range 16 {
|
||||
rl.shards[i] = shardedBucket{
|
||||
buckets: make(map[string]*tokenBucket),
|
||||
}
|
||||
@ -437,7 +437,7 @@ func (rl *RateLimiter) Reset(key string) {
|
||||
//
|
||||
// 清空所有桶记录,所有客户端将重新开始计数。
|
||||
func (rl *RateLimiter) ResetAll() {
|
||||
for i := 0; i < 16; i++ {
|
||||
for i := range 16 {
|
||||
rl.shards[i].mu.Lock()
|
||||
rl.shards[i].buckets = make(map[string]*tokenBucket)
|
||||
rl.shards[i].mu.Unlock()
|
||||
@ -453,7 +453,7 @@ func (rl *RateLimiter) ResetAll() {
|
||||
// - maxAge: 未使用桶的最大保留时间
|
||||
func (rl *RateLimiter) Cleanup(maxAge time.Duration) {
|
||||
now := time.Now()
|
||||
for i := 0; i < 16; i++ {
|
||||
for i := range 16 {
|
||||
shard := &rl.shards[i]
|
||||
shard.mu.Lock()
|
||||
for key, bucket := range shard.buckets {
|
||||
@ -526,7 +526,7 @@ type RateLimitStats struct {
|
||||
// - RateLimitStats: 包含桶数量、速率和容量的统计对象
|
||||
func (rl *RateLimiter) GetStats() RateLimitStats {
|
||||
totalBuckets := 0
|
||||
for i := 0; i < 16; i++ {
|
||||
for i := range 16 {
|
||||
rl.shards[i].mu.RLock()
|
||||
totalBuckets += len(rl.shards[i].buckets)
|
||||
rl.shards[i].mu.RUnlock()
|
||||
@ -551,7 +551,7 @@ type ConnLimiter struct {
|
||||
keyFunc KeyFunc
|
||||
counts map[string]int64
|
||||
max int
|
||||
current int64
|
||||
current atomic.Int64
|
||||
mu sync.RWMutex
|
||||
perKey bool
|
||||
}
|
||||
@ -601,9 +601,9 @@ func NewConnLimiter(maxConns int, perKey bool, keyType string) (*ConnLimiter, er
|
||||
func (cl *ConnLimiter) Acquire(ctx *fasthttp.RequestCtx) bool {
|
||||
if !cl.perKey {
|
||||
// 全局限制(原子递增后检查溢出,避免 TOCTOU 竞态)
|
||||
current := atomic.AddInt64(&cl.current, 1)
|
||||
current := cl.current.Add(1)
|
||||
if current > int64(cl.max) {
|
||||
atomic.AddInt64(&cl.current, -1)
|
||||
cl.current.Add(-1)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@ -632,7 +632,7 @@ func (cl *ConnLimiter) Acquire(ctx *fasthttp.RequestCtx) bool {
|
||||
// - ctx: FastHTTP 请求上下文
|
||||
func (cl *ConnLimiter) Release(ctx *fasthttp.RequestCtx) {
|
||||
if !cl.perKey {
|
||||
atomic.AddInt64(&cl.current, -1)
|
||||
cl.current.Add(-1)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@ -128,7 +128,7 @@ func BenchmarkRateLimiterCleanup_1000Buckets(b *testing.B) {
|
||||
defer rl.StopCleanup()
|
||||
|
||||
// 预创建 1000 个桶
|
||||
for i := 0; i < 1000; i++ {
|
||||
for i := range 1000 {
|
||||
rl.Allow("192.168.0." + string(rune(i)))
|
||||
}
|
||||
|
||||
@ -136,7 +136,7 @@ func BenchmarkRateLimiterCleanup_1000Buckets(b *testing.B) {
|
||||
for b.Loop() {
|
||||
rl.Cleanup(0) // 清理所有桶
|
||||
// 重新创建桶以保持测试一致性
|
||||
for j := 0; j < 1000; j++ {
|
||||
for j := range 1000 {
|
||||
rl.Allow("192.168.0." + string(rune(j)))
|
||||
}
|
||||
}
|
||||
@ -217,7 +217,7 @@ func BenchmarkRateLimiterStats(b *testing.B) {
|
||||
defer rl.StopCleanup()
|
||||
|
||||
// 预创建一些桶
|
||||
for i := 0; i < 100; i++ {
|
||||
for i := range 100 {
|
||||
rl.Allow("192.168.0." + string(rune(i)))
|
||||
}
|
||||
|
||||
|
||||
@ -112,7 +112,7 @@ func TestRateLimiterAllow(t *testing.T) {
|
||||
key := "test-key"
|
||||
|
||||
// Should allow burst requests
|
||||
for i := 0; i < 10; i++ {
|
||||
for i := range 10 {
|
||||
if !rl.Allow(key) {
|
||||
t.Errorf("Expected request %d to be allowed", i+1)
|
||||
}
|
||||
@ -141,7 +141,7 @@ func TestRateLimiterTokenRefill(t *testing.T) {
|
||||
key := "refill-test"
|
||||
|
||||
// Exhaust the burst
|
||||
for i := 0; i < 100; i++ {
|
||||
for range 100 {
|
||||
rl.Allow(key)
|
||||
}
|
||||
|
||||
@ -607,7 +607,7 @@ func TestRateLimiter_GetRetryAfter(t *testing.T) {
|
||||
|
||||
// 创建一个桶并消耗令牌
|
||||
key := "test-key"
|
||||
for i := 0; i < 10; i++ {
|
||||
for range 10 {
|
||||
rl.Allow(key)
|
||||
}
|
||||
|
||||
|
||||
@ -94,7 +94,7 @@ func NewSlidingWindowLimiter(window time.Duration, limit int, precise bool) *Sli
|
||||
precise: precise,
|
||||
}
|
||||
// 初始化16个分段锁桶
|
||||
for i := 0; i < 16; i++ {
|
||||
for i := range 16 {
|
||||
s.buckets[i] = &limiterBucket{
|
||||
counters: make(map[string]*windowCounter),
|
||||
}
|
||||
@ -225,7 +225,7 @@ func (s *SlidingWindowLimiter) Reset(key string) {
|
||||
|
||||
// ResetAll 重置所有计数器。
|
||||
func (s *SlidingWindowLimiter) ResetAll() {
|
||||
for i := 0; i < 16; i++ {
|
||||
for i := range 16 {
|
||||
bucket := s.buckets[i]
|
||||
bucket.mu.Lock()
|
||||
bucket.counters = make(map[string]*windowCounter)
|
||||
@ -239,7 +239,7 @@ func (s *SlidingWindowLimiter) ResetAll() {
|
||||
// - maxAge: 未使用计数器的最大保留时间
|
||||
func (s *SlidingWindowLimiter) Cleanup(maxAge time.Duration) {
|
||||
now := time.Now()
|
||||
for i := 0; i < 16; i++ {
|
||||
for i := range 16 {
|
||||
bucket := s.buckets[i]
|
||||
bucket.mu.Lock()
|
||||
for key, counter := range bucket.counters {
|
||||
@ -267,7 +267,7 @@ type SlidingWindowStats struct {
|
||||
// GetStats 返回统计信息。
|
||||
func (s *SlidingWindowLimiter) GetStats() SlidingWindowStats {
|
||||
totalKeys := 0
|
||||
for i := 0; i < 16; i++ {
|
||||
for i := range 16 {
|
||||
bucket := s.buckets[i]
|
||||
bucket.mu.RLock()
|
||||
totalKeys += len(bucket.counters)
|
||||
|
||||
@ -73,7 +73,7 @@ func BenchmarkSlidingWindowCleanup(b *testing.B) {
|
||||
sw := NewSlidingWindowLimiter(time.Second, 1000, false)
|
||||
|
||||
// 预创建 100 个键
|
||||
for i := 0; i < 100; i++ {
|
||||
for i := range 100 {
|
||||
sw.Allow("192.168.0." + string(rune(i)))
|
||||
}
|
||||
|
||||
@ -89,7 +89,7 @@ func BenchmarkSlidingWindowGetCount(b *testing.B) {
|
||||
key := "192.168.1.100"
|
||||
|
||||
// 预先添加一些请求
|
||||
for i := 0; i < 100; i++ {
|
||||
for range 100 {
|
||||
sw.Allow(key)
|
||||
}
|
||||
|
||||
@ -131,7 +131,7 @@ func BenchmarkSlidingWindowStats(b *testing.B) {
|
||||
sw := NewSlidingWindowLimiter(time.Second, 10000, false)
|
||||
|
||||
// 预创建一些键
|
||||
for i := 0; i < 50; i++ {
|
||||
for i := range 50 {
|
||||
sw.Allow("192.168.0." + string(rune(i)))
|
||||
}
|
||||
|
||||
|
||||
@ -42,7 +42,7 @@ func TestSlidingWindowLimiter_Allow(t *testing.T) {
|
||||
t.Run("近似模式-允许请求", func(t *testing.T) {
|
||||
limiter := NewSlidingWindowLimiter(time.Second, 10, false)
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
for i := range 10 {
|
||||
if !limiter.Allow("test-key") {
|
||||
t.Errorf("请求 %d 应该被允许", i+1)
|
||||
}
|
||||
@ -53,7 +53,7 @@ func TestSlidingWindowLimiter_Allow(t *testing.T) {
|
||||
limiter := NewSlidingWindowLimiter(time.Second, 5, false)
|
||||
|
||||
// 发送 5 个请求
|
||||
for i := 0; i < 5; i++ {
|
||||
for range 5 {
|
||||
limiter.Allow("test-key")
|
||||
}
|
||||
|
||||
@ -67,7 +67,7 @@ func TestSlidingWindowLimiter_Allow(t *testing.T) {
|
||||
t.Run("精确模式-允许请求", func(t *testing.T) {
|
||||
limiter := NewSlidingWindowLimiter(time.Second, 10, true)
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
for i := range 10 {
|
||||
if !limiter.Allow("test-key") {
|
||||
t.Errorf("请求 %d 应该被允许", i+1)
|
||||
}
|
||||
@ -78,7 +78,7 @@ func TestSlidingWindowLimiter_Allow(t *testing.T) {
|
||||
limiter := NewSlidingWindowLimiter(time.Second, 3, true)
|
||||
|
||||
// 发送 3 个请求
|
||||
for i := 0; i < 3; i++ {
|
||||
for i := range 3 {
|
||||
if !limiter.Allow("test-key") {
|
||||
t.Errorf("请求 %d 应该被允许", i+1)
|
||||
}
|
||||
@ -108,7 +108,7 @@ func TestSlidingWindowLimiter_Reset(t *testing.T) {
|
||||
limiter := NewSlidingWindowLimiter(time.Second, 5, true)
|
||||
|
||||
// 发送一些请求
|
||||
for i := 0; i < 5; i++ {
|
||||
for range 5 {
|
||||
limiter.Allow("test-key")
|
||||
}
|
||||
|
||||
@ -173,7 +173,7 @@ func TestSlidingWindowLimiter_GetCount(t *testing.T) {
|
||||
limiter := NewSlidingWindowLimiter(time.Second, 10, true)
|
||||
|
||||
// 发送 3 个请求
|
||||
for i := 0; i < 3; i++ {
|
||||
for range 3 {
|
||||
limiter.Allow("test-key")
|
||||
}
|
||||
|
||||
|
||||
@ -165,7 +165,7 @@ func TestConcurrentAccess(t *testing.T) {
|
||||
|
||||
// 并发调用 DetectContentType
|
||||
wg.Add(numGoroutines)
|
||||
for i := 0; i < numGoroutines; i++ {
|
||||
for range numGoroutines {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
DetectContentType("test.html")
|
||||
@ -174,7 +174,7 @@ func TestConcurrentAccess(t *testing.T) {
|
||||
|
||||
// 并发调用 AddTypes
|
||||
wg.Add(numGoroutines)
|
||||
for i := 0; i < numGoroutines; i++ {
|
||||
for i := range numGoroutines {
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
AddTypes(map[string]string{".test": "application/x-test"})
|
||||
@ -183,7 +183,7 @@ func TestConcurrentAccess(t *testing.T) {
|
||||
|
||||
// 并发调用 SetDefaultType 和 GetDefaultType
|
||||
wg.Add(numGoroutines)
|
||||
for i := 0; i < numGoroutines; i++ {
|
||||
for range numGoroutines {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
SetDefaultType("text/plain")
|
||||
|
||||
@ -71,7 +71,7 @@ func BenchmarkConnectionPool_Normal(b *testing.B) {
|
||||
}
|
||||
|
||||
// 预热连接池
|
||||
for i := 0; i < 10; i++ {
|
||||
for range 10 {
|
||||
ctx := &fasthttp.RequestCtx{}
|
||||
ctx.Request.SetRequestURI("/api/test")
|
||||
ctx.Request.Header.SetMethod(fasthttp.MethodGet)
|
||||
@ -156,7 +156,7 @@ func BenchmarkConnectionPool_SmallBody(b *testing.B) {
|
||||
}
|
||||
|
||||
// 预热
|
||||
for i := 0; i < 5; i++ {
|
||||
for range 5 {
|
||||
ctx := &fasthttp.RequestCtx{}
|
||||
ctx.Request.SetRequestURI("/api/test")
|
||||
p.ServeHTTP(ctx)
|
||||
@ -221,7 +221,7 @@ func BenchmarkConnectionPool_MultiTarget(b *testing.B) {
|
||||
targets := make([]*loadbalance.Target, 3)
|
||||
cleanups := make([]func(), 3)
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
for i := range 3 {
|
||||
addr, cleanup := setupInmemoryBackend([]byte("backend" + strconv.Itoa(i)))
|
||||
cleanups[i] = cleanup
|
||||
targets[i] = &loadbalance.Target{
|
||||
@ -295,4 +295,4 @@ func BenchmarkHostClient_AcquireRelease(b *testing.B) {
|
||||
fasthttp.ReleaseRequest(req)
|
||||
fasthttp.ReleaseResponse(resp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -74,7 +74,7 @@ const (
|
||||
// 注意:从 pool 获取的 map 使用后不能 Put 回 pool,
|
||||
// 因为 cache.Set 存储了 map 引用。
|
||||
var headersPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
New: func() any {
|
||||
return make(map[string]string, 20)
|
||||
},
|
||||
}
|
||||
|
||||
@ -187,7 +187,7 @@ func BenchmarkProxyForwardMultipleTargets(b *testing.B) {
|
||||
targets := make([]*loadbalance.Target, numTargets)
|
||||
cleanups := make([]func(), numTargets)
|
||||
|
||||
for i := 0; i < numTargets; i++ {
|
||||
for i := range numTargets {
|
||||
addr, cleanup := setupMockBackend(smallBody)
|
||||
cleanups[i] = cleanup
|
||||
targets[i] = &loadbalance.Target{
|
||||
|
||||
@ -110,10 +110,7 @@ func (p *Proxy) startDNSRefreshLoop() {
|
||||
}
|
||||
|
||||
// 刷新间隔为 TTL / 2
|
||||
interval := ttl / 2
|
||||
if interval < time.Second {
|
||||
interval = time.Second
|
||||
}
|
||||
interval := max(ttl/2, time.Second)
|
||||
|
||||
ticker := time.NewTicker(interval)
|
||||
defer ticker.Stop()
|
||||
|
||||
@ -39,7 +39,7 @@ import (
|
||||
// wsBufPool WebSocket 数据转发 buffer pool。
|
||||
// 复用 32KB buffer 避免每次 copyData 调用分配。
|
||||
var wsBufPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
New: func() any {
|
||||
buf := make([]byte, 32*1024)
|
||||
return &buf
|
||||
},
|
||||
|
||||
@ -682,7 +682,7 @@ func TestWebSocketBridge_Concurrent(t *testing.T) {
|
||||
var wg sync.WaitGroup
|
||||
errCh := make(chan error, numBridges)
|
||||
|
||||
for i := 0; i < numBridges; i++ {
|
||||
for i := range numBridges {
|
||||
wg.Add(1)
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
|
||||
@ -395,14 +395,12 @@ func TestMockDNSConcurrentAccess(t *testing.T) {
|
||||
concurrency := 10
|
||||
iterations := 100
|
||||
|
||||
for i := 0; i < concurrency; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for j := 0; j < iterations; j++ {
|
||||
for range concurrency {
|
||||
wg.Go(func() {
|
||||
for range iterations {
|
||||
_, _ = resolver.LookupHostWithCache(context.Background(), "localhost")
|
||||
}
|
||||
}()
|
||||
})
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
@ -492,16 +490,14 @@ func TestMockDNSCacheEntryConcurrency(t *testing.T) {
|
||||
var readCount atomic.Int64
|
||||
|
||||
// 并发读取
|
||||
for i := 0; i < 100; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for range 100 {
|
||||
wg.Go(func() {
|
||||
entry.mu.RLock()
|
||||
_ = entry.IPs
|
||||
_ = entry.ExpiresAt
|
||||
entry.mu.RUnlock()
|
||||
readCount.Add(1)
|
||||
}()
|
||||
})
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
@ -373,10 +373,7 @@ func (r *DNSResolver) Start() error {
|
||||
// refreshLoop 后台刷新循环。
|
||||
func (r *DNSResolver) refreshLoop() {
|
||||
// 刷新间隔为 TTL / 2
|
||||
interval := r.config.TTL() / 2
|
||||
if interval < time.Second {
|
||||
interval = time.Second
|
||||
}
|
||||
interval := max(r.config.TTL()/2, time.Second)
|
||||
|
||||
ticker := time.NewTicker(interval)
|
||||
defer ticker.Stop()
|
||||
|
||||
@ -27,7 +27,7 @@ func createTestResolver() *DNSResolver {
|
||||
r := New(cfg).(*DNSResolver)
|
||||
|
||||
// 预填充缓存条目,模拟真实的解析场景
|
||||
for i := 0; i < 100; i++ {
|
||||
for i := range 100 {
|
||||
host := fmt.Sprintf("host%d.example.com", i)
|
||||
r.storeCache(host, &DNSCacheEntry{
|
||||
IPs: []string{fmt.Sprintf("192.168.1.%d", i%256), fmt.Sprintf("192.168.2.%d", i%256)},
|
||||
@ -214,7 +214,7 @@ func BenchmarkDNSResolverMixedWorkload(b *testing.B) {
|
||||
ctx := context.Background()
|
||||
|
||||
// 预填充一些缓存
|
||||
for i := 0; i < 50; i++ {
|
||||
for i := range 50 {
|
||||
host := fmt.Sprintf("cached%d.example.com", i)
|
||||
r.storeCache(host, &DNSCacheEntry{
|
||||
IPs: []string{fmt.Sprintf("192.168.1.%d", i%256)},
|
||||
|
||||
@ -203,7 +203,7 @@ func TestCacheHitRate(t *testing.T) {
|
||||
r.mu.Unlock()
|
||||
|
||||
// 3 次命中
|
||||
for i := 0; i < 3; i++ {
|
||||
for range 3 {
|
||||
_, _ = r.LookupHostWithCache(context.Background(), "test.example.com")
|
||||
}
|
||||
|
||||
@ -356,7 +356,7 @@ func TestClearCache(t *testing.T) {
|
||||
r := New(cfg).(*DNSResolver)
|
||||
|
||||
// 添加多个缓存
|
||||
for i := 0; i < 5; i++ {
|
||||
for i := range 5 {
|
||||
host := fmt.Sprintf("test%d.example.com", i)
|
||||
r.storeCache(host, &DNSCacheEntry{
|
||||
IPs: []string{fmt.Sprintf("192.168.1.%d", i)},
|
||||
@ -397,12 +397,10 @@ func TestConcurrentAccess(t *testing.T) {
|
||||
|
||||
// 并发读取
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < 100; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for range 100 {
|
||||
wg.Go(func() {
|
||||
_, _ = r.LookupHostWithCache(context.Background(), "test.example.com")
|
||||
}()
|
||||
})
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
@ -578,7 +576,7 @@ func TestCacheSizeLimit(t *testing.T) {
|
||||
r := New(cfg).(*DNSResolver)
|
||||
|
||||
// 添加 5 个缓存条目,应淘汰 2 个
|
||||
for i := 0; i < 5; i++ {
|
||||
for i := range 5 {
|
||||
host := fmt.Sprintf("host%d.example.com", i)
|
||||
r.storeCache(host, &DNSCacheEntry{
|
||||
IPs: []string{fmt.Sprintf("192.168.1.%d", i)},
|
||||
@ -623,7 +621,7 @@ func TestCacheSizeZero(t *testing.T) {
|
||||
r := New(cfg).(*DNSResolver)
|
||||
|
||||
// 添加大量缓存条目
|
||||
for i := 0; i < 100; i++ {
|
||||
for i := range 100 {
|
||||
host := fmt.Sprintf("host%d.example.com", i)
|
||||
r.storeCache(host, &DNSCacheEntry{
|
||||
IPs: []string{fmt.Sprintf("192.168.1.%d", i%256)},
|
||||
|
||||
@ -195,10 +195,8 @@ func (p *GoroutinePool) Submit(ctx *fasthttp.RequestCtx, task Task) error {
|
||||
// worker 从任务队列获取任务执行,空闲超时后自动退出(保持最小数量)。
|
||||
func (p *GoroutinePool) startWorker() {
|
||||
atomic.AddInt32(&p.workers, 1)
|
||||
p.wg.Add(1)
|
||||
|
||||
go func() {
|
||||
defer p.wg.Done()
|
||||
p.wg.Go(func() {
|
||||
defer atomic.AddInt32(&p.workers, -1)
|
||||
|
||||
idleTimer := time.NewTimer(p.idleTimeout)
|
||||
@ -233,7 +231,7 @@ func (p *GoroutinePool) startWorker() {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
})
|
||||
}
|
||||
|
||||
// Stats 返回池的统计信息。
|
||||
|
||||
@ -57,7 +57,7 @@ func BenchmarkGoroutinePoolParallel(b *testing.B) {
|
||||
task := func(_ *fasthttp.RequestCtx) {
|
||||
// 模拟微小工作负载
|
||||
sum := 0
|
||||
for j := 0; j < 100; j++ {
|
||||
for j := range 100 {
|
||||
sum += j
|
||||
}
|
||||
_ = sum
|
||||
@ -91,7 +91,7 @@ func BenchmarkGoroutinePoolSubmit_BlockingPath(b *testing.B) {
|
||||
}
|
||||
|
||||
// 提交任务使队列保持满状态
|
||||
for i := 0; i < 5; i++ {
|
||||
for range 5 {
|
||||
go func() {
|
||||
for {
|
||||
_ = pool.Submit(ctx, slowTask)
|
||||
@ -136,7 +136,7 @@ func BenchmarkGoroutinePoolQueueFull(b *testing.B) {
|
||||
task := func(_ *fasthttp.RequestCtx) {
|
||||
// 模拟微小工作负载
|
||||
sum := 0
|
||||
for j := 0; j < 10; j++ {
|
||||
for j := range 10 {
|
||||
sum += j
|
||||
}
|
||||
_ = sum
|
||||
@ -167,7 +167,7 @@ func BenchmarkGoroutinePoolWorkerRecycle(b *testing.B) {
|
||||
time.Sleep(100 * time.Microsecond)
|
||||
}
|
||||
|
||||
for j := 0; j < 30; j++ {
|
||||
for range 30 {
|
||||
go pool.Submit(ctx, task)
|
||||
}
|
||||
|
||||
@ -201,7 +201,7 @@ func BenchmarkGoroutinePoolSubmitWithWork(b *testing.B) {
|
||||
task := func(_ *fasthttp.RequestCtx) {
|
||||
// 模拟中等计算量
|
||||
sum := 0
|
||||
for i := 0; i < 1000; i++ {
|
||||
for i := range 1000 {
|
||||
sum += i
|
||||
}
|
||||
_ = sum
|
||||
|
||||
@ -138,15 +138,13 @@ func TestPoolConcurrentSubmit(t *testing.T) {
|
||||
var counter atomic.Int32
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for range 100 {
|
||||
wg.Go(func() {
|
||||
|
||||
_ = p.Submit(nil, func(_ *fasthttp.RequestCtx) {
|
||||
counter.Add(1)
|
||||
})
|
||||
}()
|
||||
})
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
@ -395,7 +393,7 @@ func TestPoolSubmit_MultipleQueuedTasks(t *testing.T) {
|
||||
var counter atomic.Int32
|
||||
|
||||
// 快速提交多个任务
|
||||
for i := 0; i < 5; i++ {
|
||||
for range 5 {
|
||||
_ = p.Submit(nil, func(*fasthttp.RequestCtx) {
|
||||
counter.Add(1)
|
||||
})
|
||||
|
||||
@ -122,12 +122,10 @@ func TestWriteGoroutineProfile(t *testing.T) {
|
||||
|
||||
// 启动一些 goroutine
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < 5; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for range 5 {
|
||||
wg.Go(func() {
|
||||
select {}
|
||||
}()
|
||||
})
|
||||
}
|
||||
|
||||
writeGoroutineProfile(&buf)
|
||||
@ -242,7 +240,7 @@ func TestCPUProfileMutex(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
|
||||
// 启动多个 goroutine 同时操作
|
||||
for i := 0; i < 10; i++ {
|
||||
for range 10 {
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
@ -109,7 +109,7 @@ func TestStopAfterStop(t *testing.T) {
|
||||
s := New(cfg)
|
||||
|
||||
// 多次调用 StopWithTimeout 应该都是安全的
|
||||
for i := 0; i < 3; i++ {
|
||||
for i := range 3 {
|
||||
err := s.StopWithTimeout(5 * time.Second)
|
||||
if err != nil {
|
||||
t.Errorf("StopWithTimeout() call %d returned error: %v", i+1, err)
|
||||
@ -375,7 +375,7 @@ func TestTrackStats_MultipleRequests(t *testing.T) {
|
||||
wrappedHandler := s.trackStats(handler)
|
||||
|
||||
// 执行多次请求
|
||||
for i := 0; i < 10; i++ {
|
||||
for range 10 {
|
||||
ctx := &fasthttp.RequestCtx{}
|
||||
ctx.Init(&fasthttp.Request{}, nil, nil)
|
||||
wrappedHandler(ctx)
|
||||
@ -1508,7 +1508,7 @@ func TestServer_GetProxyCacheStats_AllProxiesWithCache(t *testing.T) {
|
||||
|
||||
// 创建多个带缓存的代理
|
||||
proxies := make([]*proxy.Proxy, 3)
|
||||
for i := 0; i < 3; i++ {
|
||||
for i := range 3 {
|
||||
proxyCfg := &config.ProxyConfig{
|
||||
Path: fmt.Sprintf("/api%d", i),
|
||||
LoadBalance: "round_robin",
|
||||
@ -1552,7 +1552,7 @@ func TestServer_GetProxyCacheStats_AllProxiesNoCache(t *testing.T) {
|
||||
|
||||
// 创建多个不带缓存的代理
|
||||
proxies := make([]*proxy.Proxy, 3)
|
||||
for i := 0; i < 3; i++ {
|
||||
for i := range 3 {
|
||||
proxyCfg := &config.ProxyConfig{
|
||||
Path: fmt.Sprintf("/api%d", i),
|
||||
LoadBalance: "round_robin",
|
||||
@ -2731,7 +2731,7 @@ func TestShutdownServers_RunningServers(t *testing.T) {
|
||||
servers := make([]*fasthttp.Server, 2)
|
||||
listeners := make([]net.Listener, 2)
|
||||
|
||||
for i := 0; i < 2; i++ {
|
||||
for i := range 2 {
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create listener: %v", err)
|
||||
@ -2773,7 +2773,7 @@ func TestShutdownServers_ManyServers(t *testing.T) {
|
||||
// 创建大量服务器
|
||||
count := 50
|
||||
servers := make([]*fasthttp.Server, count)
|
||||
for i := 0; i < count; i++ {
|
||||
for i := range count {
|
||||
servers[i] = &fasthttp.Server{
|
||||
Handler: func(ctx *fasthttp.RequestCtx) { ctx.SetBodyString("test") },
|
||||
}
|
||||
@ -2792,7 +2792,7 @@ func TestShutdownServers_MixedNilAndRealServers(t *testing.T) {
|
||||
|
||||
count := 20
|
||||
servers := make([]*fasthttp.Server, count)
|
||||
for i := 0; i < count; i++ {
|
||||
for i := range count {
|
||||
if i%2 == 0 {
|
||||
servers[i] = nil
|
||||
} else {
|
||||
@ -2814,16 +2814,14 @@ func TestShutdownServers_ConcurrentSafety(t *testing.T) {
|
||||
|
||||
// 并发调用 shutdownServers
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < 10; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for range 10 {
|
||||
wg.Go(func() {
|
||||
servers := []*fasthttp.Server{
|
||||
{Handler: func(ctx *fasthttp.RequestCtx) { ctx.SetBodyString("test") }},
|
||||
{Handler: func(ctx *fasthttp.RequestCtx) { ctx.SetBodyString("test") }},
|
||||
}
|
||||
_ = shutdownServers(ctx, servers)
|
||||
}()
|
||||
})
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
@ -94,7 +94,7 @@ func (u *UpgradeManager) WritePid() error {
|
||||
}
|
||||
|
||||
pid := os.Getpid()
|
||||
return os.WriteFile(u.pidFile, []byte(fmt.Sprintf("%d", pid)), 0o644)
|
||||
return os.WriteFile(u.pidFile, fmt.Appendf(nil, "%d", pid), 0o644)
|
||||
}
|
||||
|
||||
// ReadOldPid 读取旧进程 PID。
|
||||
@ -223,7 +223,7 @@ func (u *UpgradeManager) GracefulUpgrade(newBinary string) error {
|
||||
|
||||
// 写入新 PID 到文件
|
||||
if u.pidFile != "" {
|
||||
_ = os.WriteFile(u.pidFile, []byte(fmt.Sprintf("%d", newPid)), 0o644)
|
||||
_ = os.WriteFile(u.pidFile, fmt.Appendf(nil, "%d", newPid), 0o644)
|
||||
}
|
||||
|
||||
// 启动 goroutine 等待子进程结束,避免产生僵尸进程
|
||||
|
||||
@ -184,7 +184,7 @@ func TestSessionTicketManager_KeyRetention(t *testing.T) {
|
||||
defer mgr.Stop()
|
||||
|
||||
// 生成多个密钥
|
||||
for i := 0; i < 5; i++ {
|
||||
for i := range 5 {
|
||||
if err := mgr.RotateKey(); err != nil {
|
||||
t.Fatalf("RotateKey() failed at iteration %d: %v", i, err)
|
||||
}
|
||||
@ -382,7 +382,7 @@ func TestSessionTicketManager_ConcurrentAccess(t *testing.T) {
|
||||
|
||||
// 协程 1: 持续获取密钥
|
||||
go func() {
|
||||
for i := 0; i < 100; i++ {
|
||||
for range 100 {
|
||||
_ = mgr.GetKeys()
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
@ -391,7 +391,7 @@ func TestSessionTicketManager_ConcurrentAccess(t *testing.T) {
|
||||
|
||||
// 协程 2: 持续获取状态
|
||||
go func() {
|
||||
for i := 0; i < 100; i++ {
|
||||
for range 100 {
|
||||
_ = mgr.GetStatus()
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
@ -400,7 +400,7 @@ func TestSessionTicketManager_ConcurrentAccess(t *testing.T) {
|
||||
|
||||
// 协程 3: 手动轮换
|
||||
go func() {
|
||||
for i := 0; i < 20; i++ {
|
||||
for range 20 {
|
||||
_ = mgr.RotateKey()
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
}
|
||||
@ -408,7 +408,7 @@ func TestSessionTicketManager_ConcurrentAccess(t *testing.T) {
|
||||
}()
|
||||
|
||||
// 等待所有协程完成
|
||||
for i := 0; i < 3; i++ {
|
||||
for range 3 {
|
||||
<-done
|
||||
}
|
||||
|
||||
@ -442,7 +442,7 @@ func BenchmarkSessionTicketManager_GetKeys(b *testing.B) {
|
||||
defer mgr.Stop()
|
||||
|
||||
// 预生成多个密钥
|
||||
for i := 0; i < 2; i++ {
|
||||
for range 2 {
|
||||
_ = mgr.RotateKey()
|
||||
}
|
||||
|
||||
|
||||
@ -557,7 +557,7 @@ func matchMarker(data []byte, marker []byte) bool {
|
||||
if len(data) < len(marker) {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < len(marker); i++ {
|
||||
for i := range marker {
|
||||
if data[i] != marker[i] {
|
||||
return false
|
||||
}
|
||||
|
||||
@ -368,7 +368,7 @@ func BenchmarkOCSPStapling_GetStatus(b *testing.B) {
|
||||
defer ocspMgr.Stop()
|
||||
|
||||
// 注册一些模拟状态
|
||||
for i := 0; i < 10; i++ {
|
||||
for i := range 10 {
|
||||
serial := string(rune('0' + i))
|
||||
ocspMgr.mu.Lock()
|
||||
ocspMgr.responses[serial] = &ocspResponse{
|
||||
@ -430,7 +430,7 @@ func BenchmarkSessionResumption(b *testing.B) {
|
||||
defer sessionMgr.Stop()
|
||||
|
||||
// 预热密钥轮换
|
||||
for i := 0; i < 2; i++ {
|
||||
for range 2 {
|
||||
_ = sessionMgr.RotateKey()
|
||||
}
|
||||
|
||||
@ -566,7 +566,7 @@ func BenchmarkSessionTicketManager_ApplyToTLSConfig(b *testing.B) {
|
||||
}
|
||||
defer sessionMgr.Stop()
|
||||
|
||||
for i := 0; i < 2; i++ {
|
||||
for range 2 {
|
||||
_ = sessionMgr.RotateKey()
|
||||
}
|
||||
|
||||
|
||||
@ -65,7 +65,7 @@ func generateMultipleCerts(t *testing.T, count int) []byte {
|
||||
t.Helper()
|
||||
|
||||
var pemData []byte
|
||||
for i := 0; i < count; i++ {
|
||||
for i := range count {
|
||||
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate private key: %v", err)
|
||||
|
||||
@ -63,7 +63,7 @@ type Balancer interface {
|
||||
// 使用原子计数器实现线程安全的轮询选择,每次选择后计数器递增,
|
||||
// 确保请求均匀分布到所有健康目标。
|
||||
type roundRobin struct {
|
||||
counter uint64
|
||||
counter atomic.Uint64
|
||||
healthyPool sync.Pool
|
||||
}
|
||||
|
||||
@ -94,7 +94,7 @@ func (r *roundRobin) Select(targets []*Target) *Target {
|
||||
r.healthyPool.Put(healthyPtr)
|
||||
return nil
|
||||
}
|
||||
idx := atomic.AddUint64(&r.counter, 1) - 1
|
||||
idx := r.counter.Add(1) - 1
|
||||
result := healthy[idx%uint64(len(healthy))]
|
||||
r.healthyPool.Put(healthyPtr)
|
||||
return result
|
||||
@ -147,7 +147,7 @@ func (l *leastConn) Select(targets []*Target) *Target {
|
||||
// 根据目标服务器的权重分配请求,权重高的目标获得更多请求。
|
||||
// 使用原子计数器确保线程安全,支持不同权重的目标混合使用。
|
||||
type weightedRoundRobin struct {
|
||||
counter uint64
|
||||
counter atomic.Uint64
|
||||
healthyPool sync.Pool
|
||||
}
|
||||
|
||||
@ -194,7 +194,7 @@ func (w *weightedRoundRobin) Select(targets []*Target) *Target {
|
||||
}
|
||||
|
||||
// 使用原子计数器确定位置
|
||||
idx := atomic.AddUint64(&w.counter, 1) - 1
|
||||
idx := w.counter.Add(1) - 1
|
||||
pos := int(idx % uint64(totalWeight))
|
||||
|
||||
// 找到对应位置的目标
|
||||
|
||||
@ -71,7 +71,7 @@ func BenchmarkStreamFilterHealthyPreallocated(b *testing.B) {
|
||||
|
||||
// 创建目标列表
|
||||
targets := make([]*Target, targetCount)
|
||||
for i := 0; i < targetCount; i++ {
|
||||
for i := range targetCount {
|
||||
targets[i] = &Target{
|
||||
addr: fmt.Sprintf("backend%d:8080", i),
|
||||
weight: 1,
|
||||
@ -119,7 +119,7 @@ func BenchmarkUDPSessionAllocations(b *testing.B) {
|
||||
var pool *sync.Pool
|
||||
if tc.poolSize > 0 {
|
||||
pool = &sync.Pool{
|
||||
New: func() interface{} {
|
||||
New: func() any {
|
||||
return make([]byte, tc.bufSize)
|
||||
},
|
||||
}
|
||||
@ -168,7 +168,7 @@ func BenchmarkUDPSessionGetOrCreate(b *testing.B) {
|
||||
|
||||
// 预创建一些客户端地址
|
||||
clientAddrs := make([]*net.UDPAddr, 100)
|
||||
for i := 0; i < 100; i++ {
|
||||
for i := range 100 {
|
||||
clientAddrs[i], _ = net.ResolveUDPAddr("udp", fmt.Sprintf("127.0.0.1:%d", 20000+i))
|
||||
}
|
||||
|
||||
@ -206,7 +206,7 @@ func BenchmarkUDPSessionGetOnly(b *testing.B) {
|
||||
|
||||
// 预创建会话
|
||||
clientAddrs := make([]*net.UDPAddr, 100)
|
||||
for i := 0; i < 100; i++ {
|
||||
for i := range 100 {
|
||||
clientAddrs[i], _ = net.ResolveUDPAddr("udp", fmt.Sprintf("127.0.0.1:%d", 30000+i))
|
||||
// 手动创建会话
|
||||
targetAddr, _ := net.ResolveUDPAddr("udp", upstream.targets[0].addr)
|
||||
|
||||
@ -77,7 +77,7 @@ func TestWeightedRoundRobinZeroWeight(t *testing.T) {
|
||||
wrr := newWeightedRoundRobin().(*weightedRoundRobin)
|
||||
|
||||
// 权重为 0 或负数应视为权重 1
|
||||
for i := 0; i < 4; i++ {
|
||||
for range 4 {
|
||||
selected := wrr.Select(targets)
|
||||
if selected == nil {
|
||||
t.Error("Select() should return target with zero/negative weight")
|
||||
|
||||
@ -77,7 +77,7 @@ func TestRoundRobinBalancer(t *testing.T) {
|
||||
|
||||
// 测试轮询
|
||||
results := make(map[string]int)
|
||||
for i := 0; i < 6; i++ {
|
||||
for range 6 {
|
||||
selected := rr.Select(targets)
|
||||
if selected == nil {
|
||||
t.Error("Expected non-nil target")
|
||||
@ -147,7 +147,7 @@ func TestWeightedRoundRobinBalancer(t *testing.T) {
|
||||
|
||||
// 测试加权分布:3:1 比例
|
||||
results := make(map[string]int)
|
||||
for i := 0; i < 8; i++ {
|
||||
for range 8 {
|
||||
selected := wrr.Select(targets)
|
||||
if selected == nil {
|
||||
t.Error("Expected non-nil target")
|
||||
@ -306,12 +306,10 @@ func TestConcurrentConnections(t *testing.T) {
|
||||
|
||||
// 并发增加连接数
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < 100; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for range 100 {
|
||||
wg.Go(func() {
|
||||
atomic.AddInt64(&s.connCount, 1)
|
||||
}()
|
||||
})
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
@ -498,7 +496,7 @@ func TestRoundRobinBalancerWithSingleTarget(t *testing.T) {
|
||||
targets[0].healthy.Store(true)
|
||||
|
||||
// 测试单个健康目标
|
||||
for i := 0; i < 5; i++ {
|
||||
for range 5 {
|
||||
target := rb.Select(targets)
|
||||
if target == nil {
|
||||
t.Error("Expected non-nil target")
|
||||
|
||||
@ -138,7 +138,7 @@ func TestVariableExpansionPerformance(t *testing.T) {
|
||||
// 执行多次展开
|
||||
start := time.Now()
|
||||
iterations := 10000
|
||||
for i := 0; i < iterations; i++ {
|
||||
for range iterations {
|
||||
_ = vc.Expand(template)
|
||||
}
|
||||
elapsed := time.Since(start)
|
||||
|
||||
@ -353,7 +353,7 @@ func TestSetSSLClientInfoInContext_WithPeerCert(t *testing.T) {
|
||||
Raw: make([]byte, 25), // 模拟原始数据(25字节)
|
||||
}
|
||||
// 填充可预测的原始数据
|
||||
for i := 0; i < 25; i++ {
|
||||
for i := range 25 {
|
||||
cert.Raw[i] = byte(i + 1)
|
||||
}
|
||||
|
||||
@ -367,7 +367,7 @@ func TestSetSSLClientInfoInContext_WithPeerCert(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
key string
|
||||
expected interface{}
|
||||
expected any
|
||||
}{
|
||||
{"verify", VarSSLClientVerify, "SUCCESS"},
|
||||
{"peer_cert_present", "tls_peer_cert_present", true},
|
||||
|
||||
@ -53,7 +53,7 @@ type Context struct {
|
||||
|
||||
// pool 用于复用 Context
|
||||
var pool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
New: func() any {
|
||||
return &Context{
|
||||
store: make(map[string]string),
|
||||
cache: make(map[string]string),
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
package variable
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@ -455,7 +456,7 @@ func TestPoolReuse(t *testing.T) {
|
||||
ctx := mockRequestCtx(t)
|
||||
|
||||
// 获取和释放多个 context,确保没有 panic
|
||||
for i := 0; i < 10; i++ {
|
||||
for i := range 10 {
|
||||
vc := NewContext(ctx)
|
||||
vc.Set("key", "value")
|
||||
if v, ok := vc.Get("key"); !ok || v != "value" {
|
||||
@ -813,12 +814,7 @@ func TestBuiltinVarNames(t *testing.T) {
|
||||
|
||||
// 检查是否包含一些已知变量
|
||||
hasVar := func(name string) bool {
|
||||
for _, n := range names {
|
||||
if n == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return slices.Contains(names, name)
|
||||
}
|
||||
|
||||
if !hasVar("host") {
|
||||
@ -1109,9 +1105,9 @@ func TestGlobalVariablesConcurrent(_ *testing.T) {
|
||||
done := make(chan bool)
|
||||
|
||||
// 并发读取
|
||||
for i := 0; i < 10; i++ {
|
||||
for range 10 {
|
||||
go func() {
|
||||
for j := 0; j < 100; j++ {
|
||||
for range 100 {
|
||||
_, _ = GetGlobalVariable("counter")
|
||||
}
|
||||
done <- true
|
||||
@ -1119,9 +1115,9 @@ func TestGlobalVariablesConcurrent(_ *testing.T) {
|
||||
}
|
||||
|
||||
// 并发写入
|
||||
for i := 0; i < 5; i++ {
|
||||
for range 5 {
|
||||
go func() {
|
||||
for j := 0; j < 50; j++ {
|
||||
for range 50 {
|
||||
SetGlobalVariables(map[string]string{"counter": "updated"})
|
||||
}
|
||||
done <- true
|
||||
@ -1129,7 +1125,7 @@ func TestGlobalVariablesConcurrent(_ *testing.T) {
|
||||
}
|
||||
|
||||
// 等待所有 goroutine 完成
|
||||
for i := 0; i < 15; i++ {
|
||||
for range 15 {
|
||||
<-done
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user