Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support detection of circular dependencies in dynamic factory-based registrations #13

Open
craigfowler opened this issue Jan 4, 2018 · 1 comment

Comments

@craigfowler
Copy link

craigfowler commented Jan 4, 2018

As noted in an ignored integration test: BoDi.Tests.RegisterFactoryDelegateTests.ShouldThrowExceptionForDynamicCircuarDepenencies - if a factory delegate registers a component and makes use of a resolved IObjectContainer inside of the registration, circular dependencies are not detected if they exist in the resolved objects.

Example

Here's the example from that integration test:

container.RegisterFactoryAs<ClassWithCircularDependency1>(c => new ClassWithCircularDependency1(c.Resolve<ClassWithCircularDependency2>()));
container.Resolve<ClassWithCircularDependency1>();

Here, we have two 'resolutions' occurring. An outer resolution - in which the container resolves a factory registration for ClassWithCircularDependency1 and an inner resolution in which the container resolves an unregistered type for ClassWithCircularDependency2.

Root cause analysis

The root cause of this is that when resolving an IObjectContainer and using this to dynamically resolve dependencies, that inner resolve operation does not have access to the "resolution path" information of the outer resolution operation. This thwarts circular dependency detection because the resolution path chain is repeatedly broken and started again from fresh, and never comes full circle.

The resolution path looks like:

  • We begin with the outer resolve operation's resolution path:
    1. ClassWithCircularDependency1
    2. IObjectContainer
  • Then a new resolve operation - the inner one - begins a new resolution path
    1. ClassWithCircularDependency2
    2. ClassWithCircularDependency1
    3. IObjectContainer
  • Another new inner resolve operation begins, using a new path, and another, and another until the stack overflows

Proposed solution (overview)

I have a solution for this - which is that when resolving an IObjectContainer and the object container is not the very first thing in the resolution path, then instead of resolving and returning the real object container, instead return an instance of a proxy class which also implements the same interface (and contains the real container as a private field). That same proxy type would also receive the "resolution path" information from the parent/outer resolution operation.

Within the proxy type, most calls may just be proxied wholesale, but calls to resolve things would instead use a resolve method which passes on that resolution path information. Thus the continuity of the 'overall' resolve operation (the outer and the inner) is preserved. Thus there is only ever one resolution path in play and it looks like so:

  1. ClassWithCircularDependency1
  2. IObjectContainer
  3. ClassWithCircularDependency2
  4. ClassWithCircularDependency1

At this stage, circular dependency detection may trigger and raise an exception, because ClassWithCircularDependency1 appears twice in the path.

Where to find an implementation

As a project of my own, I have forked BoDi. I have fixed this very same problem in that project but it's diverged so much that it would be impossible to do a simple back-port of the fix into BoDI. Indeed, I've since archived that forked repo in order to create a new non-forked one.

However, you can find my fix in this this issue in that archived repo. The relevant commits should all be findable from the issue.

@gasparnagy
Copy link
Collaborator

This makes sense. Let's come back to it when we have moved away from the single-file approach.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants