1 2 Previous Next 23 Replies Latest reply on Feb 8, 2010 2:40 PM by kabirkhan Go to original post
      • 15. Re: Pluggable dependency resolver
        kabirkhan

        I've done some work on scoped aliases, and made the dependency resolver matchers not throw errors if things cannot be found when cleaning up the registered dependencies on uninstall (since multiple controllers may be involved). In dependency I still see some failures which I need to dig into, but all the tests in kernel pass with the new resolver. I need to add some tests making sure that everything is cleaned up in the new dependency resolver to make sure we don't leak memory.

         

        As mentioned before I will look into this

        "One thing I think we're lacking is "wrong order" tests for contextual injection, at least with qualifiers, so I need to add some. The same might be the case for supply/demand, which I need to check."

         

        I need to revisit how I am handling concurrency when accessing the indexed dependencies in the matchers.

         

        Next I'll look at how to get all the the tests run in all controller modes, I'll probably do this by adding some maven profiles as suggested by Paul

        I'm assuming you are using the surefire plugin, right?  You can set up multiple executions of any plugin, including surefire, and these executions can be in the same profile or separate profiles.  Just make sure you have a different ID for the two profiles.

        It looks something like this:

        <plugin>
        <groupId>
        <artifctId>
        <executions>
        <execution>
          <id>test-config-1</id>
          <configuration>
           ...
          </configuration>
        </execution>
        <execution>
           <id>test-setup-2</id>
           <configuration>
             ...
           </configuration>
        </execution>
        </executions>
        </plugin>

        • 16. Re: Pluggable dependency resolver
          kabirkhan

          Apart from aop-mc-int since its tests are more complex than the others, I have got the tests running for all other modules in both modes in the branch. I had to adjust how dependency/OnDemandUnitTestCase and jmx-mc-int/NewOnDemandDependencyTest compare the expected install orders since the ordering with the new resolver is slightly different.

           

          Also, I excluded the BadDependencyInfoTestCase from the indexing dependency resolver run since no matter how hard I try I just can't figure out what this test does :-) anyway it seems to depend on implementation details for the standard dependency resolver, so we will probably need a similar but different test for the indexing dependency resolver.

           

          I tried booting up AS with the indexing dependency resolver enabled yesterday and it fell over, so I need to figure out why and try to test that as well as address the points in my previous post.

          • 17. Re: Pluggable dependency resolver
            kabirkhan

            I got AS trunk started with the indexing dependency resolver, unfortunately I did not see any difference in startup speed worth mentioning :-(

             

            So, although we only try to increment the contexts that have actually had a dependency resolved, at the moment there is a lot of work going into determining what those contexts are. Maybe too much?

             

            An overview of what I have so far is probably best described with an example. I deploy

             

            <bean name="Bean" class="...">
               <property name="prop"><inject bean="Other"/></property>
            </bean>
            
            

            Since this is an explicit dependency on a name which is required in the CONFIGURED state and Other reaches the INSTALLED state, this is recorded in the NameDependencyResolverMatcher under

             

            [INSTALLED->["Other"->{Context[name="Bean"]}]

             

            (Read as when a bean called "Other" reaches the Installed state, the Context named Bean is a candidate for injection having its state incremented)

             

            We then try to push Bean as far through the states as we can, in this case it will stop in the INSTANTIATED state since Other is not available yet when we try to resolve the dependencies to enter Configured.

             

            Now if we deploy

             

            <bean name="Other" class="…"/>

             

            We push this through the states. There is a stateIncremented(ControllerContext) method in the resolver called by the controller whenever a context has its state incremented which calls through to all the dependency resolver matchers. For Other in PRE_INSTALL, …, START nothing is found in the matchers so we just keep on moving Other through the lifecycle. Once stateIncremented(ControllerContext) is called for Other when it hits the INSTALLED state we then check all the dependency resolver matchers. We hit the NameDepedencyResolverMatcher and for INSTALLED we find

             

            ["Other"->{Context[name="Bean"]}]

             

            We then look up "Other" in this map and find

             

            {Context[name="Bean"]}

             

            in other words the context called Bean has been waiting for Other to reach the INSTALLED phase, we then check that Bean has its other dependencies resolved and then attempt to push it through the states as before.

             

            I have not tried profiling anything yet, but for simple dependencies like the one above I think the above mechanism should be fast. However, there are a few other factors that could well slow it all down.

             

            ====== Contextual Injection (and Callbacks) =========

             

            If we first deploy

             

            <bean name="Bean" class="...">
               <property name="prop"><inject/></property>
            </bean>
            

             

            where the type of the prop property is 'interface SomeInterface'. This becomes a ContextualInjectionDependencyItem, and is recorded in ContextualInjectionDependencyResolverMatcher as

             

            [INSTALLED->[[interface SomeInterface]->{Context[name="Bean"]}]

             

            As before Bean is moved to the INSTANTIATED phase.

             

            Now we deploy

             

            <bean name="Bean" class="SomeClass"/>
            
            

            where SomeClass somewhere in its inheritance hierarchy has SomeInterface as a parent. When installing Bean, each time its state is incremented it checks the matchers for matching contexts. It will find nothing in NameDependencyResolverMatcher nor in ContextualInjectionDependencyMatcher for PRE_INSTALL, …,START. When it gets to INSTALLED, again it finds nothing in  NameDependencyResolverMatcher, but when it hits ContextualInjectionDependencyMatcher it finds this for INSTALLED

             

            [interface SomeInterface]->{Context[name="Bean"]}

             

            Now it checks if SomeClass is there, nothing. Then do the same for all the superclasses and interfaces until it finally finds the Bean Context under SomeInterface. We then check Bean's other dependencies and push it through the remaining states.

             

            I am not sure how extensively contextual injection is used in the AS, but the point here is that if contextual injection is used *at all* and we have contexts registered as waiting for a few other states, whenever other contexts reach one of those states we have to do all the work of looking up all the superclasses/interfaces to see if any of the waiting contexts can be removed resolved.

             

            Also, this work is duplicated since we also have a CallbackDependencyResolverMatcher which effectively does exactly the same but for CallbackDependencyItems.

             

            ====== Scoping =======

             

            We only record the dependencies in the matchers for the controller into which the context owning the dependency was installed. If a context has its state incremented, there might be contexts with dependencies on it waiting in one of the child controllers. So the stateIncremented(ControllerContext) method ends up calling stateIncremented(ControllerContext) on all the child controller resolvers as well, which in turn will invoke all the matchers for each child controller.

            • 18. Re: Pluggable dependency resolver
              ropalka

              I'm open to help in this area. So if you want suggestions from others (not just MC folks)

              please formulate/describe the problem in a way we could join the brainstorming process too

               

              "A problem well stated is a problem half solved."
                   --Charles F. Kettering

               


              • 19. Re: Pluggable dependency resolver
                kabirkhan

                Right,

                 

                So the basic outline is that AbstractController.install(ControllerContext) installs the contexts into the controller. In doing so the context goes through several states. Upon entry to each state, the controller checks if the context has all its dependencies resolved, and if so it is moved to the next state. If not, it is left in that state, and we try to resolve the context later once another context is added to the controller. Once the context reaches INSTALLED state it is considered to be installed.

                 

                The mechanism that is currently in trunk does this by calling a method internally in resolveContexts(). What this essentially does is to iterate over each controller state. For each controller state it gets a list of the contexts in that state and then for each context see if the dependencies are resolved. The contexts with all dependencies resolved are pushed to the next state, and the contexts that don't are left in the current state. If at least one context could be moved to the next state, we iterate over the states again.

                 

                Pseudocode:

                 

                resolveContexts()

                {

                   while (resolved)

                   {

                       resolved = false;

                       for (ControllerState state : statesInController)

                       {

                            Set<ControllerContext> contexts = getContextsForState(state);

                            Set<ControllerContext) resolvedContexts = new HashSet<ControllerContext>();

                            for (ControllerContext context : contexts)

                            {

                                //Req. state will normaly be INSTALLED so we don't look at the contexts that have reached that state

                                if (state == context.getRequiredState())

                                    continue;

                                //Iterate over context's DependencyItems and try to resolve them

                                //(they are resolved by looking up the information from the DependencyItem in the controller)

                                if (isResolved(context), state)

                                {

                                    resolvedContexts.add(context);

                                }

                            }

                 

                            if (resolvedContexts.size() > 0)

                            {

                                 for (ControllerContext context : contexts)

                                    incrementState(context); // Puts the context's state to state, and invokes any associated ControllerContextAction

                 

                                 resolved = true;

                                 break;

                            }

                       }

                    }

                }

                 

                So basically every time a context has its state incremented there could potentially be other contexts waiting for it to enter that state via a dependency which is why we go over and check all the contexts in each state.

                 

                With my indexing resolver I take a different approach. When adding a context to the controller I index it by its dependencies. Then later when adding another context, I look in the index if something has an indexed dependency on the new context. If that is the case, I only try to increment the state of the contexts registered as having a dependency.

                 

                Some basic profiling (take with a pinch of salt) shows that not that much time is spent actually trying to resolve dependencies with either way of doing it. Having spoken about this with Ales, the reason for this is that most stuff deployed in AS is deployed in the right order or more or less at the same time as their dependencies, so we don't have HUGE numbers of contexts waiting in a state while hoping to reach INSTALLED. If that were the case I am pretty confident that the indexing resolver would win easily.

                • 20. Re: Pluggable dependency resolver
                  kabirkhan

                  I did a little benchmark to test out my assumption that the indexing resolver performs better for large amounts of contexts waiting for dependencies. In each case I deploy 1000 beans.

                   

                  * Scenario 1 - all contexts in the right order

                  Bean 1 has no depdencies

                  Bean 2 depends on Bean 1

                  Bean 3 depends on Bean 2

                  etc.

                   

                  Standard resolver: 702 ms

                  Indexing resolver: 554 ms

                   

                  * Scenario 2 - all contexts in wrong order

                  Bean 1 depends on Bean 2

                  Bean 2 depdends on Bean 1

                  etc.

                   

                  Standard resolver: 3476

                  Indexing resolver: 601

                   

                  I tried this with higher numbers, but this causes a StackOverflowError with the indexing resolver, the reason being that it works recursively when a waiting context gets its dependencies resolved. AbstractController.install()->IndexingDependencyResolver.resolveContext(ControllerContext) (takes the installed context as param) ->AbstractContoller.incrementState()->IndexingDependencyResolver.stateIncremented()->IndexingDependencyResolver.resolveContext(ControllerContext) (takes any waiting context as param). I'll try to untangle this recursion.

                  • 21. Re: Pluggable dependency resolver
                    kabirkhan
                    I have got rid of the recursion, so the benchmark now passes with larger numbers. These are the figures:
                    **10,000 beans
                    *wrong order
                    -Legacy resolver: I gave up and stopped the test after 4 minutes
                    -Indexing resolver: 4655ms
                    *correct order
                    -Legacy resolver: 12937ms
                    -Indexing resolver: 2553ms
                    **3,000 beans
                    *wrong order
                    -Legacy resolver: 29201ms
                    -Indexing resolver: 1447ms
                    *correct order
                    -Legacy resolver: 2264ms
                    -Indexing resolver: 1173ms
                    Note that this is done on plain test controller contexts in the dependency project, so there should be no overhead associated with stuff happening in the ControllerContext actions such as accessing the MDR, instantiating the context etc.
                    Next, I'll try this revised version in AS to see if it has any influence on boot times.
                    What I have done breaks a few of the tests in the branch. I think they can be fixed easily, since they take into account the order that things reach the various states. For the record these tests are:
                    KernelAllTestSuite
                    All Tests
                    Kernel Tests
                    Dependency Tests
                    org.jboss.test.kernel.dependency.test.PlainLifecycleDependencyTestCase
                    testPlainLifecycleDependencyWrongOrder(org.jboss.test.kernel.dependency.test.PlainLifecycleDependencyTestCase)
                    junit.framework.AssertionFailedError: expected:<2> but was:<3>
                    at junit.framework.Assert.fail(Assert.java:47)
                    at junit.framework.Assert.failNotEquals(Assert.java:277)
                    at junit.framework.Assert.assertEquals(Assert.java:64)
                    at junit.framework.Assert.assertEquals(Assert.java:195)
                    at junit.framework.Assert.assertEquals(Assert.java:201)
                    at org.jboss.test.kernel.dependency.test.PlainLifecycleDependencyTestCase.testPlainLifecycleDependencyWrongOrder(PlainLifecycleDependencyTestCase.java:98)
                    testPlainLifecycleDependencyReinstall(org.jboss.test.kernel.dependency.test.PlainLifecycleDependencyTestCase)
                    junit.framework.AssertionFailedError: expected:<10> but was:<11>
                    at junit.framework.Assert.fail(Assert.java:47)
                    at junit.framework.Assert.failNotEquals(Assert.java:277)
                    at junit.framework.Assert.assertEquals(Assert.java:64)
                    at junit.framework.Assert.assertEquals(Assert.java:195)
                    at junit.framework.Assert.assertEquals(Assert.java:201)
                    at org.jboss.test.kernel.dependency.test.PlainLifecycleDependencyTestCase.testPlainLifecycleDependencyReinstall(PlainLifecycleDependencyTestCase.java:141)
                    org.jboss.test.kernel.dependency.test.PlainLifecycleDependencyXMLTestCase
                    testPlainLifecycleDependencyWrongOrder(org.jboss.test.kernel.dependency.test.PlainLifecycleDependencyXMLTestCase)
                    junit.framework.AssertionFailedError: expected:<2> but was:<3>
                    at junit.framework.Assert.fail(Assert.java:47)
                    at junit.framework.Assert.failNotEquals(Assert.java:277)
                    at junit.framework.Assert.assertEquals(Assert.java:64)
                    at junit.framework.Assert.assertEquals(Assert.java:195)
                    at junit.framework.Assert.assertEquals(Assert.java:201)
                    at org.jboss.test.kernel.dependency.test.PlainLifecycleDependencyTestCase.testPlainLifecycleDependencyWrongOrder(PlainLifecycleDependencyTestCase.java:98)
                    testPlainLifecycleDependencyReinstall(org.jboss.test.kernel.dependency.test.PlainLifecycleDependencyXMLTestCase)
                    junit.framework.AssertionFailedError: expected:<10> but was:<11>
                    at junit.framework.Assert.fail(Assert.java:47)
                    at junit.framework.Assert.failNotEquals(Assert.java:277)
                    at junit.framework.Assert.assertEquals(Assert.java:64)
                    at junit.framework.Assert.assertEquals(Assert.java:195)
                    at junit.framework.Assert.assertEquals(Assert.java:201)
                    at org.jboss.test.kernel.dependency.test.PlainLifecycleDependencyTestCase.testPlainLifecycleDependencyReinstall(PlainLifecycleDependencyTestCase.java:141)
                    org.jboss.test.kernel.dependency.test.PlainLifecycleDependencyAnnotationTestCase
                    testPlainLifecycleDependencyWrongOrder(org.jboss.test.kernel.dependency.test.PlainLifecycleDependencyAnnotationTestCase)
                    junit.framework.AssertionFailedError: expected:<2> but was:<3>
                    at junit.framework.Assert.fail(Assert.java:47)
                    at junit.framework.Assert.failNotEquals(Assert.java:277)
                    at junit.framework.Assert.assertEquals(Assert.java:64)
                    at junit.framework.Assert.assertEquals(Assert.java:195)
                    at junit.framework.Assert.assertEquals(Assert.java:201)
                    at org.jboss.test.kernel.dependency.test.PlainLifecycleDependencyTestCase.testPlainLifecycleDependencyWrongOrder(PlainLifecycleDependencyTestCase.java:98)
                    testPlainLifecycleDependencyReinstall(org.jboss.test.kernel.dependency.test.PlainLifecycleDependencyAnnotationTestCase)
                    junit.framework.AssertionFailedError: expected:<10> but was:<11>
                    at junit.framework.Assert.fail(Assert.java:47)
                    at junit.framework.Assert.failNotEquals(Assert.java:277)
                    at junit.framework.Assert.assertEquals(Assert.java:64)
                    at junit.framework.Assert.assertEquals(Assert.java:195)
                    at junit.framework.Assert.assertEquals(Assert.java:201)
                    at org.jboss.test.kernel.dependency.test.PlainLifecycleDependencyTestCase.testPlainLifecycleDependencyReinstall(PlainLifecycleDependencyTestCase.java:141)
                    • 22. Re: Pluggable dependency resolver
                      alesj

                      Afaik, the tests are valid - contexts are pushed as far as they go.

                      So, are you saying you'll "fix" the tests or fix your impl? :-)

                       

                      Otoh the numbers look good. :-)

                      Although the AS itself is around 1500 contexts (last time I ran my Grapher tool),

                      where they have mostly "correct" order, so judging from 3k tests, this won't bring us much. :-(

                      But definitely a worthy prototype!

                      • 23. Re: Pluggable dependency resolver
                        kabirkhan

                        I mean this:

                         

                           public void testPlainLifecycleDependencyWrongOrder() throws Throwable

                           {

                              plainLifecycleDependencyWrongOrder();

                         

                              ControllerContext context2 = assertInstall(1, "Name2", ControllerState.CONFIGURED);

                              ControllerContext context1 = assertInstall(0, "Name1");

                              assertEquals(ControllerState.INSTALLED, context2.getState());

                         

                              SimpleBeanWithLifecycle bean1 = (SimpleBeanWithLifecycle) context1.getTarget();

                              assertNotNull(bean1);

                         

                              SimpleBeanWithLifecycle bean2 = (SimpleBeanWithLifecycle) context2.getTarget();

                              assertNotNull(bean2);

                         

                              assertEquals(1, bean1.createOrder);

                              assertEquals(2, bean2.createOrder);

                              assertEquals(3, bean1.startOrder);

                              assertEquals(4, bean2.startOrder);

                           }

                        The new resolver works with

                              assertEquals(1, bean1.createOrder);

                              assertEquals(2, bean1.startOrder);

                              assertEquals(3, bean2.createOrder);

                              assertEquals(4, bean2.startOrder);

                        The actual hardcoded orders of beans 1 and 2 is an implemetation detail as I see it. The real check is making sure that the initial install of context 2 does not go beyond CONFIGURED and:
                        bean1.startOrder > bean1.createOrder
                        bean2.startOrder > bean2.createOrder
                        bean2.createOrder > bean1.createOrder
                        bean2.startOrder > bean1.startOrder
                        I've got an infinite loop in the indexing resolver when I start up AS which I need to fix before I can get any measurements of boot time, although it sounds like we won't gain much from this.
                        1 2 Previous Next