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.

Reply via email to