Permit class loading after framework shutdown
---------------------------------------------
Key: FELIX-2128
URL: https://issues.apache.org/jira/browse/FELIX-2128
Project: Felix
Issue Type: Improvement
Components: Framework
Affects Versions: felix-2.0.3
Environment: Linux, JDK 6.
Reporter: Jesse Glick
Priority: Minor
In http://hg.netbeans.org/core-main/raw-file/default/core.netigso/test/unit/src/org/netbeans/core/osgi/ActivatorTest.java
I have some unit tests which repeatedly launch Felix, start some bundles, shut down, and repeat.
On occasion - more reproducibly if calls to System.gc() and System.runFinalization() are inserted
into ActivatorTest.setUp - I get errors like these (though the test still passes):
{noformat}
ERROR: JarContent: Unable to read bytes. (java.lang.IllegalStateException: zip file closed)
java.lang.IllegalStateException: zip file closed
at java.util.zip.ZipFile.ensureOpen(ZipFile.java:403)
at java.util.zip.ZipFile.getEntry(ZipFile.java:148)
at java.util.jar.JarFile.getEntry(JarFile.java:206)
at org.apache.felix.framework.util.JarFileX.getEntry(JarFileX.java:77)
at org.apache.felix.framework.cache.JarContent.getEntryAsBytes(JarContent.java:120)
at org.apache.felix.framework.ModuleImpl$ModuleClassLoader.findClass(ModuleImpl.java:1746)
at org.apache.felix.framework.ModuleImpl.findClassOrResourceByDelegation(ModuleImpl.java:723)
at org.apache.felix.framework.ModuleImpl.access$100(ModuleImpl.java:61)
at org.apache.felix.framework.ModuleImpl$ModuleClassLoader.loadClass(ModuleImpl.java:1698)
at java.lang.ClassLoader.loadClass(ClassLoader.java:248)
at java.lang.Class.getDeclaredMethods0(Native Method)
at java.lang.Class.privateGetDeclaredMethods(Class.java:2427)
at java.lang.Class.getMethod0(Class.java:2670)
at java.lang.Class.getMethod(Class.java:1603)
at org.openide.util.WeakListenerImpl$ListenerReference.getRemoveMethod(WeakListenerImpl.java:610)
at org.openide.util.WeakListenerImpl$ListenerReference.run(WeakListenerImpl.java:563)
at org.openide.util.lookup.implspi.ActiveQueue$Impl.run(ActiveQueue.java:73)
at java.lang.Thread.run(Thread.java:619)
Feb 23, 2010 3:22:53 PM org.openide.util.lookup.implspi.ActiveQueue$Impl run
WARNING: null
java.lang.NoClassDefFoundError: org/openide/loaders/FolderListListener
at java.lang.Class.getDeclaredMethods0(Native Method)
at java.lang.Class.privateGetDeclaredMethods(Class.java:2427)
at java.lang.Class.getMethod0(Class.java:2670)
at java.lang.Class.getMethod(Class.java:1603)
at org.openide.util.WeakListenerImpl$ListenerReference.getRemoveMethod(WeakListenerImpl.java:610)
at org.openide.util.WeakListenerImpl$ListenerReference.run(WeakListenerImpl.java:563)
at org.openide.util.lookup.implspi.ActiveQueue$Impl.run(ActiveQueue.java:73)
at java.lang.Thread.run(Thread.java:619)
Caused by: java.lang.ClassNotFoundException: org.openide.loaders.FolderListListener
at org.apache.felix.framework.ModuleImpl.findClassOrResourceByDelegation(ModuleImpl.java:779)
at org.apache.felix.framework.ModuleImpl.access$100(ModuleImpl.java:61)
at org.apache.felix.framework.ModuleImpl$ModuleClassLoader.loadClass(ModuleImpl.java:1698)
at java.lang.ClassLoader.loadClass(ClassLoader.java:248)
... 8 more
{noformat}
Here some code in a bundle has registered a special ReferenceQueue and is doing some minor
cleanup of recently finalized objects. Unfortunately running this code block can trigger fresh
class loading and JarContent throws an ISE when trying to load from the now-closed JAR file.
The situation is less likely to come up in a real app than in a unit test but still possible
- in case a bundle is dynamically unloaded, or some cleanup tasks happen to run during JVM
shutdown.
The timing of class loading is not easily predictable: it will occur any time a section of
code is run for the first time. Even in the absence of apparent threads, it is very hard to
guarantee that no class loading will take place after code ceases to be called externally,
since overridden finalize() methods and JVM shutdown hooks can be called passively at any
time. The code in this example could disable its RQ upon BundleActivator.stop if it were originally
written for use inside OSGi, but it is not.
I have come up with a patch to JarFileX which lets it load classes from nominally closed JARs
on an emergency basis. (This was implemented years ago in the NetBeans module system.) To
make it safer for the original JAR to be recreated or deleted, especially on Windows with
its mandatory file locks, a temporary copy is made.
(Safest would be to copy the original JAR eagerly in close(), but this would impose a huge
performance penalty. Instead, the JAR is copied on demand only in cases where an ISE would
otherwise be thrown. It is possible for the JAR to be modified/deleted after close() but before
the next class load, in which case the ISE will still occur; similarly if a SecurityManager
prevents the copying, etc.)
It is not clear to me from the OSGi spec whether it is permissible for the bundle class loader
to continue to function after framework shutdown (or generally after a bundle moves into an
unresolved state). The spec seems to say that Bundle.loadClass should throw ISE, but this
is different from performing implicit class loading at the VM's request as part of running
already-loaded code. For what it's worth, 4.4.10 does say "all old exports must remain available
for existing bundles and future resolves until the refreshPackages method is called or the
Framework is restarted". While more permissive behavior is very useful for situations like
these, if it contradicts the spec, I might suggest one or both of the following:
1. Enable emergency loading only with an optional Felix framework property. Then, for example,
unit tests which knew they would be starting and stopping code which potentially left behind
live threads or finalizer queues etc. could set the property to avoid printing such exceptions.
2. At least report when close() was called to assist the user in debugging the problem.
--
This message is automatically generated by JIRA.
-
You can reply to this email to add a comment to the issue online.