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