diff --git a/izidic.go b/izidic.go index 18a79ed..9121ef4 100644 --- a/izidic.go +++ b/izidic.go @@ -128,10 +128,15 @@ func (dic *container) Service(name string) (any, error) { // for at least one service was attempted more than once, which implies a // dependency cycle. const funcName = "github.com/fgm/izidic.(*container).Service" - // We need a vastly oversized value to cover the case of deeply nested dic.Service() calls. - pcs := make([]uintptr, 1e6) - n := runtime.Callers(1, pcs) - pcs = pcs[:n] + pcs := make([]uintptr, 64) + for { + n := runtime.Callers(1, pcs) + if n < len(pcs) { + pcs = pcs[:n] + break + } + pcs = make([]uintptr, 2*len(pcs)) + } frames := runtime.CallersFrames(pcs) serviceCalls := 0 for { diff --git a/izidic_test.go b/izidic_test.go index e173a43..466d261 100644 --- a/izidic_test.go +++ b/izidic_test.go @@ -3,6 +3,7 @@ package izidic_test import ( "errors" "fmt" + "runtime" "strings" "testing" @@ -98,6 +99,28 @@ func TestContainer_Service(t *testing.T) { } } +func TestContainer_Service_AvoidsOversizedCycleDetectorAllocation(t *testing.T) { + const maxAllocatedBytes = 1 << 20 + + runtime.GC() + var before runtime.MemStats + runtime.ReadMemStats(&before) + + dic := izidic.New() + dic.Register("s", s1) + _, err := dic.Service("s") + if err != nil { + t.Fatal(err) + } + + var after runtime.MemStats + runtime.ReadMemStats(&after) + allocatedBytes := after.TotalAlloc - before.TotalAlloc + if allocatedBytes > maxAllocatedBytes { + t.Fatalf("Service allocated %d bytes, expected at most %d", allocatedBytes, maxAllocatedBytes) + } +} + func TestContainer_MustService_Missing(t *testing.T) { const expectedFormat = "service not found: %q" defer func() {