fix: prevent OOM on rapid schema publishing
schemas / vulnerabilities (pull_request) Successful in 6m43s
schemas / check-release (pull_request) Successful in 11m27s
schemas / check (pull_request) Successful in 14m51s
pre-commit / pre-commit (pull_request) Successful in 19m39s
schemas / build (pull_request) Successful in 8m26s
schemas / deploy-prod (pull_request) Has been skipped
schemas / vulnerabilities (pull_request) Successful in 6m43s
schemas / check-release (pull_request) Successful in 11m27s
schemas / check (pull_request) Successful in 14m51s
pre-commit / pre-commit (pull_request) Successful in 19m39s
schemas / build (pull_request) Successful in 8m26s
schemas / deploy-prod (pull_request) Has been skipped
Add concurrency-limited CosmoGenerator (semaphore limit=1, 60s timeout) to prevent unbounded concurrent wgc process spawning. Add debouncer (500ms) to coalesce rapid schema updates per org+ref. Fix double subgraph fetch in Supergraph resolver and goroutine leak in SchemaUpdates subscription. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
package graph
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDebouncer_Coalesces(t *testing.T) {
|
||||
d := NewDebouncer(50 * time.Millisecond)
|
||||
var calls atomic.Int32
|
||||
|
||||
// Fire 10 rapid calls for the same key — only the last should execute.
|
||||
for range 10 {
|
||||
d.Debounce("key1", func() {
|
||||
calls.Add(1)
|
||||
})
|
||||
}
|
||||
|
||||
// Wait for the debounce delay plus some margin.
|
||||
time.Sleep(150 * time.Millisecond)
|
||||
|
||||
assert.Equal(t, int32(1), calls.Load(), "rapid calls should coalesce into a single execution")
|
||||
}
|
||||
|
||||
func TestDebouncer_DifferentKeys(t *testing.T) {
|
||||
d := NewDebouncer(50 * time.Millisecond)
|
||||
var calls atomic.Int32
|
||||
|
||||
d.Debounce("key-a", func() { calls.Add(1) })
|
||||
d.Debounce("key-b", func() { calls.Add(1) })
|
||||
d.Debounce("key-c", func() { calls.Add(1) })
|
||||
|
||||
time.Sleep(150 * time.Millisecond)
|
||||
|
||||
assert.Equal(t, int32(3), calls.Load(), "different keys should fire independently")
|
||||
}
|
||||
|
||||
func TestDebouncer_TimerReset(t *testing.T) {
|
||||
d := NewDebouncer(100 * time.Millisecond)
|
||||
var value atomic.Int32
|
||||
|
||||
// First call sets value to 1.
|
||||
d.Debounce("key", func() { value.Store(1) })
|
||||
|
||||
// Wait 60ms (less than the 100ms delay), then replace with value 2.
|
||||
time.Sleep(60 * time.Millisecond)
|
||||
d.Debounce("key", func() { value.Store(2) })
|
||||
|
||||
// At 60ms the first timer hasn't fired yet. Wait for the second timer.
|
||||
time.Sleep(150 * time.Millisecond)
|
||||
|
||||
require.Equal(t, int32(2), value.Load(), "later call should replace the earlier one")
|
||||
}
|
||||
Reference in New Issue
Block a user