[jboss-as7-dev] A strategy for mapping a directed dependency graph with transitives and cycles onto a DAG

David M. Lloyd david.lloyd at redhat.com
Tue Nov 27 11:22:18 EST 2012


The problem: MSC services must form a DAG.  However, in several cases, 
the dependency information we are given is potentially cyclic and may 
also include transitive dependency information.

Currently we deal with cyclic dependencies by using a two-phase 
resolution strategy, however this strategy can only ever encompass one 
level of dependencies and cannot accommodate transitive dependencies at all.

This problem has come up in the following contexts:

1) EJB JNDI resolution where EJB A injects EJB B, but B in turn depends 
on EJB C: A does not receive a dependency on C though it should have.

2) Module dependency resolution where a dependency has an export from 
another module.  In this case the transitive dependency is also lost.

The Two-Phase Solution
----------------------
The two-phase solution works like this:

   +---------+   +---------+
   | Item A  |   | Item B  |
   | (Create)|   | (Create)|
   |         |   |         |
   +---------+   +-+-------+
       ^           ^   ^
       |  +--------+   |
       |  |            |
   +---+--+--+   +-----+---+
   | Item A  |   | Item B  |
   | (Start) |   | (Start) |
   |         |   |         |
   +---------+   +---------+

In this example A has a dependency on B.  In the create phase, we 
ascertain what the dependencies are for a given item.  In the start 
phase we establish dependencies on all of the create-phase services of 
our dependencies.

The problem with this solution has been outlined above.

The Three-Phase Solution
------------------------
Superficially, it would seem that the problems associated with the 
two-phase solution can be resolved by the use of three phases.  The 
initial phase would encapsulate the list of dependencies; the second 
phase would depend on the immediate dependencies and would encapsulate 
the set of immediate dependencies plus their immediate dependencies; and 
the third phase would come up only when all these dependencies are up.

For cases where there is no concept of transitivity, this may suffice. 
However it is the case in AS now where both the modules/class-path use 
case and the JNDI @Resource case do indeed include the concept of 
transitivity.

The N-Phase Solution
--------------------
The only way to ensure that some arbitrary set of transitive 
dependencies is available is by using a self-configuring N-phase 
mechanism.  The way this works is as follows.

1. We create a Create-phase service as today, which encodes as part of 
its value the set of immediate dependencies.  This would include the 
following behavior (pseudo-code):

    void start() {
       dependencies = immediate declared deps of this service
       if (dependencies is not empty) {
          create child service "resolver1";
          for each (d in dependencies) {
             add create phase of d to "resolver1" as dependency
          }
       } else {
          create child service "start";
       }
    }

2. Each resolver service has the following behavior:

    void start() {
       dependencies = set of all injected create-phase services
       if (dependencies is not empty) {
          create child service "resolver" + (n + 1);
          for each (d in dependencies) {
             for each (t in d.dependencies) {
                add create phase of t to "resolver" + (n + 1);
             }
          }
       } else {
          create child service "start";
       }
    }

This way, we retain the exact service structure we have today.  From an 
outside perspective the *create/*start services act just as they always 
have, except they accommodate transitive dependencies correctly by way 
of the "private" resolver dependencies.

This solution also has the advantage that it only creates as many 
services as necessary to resolve all transitive dependencies.

Optimization
------------
Because the transitive closure of services can be pretty hefty *and* may 
contain cycles, a good optimization would be to maintain a set of 
create-phase services that we've already depended on which we pass from 
resolver to resolver to ensure that we don't bother with redundant or 
cyclic dependencies.

Not Solved
----------
This solution does not address the problem where a dependency actually 
fails to start.  Assuming there is no @DependsOn to establish a direct 
acyclic dependency, the service will still start, but any attempt to use 
a failed dependency will in turn result in a failure just like today.

-- 
- DML


More information about the jboss-as7-dev mailing list