diff --git a/scope.go b/scope.go index ab0262e..6af43b3 100644 --- a/scope.go +++ b/scope.go @@ -1,8 +1,10 @@ package dingo import ( + "fmt" "reflect" "sync" + "time" ) type ( @@ -56,7 +58,24 @@ func (s *SingletonScope) ResolveType(t reflect.Type, annotation string, unscoped if l, ok := s.instanceLock[ident]; ok { // we have the instance lock s.mu.Unlock() + + ticker := time.NewTimer(10 * time.Second) + go func() { + <-ticker.C + path := t.PkgPath() + if path == "" { + if t.Kind() == reflect.Ptr { + path = t.Elem().PkgPath() + } + } + if path != "" { + path += "." + } + path += t.Name() + panic(fmt.Sprintf("Singleton: timed out waiting for instance lock: type: %q, annotation: %q", path, annotation)) + }() l.RLock() + ticker.Stop() defer l.RUnlock() instance, _ := s.instances.Load(ident) diff --git a/scope_test.go b/scope_test.go index 31098fc..d00e781 100644 --- a/scope_test.go +++ b/scope_test.go @@ -148,3 +148,23 @@ func TestInheritedScope(t *testing.T) { secondI := i.(*inheritedScopeInjected) assert.Same(t, firstI.i, secondI.i) } + +type circSingletonA struct { + B *circSingletonB `inject:""` +} +type circSingletonB struct { + A *circSingletonA `inject:""` +} + +func TestCircularSingletonBinding(t *testing.T) { + EnableCircularTracing() + defer func() { + traceCircular = nil + }() + injector, err := NewInjector() + assert.NoError(t, err) + injector.Bind(new(circSingletonA)).In(Singleton) + assert.Panics(t, func() { + injector.GetInstance(new(circSingletonA)) + }) +}