Details
Description
Originally reported as a bug in the NetBeans IDE:
http://www.netbeans.org/issues/show_bug.cgi?id=69727
Behavior of NetBeans will be improved to be more robust, but I think it is ultimately a bug in Ivy's Ant tasks that can and should be fixed.
Background: NB runs Ant processes in the same VM as the IDE, for various reasons; normally it will load Ant and any configured Ant extension libraries in a special class loader to help insulate it from the IDE's environment. Each Ant "process" (top-level invocation of one or more targets in a given build script) gets its own o.a.t.a.Project as usual. Due to historical experience that occasionally bugs either in the Ant core, standard Ant tasks, or third-party tasks can produce memory leaks, NB attempts to null out (nonstatic) fields in Project instances after the Ant process has finished - this helps break reference chains that could otherwise leak memory.
It was found that when running Ivy tasks in NB, sometimes a NullPointerException would be thrown from Project.fireMessageLoggedEvent, meaning that the Vector Project.listeners field was null. Presumably NB was setting it to null in its attempt to clean up, so I changed NB to effectively call listeners.clear() rather than listeners = null, and that seems to have fixed the problem.
The odd thing is that the NPE is thrown before the Project is cleaned up - and never the first time the project is built in a given IDE session. A little thought reveals that in fact the NPE is coming from a method call on the old Project instance, not the one which the IDE thinks it is running!
Normally this would be impossible, since the IDE creates and then discards a Project for every build. But an inspection of Ivy sources revealed that Ivy actually caches a Project instance in a static field. Specifically:
1. Some task, e.g. IvyRetrieve, is called from Project p1.
2. IvyTask.getIvyInstance calls ensureMessageInitialized, calling Message.init with a AntMessageImpl(p1).
3. The task runs to completion, as does the build.
4. p1 is "gutted" so p1.listeners == null.
5. Now IvyRetrieve is called again, from Project p2.
6. Message.isInitialized(), so eMI() is a no-op.
7. Someone calls a Message logging method. This delegates to AMI(p1) which delegates to p1.log which throws an NPE.
So the root problem is the static field Message._impl. It stays around after the build is finished. In command-line Ant this is not an issue, but if a new Project is created, Message._impl fails to delegate to it.
Suggested fix: IT.eMI() should look something like
if (!Message.isInitialised()) {
Message.init(new AntMessageImpl(getProject()));
getProject().addBuildListener(new BuildListener() {
public void buildFinished(...)
...other methods blank...
});
}
where Message.uninit() would set _impl = null. At least, that seems to be the usual idiom used in various Ant tasks that need to keep a static cache of something. You may consider implementing SubBuildListener too, and cleaning up on subBuildFinished. An example is the standard Recorder task in Ant, which was fixed in 1.7 to do this kind of clean up:
http://issues.apache.org/bugzilla/show_bug.cgi?id=20053
Likely impacts, beyond the reported NB bug, on any environment which calls Ant as an embedded process (possibly other IDEs, probably continuous integration tools, ...):
1. Memory leaks: if the Project is holding onto other big data structures (as is quite common: think of all the references it keeps), Message._impl._project will keep those structures in memory indefinitely after the Ant process finishes.
2. Logging problems: a container is probably not expected messages to be logged from a Project which it now considers dead. Who knows where such messages would be sent; they might be silently lost, or might trigger other errors (e.g. attempt to write to a closed log file handle).