Skip to content

Commit f9fc730

Browse files
authored
Fix MaxConcurrentQueries default and add circuit breaker (#425)
Major improvements: - Fix test hangs by setting default MaxConcurrentQueries to 1000 - Add circuit breaker pattern to prevent cascading failures - Limit concurrent goroutines to prevent resource exhaustion - Re-enable gosec and staticcheck linters - Add 955 lines of comprehensive tests
1 parent 5c88ac2 commit f9fc730

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

70 files changed

+1341
-296
lines changed

.golangci.yml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,12 @@ run:
88
linters:
99
disable:
1010
- errcheck
11-
- staticcheck
12-
- goconst
13-
- gosec
1411
enable:
1512
- govet
1613
- ineffassign
1714
- unused
1815
- misspell
1916
- unconvert
2017
- gocritic
18+
- gosec
19+
- staticcheck

api/api.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,8 +175,9 @@ func (a *API) Run(ctx context.Context) {
175175
a.router.GET("/metrics", a.metrics)
176176

177177
srv := &http.Server{
178-
Addr: a.addr,
179-
Handler: a.router,
178+
Addr: a.addr,
179+
Handler: a.router,
180+
ReadHeaderTimeout: 10 * time.Second,
180181
}
181182

182183
go func() {

api/router.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ func (rt *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
5050
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
5151
zlog.Error("Recovered in API", "recover", r)
5252

53-
_, _ = os.Stderr.WriteString(fmt.Sprintf("panic: %v\n\n", r))
53+
_, _ = fmt.Fprintf(os.Stderr, "panic: %v\n\n", r)
5454
debug.PrintStack()
5555
}
5656
}()

api/tree.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -323,28 +323,28 @@ func (node *treeNode) addChild(child *treeNode) {
323323
case node.startIndex == 0:
324324
node.startIndex = firstChar
325325
node.indices = []uint8{0}
326-
node.endIndex = node.startIndex + uint8(len(node.indices))
326+
node.endIndex = node.startIndex + uint8(len(node.indices)) //nolint:gosec // G115 - slice length is controlled
327327

328328
case firstChar < node.startIndex:
329329
diff := node.startIndex - firstChar
330-
newIndices := make([]uint8, diff+uint8(len(node.indices)))
330+
newIndices := make([]uint8, diff+uint8(len(node.indices))) //nolint:gosec // G115 - slice length is controlled
331331
copy(newIndices[diff:], node.indices)
332332
node.startIndex = firstChar
333333
node.indices = newIndices
334-
node.endIndex = node.startIndex + uint8(len(node.indices))
334+
node.endIndex = node.startIndex + uint8(len(node.indices)) //nolint:gosec // G115 - slice length is controlled
335335

336336
case firstChar >= node.endIndex:
337337
diff := firstChar - node.endIndex + 1
338-
newIndices := make([]uint8, diff+uint8(len(node.indices)))
338+
newIndices := make([]uint8, diff+uint8(len(node.indices))) //nolint:gosec // G115 - slice length is controlled
339339
copy(newIndices, node.indices)
340340
node.indices = newIndices
341-
node.endIndex = node.startIndex + uint8(len(node.indices))
341+
node.endIndex = node.startIndex + uint8(len(node.indices)) //nolint:gosec // G115 - slice length is controlled
342342
}
343343

344344
index := node.indices[firstChar-node.startIndex]
345345

346346
if index == 0 {
347-
node.indices[firstChar-node.startIndex] = uint8(len(node.children))
347+
node.indices[firstChar-node.startIndex] = uint8(len(node.children)) //nolint:gosec // G115 - children length is controlled
348348
node.children = append(node.children, child)
349349
return
350350
}

authcache/authserver_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ func Test_TrySort(t *testing.T) {
1919
s.List = append(s.List, NewAuthServer(fmt.Sprintf("[::%d]:53", i), IPv6))
2020
}
2121

22-
r := rand.New(rand.NewSource(time.Now().UnixNano()))
22+
r := rand.New(rand.NewSource(time.Now().UnixNano())) //nolint:gosec // G404 - test file, not used for crypto
2323
for i := 0; i < 2000; i++ {
2424
for j := range s.List {
2525
s.List[j].Count++

cache/cache.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -136,11 +136,11 @@ func (c *Cache) clearSegments(percent int) {
136136
}
137137

138138
// Rotate which segments we clear to be fair
139-
startSegment := c.evictionCount.Add(1) % uint64(segmentCount)
139+
startSegment := c.evictionCount.Add(1) % uint64(segmentCount) //nolint:gosec // G115 - segmentCount is a constant
140140

141141
for i := 0; i < segmentsToClear; i++ {
142-
segmentIndex := (startSegment + uint64(i)) % uint64(segmentCount)
143-
c.data.ClearSegment(int(segmentIndex))
142+
segmentIndex := (startSegment + uint64(i)) % uint64(segmentCount) //nolint:gosec // G115 - i is small and bounded
143+
c.data.ClearSegment(int(segmentIndex)) //nolint:gosec // G115 - segmentIndex is always < segmentCount
144144
}
145145
}
146146

cache/cache_bound_test.go

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ func TestCacheBoundStrict(t *testing.T) {
2424

2525
// Add items continuously and check size
2626
for i := 0; i < maxSize*10; i++ {
27-
c.Add(uint64(i), i)
27+
c.Add(uint64(i), i) //nolint:gosec // G115 - i is bounded by loop
2828

2929
currentSize := int64(c.Len())
3030
for {
@@ -93,7 +93,7 @@ func TestCacheBoundUnderHeavyConcurrency(t *testing.T) {
9393
defer wg.Done()
9494

9595
for i := 0; i < itemsPerGoroutine; i++ {
96-
key := uint64(goroutineID*itemsPerGoroutine + i)
96+
key := uint64(goroutineID*itemsPerGoroutine + i) //nolint:gosec // G115 - bounded by test parameters
9797
c.Add(key, fmt.Sprintf("value-%d-%d", goroutineID, i))
9898

9999
// Occasionally check size
@@ -132,7 +132,7 @@ func TestCacheEvictionEffectiveness(t *testing.T) {
132132

133133
// Fill cache completely
134134
for i := 0; i < maxSize; i++ {
135-
c.Add(uint64(i), i)
135+
c.Add(uint64(i), i) //nolint:gosec // G115 - i is bounded by maxSize
136136
}
137137

138138
initialSize := c.Len()
@@ -142,21 +142,21 @@ func TestCacheEvictionEffectiveness(t *testing.T) {
142142

143143
// Add more items to trigger eviction
144144
for i := maxSize; i < maxSize*2; i++ {
145-
c.Add(uint64(i), i)
145+
c.Add(uint64(i), i) //nolint:gosec // G115 - i is bounded by maxSize*2
146146
}
147147

148148
// Count how many of the original items remain
149149
originalRemaining := 0
150150
for i := 0; i < maxSize; i++ {
151-
if _, found := c.Get(uint64(i)); found {
151+
if _, found := c.Get(uint64(i)); found { //nolint:gosec // G115 - i is bounded by maxSize
152152
originalRemaining++
153153
}
154154
}
155155

156156
// Count how many new items were added
157157
newItems := 0
158158
for i := maxSize; i < maxSize*2; i++ {
159-
if _, found := c.Get(uint64(i)); found {
159+
if _, found := c.Get(uint64(i)); found { //nolint:gosec // G115 - i is bounded by maxSize*2
160160
newItems++
161161
}
162162
}
@@ -211,12 +211,12 @@ func TestCacheSizeMonitoring(t *testing.T) {
211211
start := time.Now()
212212
i := 0
213213
for time.Since(start) < 500*time.Millisecond {
214-
c.Add(uint64(i), i)
214+
c.Add(uint64(i), i) //nolint:gosec // G115 - i is loop counter
215215
i++
216216

217217
// Add some reads to simulate real usage
218218
if i%10 == 0 {
219-
c.Get(uint64(i / 2))
219+
c.Get(uint64(i / 2)) //nolint:gosec // G115 - i is loop counter
220220
}
221221
}
222222

@@ -268,7 +268,7 @@ func TestCacheEvictionDeadlock(t *testing.T) {
268268
go func() {
269269
// Continuously add items
270270
for i := 0; i < 10000; i++ {
271-
c.Add(uint64(i), i)
271+
c.Add(uint64(i), i) //nolint:gosec // G115 - test loop
272272
}
273273
done <- true
274274
}()
@@ -291,7 +291,7 @@ func TestCacheEvictionWithLargeItems(t *testing.T) {
291291
// Create values of different sizes
292292
size := (i % 100) + 1
293293
value := make([]byte, size*100) // Simulate different memory usage
294-
c.Add(uint64(i), value)
294+
c.Add(uint64(i), value) //nolint:gosec // G115 - test loop
295295

296296
if i%100 == 0 {
297297
currentSize := c.Len()
@@ -316,15 +316,15 @@ func BenchmarkCacheBoundChecking(b *testing.B) {
316316

317317
// Pre-fill to 80% capacity
318318
for i := 0; i < size*8/10; i++ {
319-
c.Add(uint64(i), i)
319+
c.Add(uint64(i), i) //nolint:gosec // G115 - benchmark loop
320320
}
321321

322322
b.ResetTimer()
323323
b.RunParallel(func(pb *testing.PB) {
324324
i := 0
325325
for pb.Next() {
326326
// This will trigger eviction checking
327-
c.Add(uint64(i), i)
327+
c.Add(uint64(i), i) //nolint:gosec // G115 - benchmark loop
328328
i++
329329
}
330330
})

cache/cache_comparison_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@ func benchmarkCachePerf(b *testing.B, size int, readRatio float64) {
3636

3737
b.ResetTimer()
3838
b.RunParallel(func(pb *testing.PB) {
39-
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
39+
rng := rand.New(rand.NewSource(time.Now().UnixNano())) //nolint:gosec // G404 - benchmark test
4040
for pb.Next() {
41-
key := keys[rng.Intn(size)]
41+
key := keys[rng.Intn(size)] //nolint:gosec // G115 - bounded by size
4242
r := rng.Float64()
4343

4444
switch {
@@ -57,7 +57,7 @@ func generateTestKeys(n int) []uint64 {
5757
keys := make([]uint64, n)
5858
for i := 0; i < n; i++ {
5959
// Simulate DNS cache keys (spread out)
60-
keys[i] = uint64(i) * 0x9E3779B9
60+
keys[i] = uint64(i) * 0x9E3779B9 //nolint:gosec // G115 - test key generation
6161
}
6262
return keys
6363
}
@@ -103,7 +103,7 @@ func benchmarkConcurrent(b *testing.B, c cacheOps, numGoroutines int) {
103103
for i := 0; i < numGoroutines; i++ {
104104
go func(id int) {
105105
defer wg.Done()
106-
rng := rand.New(rand.NewSource(time.Now().UnixNano() + int64(id)))
106+
rng := rand.New(rand.NewSource(time.Now().UnixNano() + int64(id))) //nolint:gosec // G404 - test random
107107

108108
for j := 0; j < opsPerGoroutine; j++ {
109109
key := keys[rng.Intn(len(keys))]

cache/cache_eviction_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ func TestCacheEviction(t *testing.T) {
1111

1212
// Fill the cache to capacity
1313
for i := 0; i < cacheSize; i++ {
14-
c.Add(uint64(i), i)
14+
c.Add(uint64(i), i) //nolint:gosec // G115 - test loop
1515
}
1616

1717
if c.Len() != cacheSize {
@@ -20,7 +20,7 @@ func TestCacheEviction(t *testing.T) {
2020

2121
// Add more items to trigger eviction
2222
for i := cacheSize; i < cacheSize+50; i++ {
23-
c.Add(uint64(i), i)
23+
c.Add(uint64(i), i) //nolint:gosec // G115 - test loop
2424

2525
// Check size after each batch of additions
2626
if i%10 == 0 {
@@ -42,7 +42,7 @@ func TestCacheEviction(t *testing.T) {
4242
// Just verify that some items are still retrievable
4343
found := 0
4444
for i := 0; i < cacheSize+50; i++ {
45-
if _, ok := c.Get(uint64(i)); ok {
45+
if _, ok := c.Get(uint64(i)); ok { //nolint:gosec // G115 - test loop
4646
found++
4747
}
4848
}
@@ -82,7 +82,7 @@ func TestCacheEvictionConcurrent(t *testing.T) {
8282
for i := 0; i < 10; i++ {
8383
go func(start int) {
8484
for j := 0; j < 200; j++ {
85-
c.Add(uint64(start*1000+j), j)
85+
c.Add(uint64(start*1000+j), j) //nolint:gosec // G115 - test loop
8686
}
8787
done <- true
8888
}(i)
@@ -107,7 +107,7 @@ func BenchmarkCacheWithEviction(b *testing.B) {
107107
i := 0
108108
for pb.Next() {
109109
// This will trigger eviction periodically
110-
c.Add(uint64(i), i)
110+
c.Add(uint64(i), i) //nolint:gosec // G115 - benchmark loop
111111
i++
112112
}
113113
})

cache/cache_test.go

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -96,12 +96,12 @@ func TestCacheConcurrency(t *testing.T) {
9696
go func(id int) {
9797
defer wg.Done()
9898
for j := 0; j < opsPerGoroutine; j++ {
99-
key := uint64(id*opsPerGoroutine + j)
99+
key := uint64(id*opsPerGoroutine + j) //nolint:gosec // G115 - test loop
100100
c.Add(key, fmt.Sprintf("value-%d-%d", id, j))
101101

102102
// Randomly access some values
103103
if j%10 == 0 {
104-
randomKey := uint64((id + j) % (numGoroutines * opsPerGoroutine))
104+
randomKey := uint64((id + j) % (numGoroutines * opsPerGoroutine)) //nolint:gosec // G115 - test calculation
105105
c.Get(randomKey)
106106
}
107107
}
@@ -122,7 +122,7 @@ func TestCacheRemoveConcurrency(t *testing.T) {
122122

123123
// Pre-populate cache
124124
for i := 0; i < numGoroutines*keysPerGoroutine; i++ {
125-
c.Add(uint64(i), i)
125+
c.Add(uint64(i), i) //nolint:gosec // G115 - test loop //nolint:gosec // G115 - test loop
126126
}
127127

128128
var wg sync.WaitGroup
@@ -133,7 +133,7 @@ func TestCacheRemoveConcurrency(t *testing.T) {
133133
go func(id int) {
134134
defer wg.Done()
135135
for j := 0; j < keysPerGoroutine; j++ {
136-
key := uint64(id*keysPerGoroutine + j)
136+
key := uint64(id*keysPerGoroutine + j) //nolint:gosec // G115 - test loop
137137
c.Remove(key)
138138
}
139139
}(i)
@@ -178,22 +178,22 @@ func TestCacheCapacity(t *testing.T) {
178178

179179
// Add some items
180180
for i := 0; i < 50; i++ {
181-
c.Add(uint64(i), i)
181+
c.Add(uint64(i), i) //nolint:gosec // G115 - test loop
182182
}
183183

184184
// Verify size
185185
assert.Equal(t, 50, c.Len())
186186

187187
// Add more items up to capacity
188188
for i := 50; i < 100; i++ {
189-
c.Add(uint64(i), i)
189+
c.Add(uint64(i), i) //nolint:gosec // G115 - test loop
190190
}
191191

192192
assert.Equal(t, 100, c.Len())
193193

194194
// Adding more should maintain capacity
195195
for i := 100; i < 150; i++ {
196-
c.Add(uint64(i), i)
196+
c.Add(uint64(i), i) //nolint:gosec // G115 - test loop
197197
}
198198

199199
assert.LessOrEqual(t, c.Len(), 100, "Cache should not exceed capacity")
@@ -205,14 +205,14 @@ func BenchmarkCacheGet(b *testing.B) {
205205

206206
// Pre-populate
207207
for i := 0; i < 10000; i++ {
208-
c.Add(uint64(i), i)
208+
c.Add(uint64(i), i) //nolint:gosec // G115 - test loop
209209
}
210210

211211
b.ResetTimer()
212212
b.RunParallel(func(pb *testing.PB) {
213213
i := 0
214214
for pb.Next() {
215-
c.Get(uint64(i % 10000))
215+
c.Get(uint64(i % 10000)) //nolint:gosec // G115 - test loop
216216
i++
217217
}
218218
})
@@ -225,7 +225,7 @@ func BenchmarkCacheAdd(b *testing.B) {
225225
b.RunParallel(func(pb *testing.PB) {
226226
i := 0
227227
for pb.Next() {
228-
c.Add(uint64(i), i)
228+
c.Add(uint64(i), i) //nolint:gosec // G115 - test loop
229229
i++
230230
}
231231
})
@@ -236,17 +236,17 @@ func BenchmarkCacheMixed(b *testing.B) {
236236

237237
// Pre-populate
238238
for i := 0; i < 5000; i++ {
239-
c.Add(uint64(i), i)
239+
c.Add(uint64(i), i) //nolint:gosec // G115 - test loop
240240
}
241241

242242
b.ResetTimer()
243243
b.RunParallel(func(pb *testing.PB) {
244244
i := 0
245245
for pb.Next() {
246246
if i%2 == 0 {
247-
c.Get(uint64(i % 10000))
247+
c.Get(uint64(i % 10000)) //nolint:gosec // G115 - test loop //nolint:gosec // G115 - test loop
248248
} else {
249-
c.Add(uint64(i), i)
249+
c.Add(uint64(i), i) //nolint:gosec // G115 - test loop
250250
}
251251
i++
252252
}
@@ -262,13 +262,13 @@ func TestCacheMemoryUsage(t *testing.T) {
262262

263263
// Add items
264264
for i := 0; i < 10000; i++ {
265-
c.Add(uint64(i), fmt.Sprintf("value-%d", i))
265+
c.Add(uint64(i), fmt.Sprintf("value-%d", i)) //nolint:gosec // G115 - test loop
266266
}
267267

268268
// Just verify the cache is working
269269
found := 0
270270
for i := 0; i < 100; i++ {
271-
if _, ok := c.Get(uint64(i)); ok {
271+
if _, ok := c.Get(uint64(i)); ok { //nolint:gosec // G115 - test loop
272272
found++
273273
}
274274
}

0 commit comments

Comments
 (0)