NP-Completeness Re: How to drain your laptop battery using Jigsaw

Jaroslav Tulach jaroslav.tulach at oracle.com
Thu Dec 22 11:50:13 PST 2011


>## 21. 12. 2011 23:23:44 ##<
> My colleague Jarda Tulach had investigated module systems supporting
> version ranges, and wrote up a formal proof [1] that picking one version
> of each of several modules in a repository such that all dependencies are
> satisfied is in general NP-complete. I decided to verify that Jigsaw
> suffers from this problem.
> 
> The attached program, run against a recent Jigsaw build (8c6429c279e7),
> starts consuming many seconds of CPU time per round (effectively hanging)
> somewhere around 9 variables (depending on the random seed), mostly
> running in Resolver.resolve according to a profiler. The randomized nature
> of the test and the clause count is designed to stress the system, of
> course.

First of all, congrats Jesse! This is a perfect real world demonstration of my 
theoretical work. I guess for many people it is more important to hear about 
"draining your batteries", than about facing an NP-complete problem.

Anyway, I am glad you brought this problem to the jigsaw-dev mailing list. It 
is in my opinion interesting topic and if there is a way to avoid pitfalls of 
range dependencies, I'll be glad to help with that.

> Jarda please step in if I misrepresented any of these options.

I've been thinking about these issues for last decade while working on 
NetBeans and its module system (helping you, Jesse). The NP-complete problem 
exists only when we allow implicit re-export. That means:

- using a library internally, without exporting it (and under the assumption 
that multiple versions of a library can co-exist, if not re-exported[4]) is 
safe. 

That is why the NP-complete problem is caused by re-export and actually only 
re-export that is done on implicit base. I think I have a proof[2] that shows 
that if an explicit "requires" of all transitive dependencies of a library are 
enumerated (at compile time), it is polynomial problem to resolve them during 
runtime.

Actually this is my favorite solution. I am not sure if it works with range 
dependencies, but clearly it works with semantic versioning[5] (different 
major versiong means incompatible version) as my proof shows[2].

Now few comments to other solutions outlined by Jesse:
> 1. Drop the requirement that module dependencies may include version ranges
> or otherwise permit incompatible changes to be marked, so that a
> dependency is always on some version or later. This seems unsatisfactory.

There is clearly a need for an incompatible version of a module. On the other 
hand, why not rename the name of the module when producing an incompatible 
version. So there would be:

org.w3c.dom (in few revisions like 1.0, 1.1, 1.2, etc.)

and then an incompatible change as

org.w3c.dom2 (in few revisions)

and later also

org.w3c.dom3 (with many revisions).

I am not saying I like changing the name of a module with every incompatible 
version, but it is not as unsatisfactory is it might seem. The biggest problem 
in this issue is to specify a dependency on either org.w3c.dom2 or 
org.w3c.dom3, if that is ever required.

> 2. Drop the requirement that the module system pick a version of each
> module to load such that all dependencies are satisfied. For example: just
> try to load the newest available version of each module, and if any
> dependencies are in fact unsatisfied, report an error and ask the user to
> override one or more module versions manually.

This is what we do in NetBeans and it works for a single packaged application. 
However I don't think this will work for Java module system where a single 
repository may contain modules for multiple applications. If you accept #2, it 
may happen that after installing new application (and its modules), some 
previously installed application can no longer be executed. That is not 
something that would boost the creation of Java module ecosystem.

> 3. Rewrite Resolver using something like SAT4J, or even leave it as it is,
> and just document that pathological cases can result in unbounded
> execution time.

E.g. use brute force. Well, the rationalistic approach commonly worshiped on 
my side of the Ocean is completely against approach like this. If we knew that 
range dependencies with implicit re-export are the problem, we would not allow 
them and think about better solution. Less powerful but computable[3] in 
polynomial time.

However I can imagine a pragmatic design (commonly used on the other side of 
the Ocean) is able to promise something unrealistic and then empower brute 
force tools like SAT4J (as Equinox does) to deal with "pathological" cases.

> 4. Require that a new release of any module which uses an incompatible
> version of some dependency (even if used only internally) is itself marked
> incompatible. This seems unsatisfactory since it breaks encapsulation - I
> should not be forced to label my new widely used graphics toolkit 2.0
> rather than 1.1 merely because I started to use a refactored version of
> some math library after 1.0 was released. 

Remember, this is only about re-exports! If I re-export (by returning it from 
a signature of my method) from my graphics toolkit java.awt.Rectangle and 
somebody removes it from JDK, there is no way around: just to produce an 
incompatible version of my graphical toolkit.

> Also it is unclear how you would
> enforce this constraint so long as Jigsaw assigns no semantics to version
> numbers except that they are totally ordered.

Jesse recently pointed out in our other conversation that there is a semantic 
versioning[5] manifesto. I believe this is much better option than having no 
semantic and fixing that by allowing range dependencies.

> 5. Permit multiple versions of a module to be loaded so long as class
> spaces remain consistent 

This is almost a must. Unless two versions of a class get re-exported for the 
same loader, this should be allowed (see [4]).

Summary. Those are the options me and Jesse see. However the primary question 
is whether Jigsaw wants to avoid NP-completeness or just play on the pragmatic 
feelings and pretend the NP-complete problem is just a corner case. I would be 
willing to invest my knowledge into eliminating the NP-completeness. I believe 
there are reasonable ways to avoid it. And last but not least: if there should 
be a module system that avoids NP-complete problems when resolving 
dependencies, it would be an ultimate reason which rules out OSGi.

-jt

> [1] http://wiki.apidesign.org/wiki/LibraryReExportIsNPComplete

[2] The proof is here 
http://wiki.apidesign.org/index.php?title=LibraryWithoutImplicitExportIsPolynomial&useskin=monobook
btw. it is better to switch to "Black/White" background when reading math 
statements on my website.

[3] http://wiki.apidesign.org/wiki/RangeDependencies

[4] Having two modules with same namespace is OK, if the module classes are 
called, but what if the module exports a service? Is not that going to cause 
problems?

[5] http://semver.org/ 



More information about the jigsaw-dev mailing list