DO NOT REPLY TO THIS EMAIL, BUT PLEASE POST YOUR BUG RELATED COMMENTS THROUGH THE WEB INTERFACE AVAILABLE AT <http://nagoya.apache.org/bugzilla/show_bug.cgi?id=9305>. ANY REPLY MADE TO THIS MESSAGE WILL NOT BE COLLECTED AND INSERTED IN THE BUG DATABASE.
http://nagoya.apache.org/bugzilla/show_bug.cgi?id=9305 Use thread context class loader for loading ------- Additional Comments From [EMAIL PROTECTED] 2002-05-22 09:22 ------- Scott, This what I started writing: --- BEGIN ---------------- The following is from the docs/HISTORY file Release of log4j-1.2beta4 - Replaced the custom class loading based on the thread context class loader with a simple Class.forName() call. This solves two allied but distinct problems encountered when using Ant with JUnit although the bug is more general. In one instance of the problem, log4j would throw java.lang.NoClassDefFoundError for org/apache/log4j/AppenderSkeleton where log4j.jar and related classes were clearly available to the Ant classloader. In another incarnation, log4j would reject a custom appender claiming that it is not assignable to a org.apache.log4j.Appender variable. This would occur when log4j.jar was available to both the Ant classloader and the system classloader. Thanks to Dave Herman for providing detailed scenarios exposing the issues involved. See http://forum.java.sun.com/thread.jsp?forum=38&thread=70946 http://forum.java.sun.com/thread.jsp?forum=38&thread=70946#479697 http://marc.theaimsgroup.com/?l=ant-user&m=101139178705895&w=2 for more details. [*] In conclusion, while loading classes through the context class loader has advantages, it also has disadvantages. I would like to learn more about the exact scenario you are confronted with before making a decision. Moreover, please be more specific about the "many advantages" you see. --- END --------------------- Then I started reading the discussion threads on forum.java.sun.com to check whether the fix in log4j-1.2beta4 still made sense. (Until log4j-1.2beta4, alpha and beta versions of log4j used the context class loader.) Fortunately, I also had test cases reproducing the issues that Dave Herman had observed. In one scenario, log4j-1.2beta2.jar was available to the ANT class loader but not to the system class loader. Still in the same scenario, a custom appender, named foo.MyAppender, would be available to system class loader but not to the ANT class loader. In this scenario, running a simple test case that tried to configure log4j by instantiating foo.MyAppender and adding it to the root logger would fail with the following error. junit: [junit] Running foo.junit.MyTestCase [junit] Tests run: 1, Failures: 0, Errors: 1, Time elapsed: 0.05 sec [junit] Output: [junit] debug: Test case <= org.apache.tools.ant.AntClassLoader [junit] debug: Thread context <= sun.misc.Launcher$AppClassLoader [junit] [junit] Testsuite: foo.junit.MyTestCase [junit] Tests run: 1, Failures: 0, Errors: 1, Time elapsed: 0.05 sec [junit] ------------- Standard Output --------------- [junit] debug: Test case <= org.apache.tools.ant.AntClassLoader [junit] debug: Thread context <= sun.misc.Launcher$AppClassLoader [junit] ------------- ---------------- --------------- [junit] [junit] Testcase: testCustomAppender took 0.04 sec [junit] Caused an ERROR [junit] org/apache/log4j/AppenderSkeleton [junit] java.lang.NoClassDefFoundError: org/apache/log4j/AppenderSkeleton [junit] at java.lang.ClassLoader.defineClass0(Native Method) <snip> [junit] at java.security.AccessController.doPrivileged(Native Method) [junit] at java.net.URLClassLoader.findClass(URLClassLoader.java:188) [junit] at java.lang.ClassLoader.loadClass(ClassLoader.java:297) [junit] at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:286) [junit] at java.lang.ClassLoader.loadClass(ClassLoader.java:253) [junit] at org.apache.log4j.helpers.Loader.loadClass(Loader.java:124) [junit] at org.apache.log4j.helpers.OptionConverter.instantiateByClassName (OptionConverter.java:313) [junit] at org.apache.log4j.helpers.OptionConverter.instantiateByKey (OptionConverter.java:116) [junit] at org.apache.log4j.PropertyConfigurator.parseAppender (PropertyConfigurator.java:619) <snip> So AppenderSkeleton could not be found because log4j-1.2beta2.jar was not available to the system class loader and as no context class loader was set, the context class loader used defaulted to the system class loader. As I said previously, changing log4j code to use only Class.forName for class loading solves the above problem since both log4j-1.2beta2.jar and foo.MyAppender are available to the ANT class loader and its parent the system class loader. In the original scenario that Dave Herman had in mind, both the system class loader and the ANT class loader had log4j-1.2beta2.jar available to them but only the system class loader had foo.MyAppender available. Running this would result in the following error: junit: [junit] Running foo.junit.MyTestCase [junit] Tests run: 1, Failures: 1, Errors: 0, Time elapsed: 0.05 sec [junit] Error: [junit] debug: Test case <= org.apache.tools.ant.AntClassLoader [junit] debug: Thread context <= sun.misc.Launcher$AppClassLoader [junit] log4j:ERROR A "foo.MyAppender" object is not assignable to a "org.apache.log4j.Appender" variable. [junit] log4j:ERROR Could not instantiate appender named "Foo". <snip> In this case, log4j refused to use foo.MyAppender because it was not assignable to o.a.log4j.Appender. This can be explained by the fact that log4j's copy of o.a.log4j.Appender was loaded by the ANT class loader whereas the foo.MyAppender was loaded by the system class loader. Simply ignoring the context loader and using only Class.forName also fixed this scenario. So I was "tricked" into thinking that Class.forName was the way to go. What I did not realize was that there was a bug in our implementation of o.a.log4j.helpers.Loader.loadClass() method which was ultimately responsible to loading classes. The implementation was: /** Load the specified class using the <code>Thread</code> <code>contextClassLoader</code> if running under Java2 or current class loader if running under JDK 1.1. */ static public Class loadClass (String clazz) throws ClassNotFoundException { if(java1) { return Class.forName(clazz); } else { try { return Thread.currentThread().getContextClassLoader().loadClass(clazz); } catch(Exception e) { // we reached here because // currentThread().getContextClassLoader() is null or because // of a security exceptio, or because clazz could not be // loaded, in any case we now try one more time return Class.forName(clazz); } } } This implementation is sub optimal first because it does not compile with JDK 1.1 and more importantly because it is buggy. The java.lang.NoClassDefFoundError (see first scenario above) is not an Exception but an Error. This means that the code within the catch block will never be executed. Changing the Loader.loadClass code to static public Class loadClass (String clazz) throws ClassNotFoundException { if(java1) { return Class.forName(clazz); } else { try { return Thread.currentThread().getContextClassLoader().loadClass(clazz); } catch(Throwable e) { // <-- was originally Exception return Class.forName(clazz); } } } } solves the problem encountered in scenario one. It does not solve the problem encountered in the second scenario. More generally, if log4j-XX.jar is available to both the context class loader as well as to the class loader that loaded the code currently being executed, then a log4j component (e.g. an appender) may be loaded by the context class loader such that it is incompatible with the log4j classes loaded by the class loader that loaded the code currently being executed. How far fetched is this scenario? Well, I am almost convinced that someone will come up with a case where it looks perfectly legitimate. To make a long story short, I still would like to learn more about the exact scenario you are confronted with before making a decision. -- To unsubscribe, e-mail: <mailto:[EMAIL PROTECTED]> For additional commands, e-mail: <mailto:[EMAIL PROTECTED]>